json api WIP patch

Started by Andrew Dunstanabout 13 years ago71 messages
#1Andrew Dunstan
andrew@dunslane.net
1 attachment(s)

Here is a patch for the first part of the JSON API that was recently
discussed. It includes the json parser hook infrastructure and
functions for json_get and friends, plus json_keys.

As is, this exposes the json lexer fully for use by the hook functions.
But I could easily be persuaded that this should be an opaque structure
with some constructor and getter functions - I don't think the hook
functions need or should be able to set anything in the lexer.

Work is proceeding on some of the more advanced functionality discussed.

cheers

andrew

Attachments:

jsonapi1.patchtext/x-patch; name=jsonapi1.patchDownload
*** 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,81 ****
  #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);
--- 24,60 ----
  #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 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 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);
***************
*** 88,93 **** static void array_dim_to_json(StringInfo result, int dim, int ndims, int *dims,
--- 67,125 ----
  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 */
+ 
+ static inline JsonTokenType
+ lex_peek(JsonLexContext *lex)
+ {
+     return lex->token_type;
+ }
+ 
+ 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;
+ }
+ 
+ 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'
  /* letters appearing in numeric output that aren't valid in a JSON number */
***************
*** 172,325 **** json_recv(PG_FUNCTION_ARGS)
  }
  
  /*
!  * 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;
! 		}
  	}
  }
  
  /*
--- 204,383 ----
  }
  
  /*
!  * parse routines
   */
+ 
+ 
+ void
+ pg_parse_json(JsonLexContext *lex, JsonSemAction sem)
+ {
+ 
+     /* get the initial token */
+     json_lex(lex);
+ 
+ 
+     /* parse by recursive descent */
+     if (lex_peek(lex) == JSON_TOKEN_OBJECT_START)
+         parse_object(lex, sem);
+     else if (lex_peek(lex) == JSON_TOKEN_ARRAY_START)
+         parse_array(lex, sem);
+     else
+         parse_scalar(lex, sem);/* json can be a bare scalar */
+ 
+     lex_expect(JSON_PARSE_END, lex, JSON_TOKEN_END);
+ 
+ }
+ 
+ 
  static void
! parse_scalar(JsonLexContext *lex, JsonSemAction sem)
  {
!     char *val = NULL;
!     json_scalar_action sfunc = sem->scalar;
!     JsonTokenType tok = lex_peek(lex);
! 
!     if (lex_accept(lex, JSON_TOKEN_TRUE, &val) ||
!         lex_accept(lex, JSON_TOKEN_FALSE, &val) ||
!         lex_accept(lex, JSON_TOKEN_NULL, &val) ||
!         lex_accept(lex, JSON_TOKEN_NUMBER, &val) ||
!         lex_accept(lex, JSON_TOKEN_STRING, &val))
!     {
!         if (sfunc != NULL)
!             (*sfunc) (sem->semstate, val, tok);
!     }
!     else
!     {
!         report_parse_error(JSON_PARSE_VALUE, lex);
!     }
! }
  
! static void
! parse_object_field(JsonLexContext *lex, JsonSemAction sem)
! {
  
!     char *fname = NULL; /* keep compiler quiet */
!     json_ofield_action ostart = sem->object_field_start;
!     json_ofield_action oend = sem->object_field_end;
!     bool isnull;
  
!     if (! lex_accept(lex, JSON_TOKEN_STRING, &fname))
!         report_parse_error(JSON_PARSE_STRING, lex); 
  
!     lex_expect(JSON_PARSE_OBJECT_LABEL, lex, JSON_TOKEN_COLON);
  
!     isnull = lex_peek(lex) == JSON_TOKEN_NULL;
  
!     if (ostart != NULL)
!         (*ostart) (sem->semstate, fname, isnull);
  
!     if (lex_peek(lex) == JSON_TOKEN_OBJECT_START)
!         parse_object(lex, sem);
!     else if (lex_peek(lex) == JSON_TOKEN_ARRAY_START)
!         parse_array(lex,sem);
!     else
!         parse_scalar(lex, sem);
! 
!     if (oend != NULL)
!         (*oend) (sem->semstate, fname, isnull);
! }
! 
! static void 
! parse_object(JsonLexContext *lex, JsonSemAction sem)
! {
!     
!     json_struct_action ostart = sem->object_start;
!     json_struct_action oend = sem->object_end;
! 
!     if (ostart != NULL)
!         (*ostart) (sem->semstate);
! 
! 	/* we know this will succeeed, just clearing the token */
!     lex_expect(JSON_PARSE_OBJECT_START, lex, JSON_TOKEN_OBJECT_START);
!     if (lex_peek(lex) == JSON_TOKEN_STRING)
!     {
!         parse_object_field(lex, sem);
! 
!         while (lex_accept(lex,JSON_TOKEN_COMMA,NULL))
!                 parse_object_field(lex, sem);
!         
!     }
! 	else if (lex_peek(lex) != JSON_TOKEN_OBJECT_END)
! 	{
! 		/* 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);
+ 
+     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;
+     bool isnull;
+ 
+     isnull = lex_peek(lex) == JSON_TOKEN_NULL;
+ 
+     if (astart != NULL)
+         (*astart) (sem->semstate, isnull);
+ 
+     if (lex_peek(lex) == JSON_TOKEN_OBJECT_START)
+         parse_object(lex, sem);
+     else if (lex_peek(lex) == JSON_TOKEN_ARRAY_START)
+         parse_array(lex, sem);
+     else
+         parse_scalar(lex, sem);
+ 
+     if (aend != NULL)
+         (*aend) (sem->semstate, isnull);
+ }
+ 
+ static void 
+ parse_array(JsonLexContext *lex, JsonSemAction sem)
+ {
+     json_struct_action astart = sem->array_start;
+     json_struct_action aend = sem->array_end;
+ 
+     if (astart != NULL)
+         (*astart) (sem->semstate);
+ 
+     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);
+ 
+     if (aend != NULL)
+         (*aend) (sem->semstate);
+ }
+ 
+ /*
+  * Check whether supplied input is valid JSON.
+  */
+ static void
+ json_validate_cstring(char *input)
+ {
+ 
+ 	JsonLexContext lex;
+ 
+ 	/* Set up lexing context. */
+ 	lex.input = input;
+ 	lex.token_terminator = lex.input;
+ 	lex.line_number = 1;
+ 	lex.line_start = input;
+ 	lex.strval = NULL; /* don't care about de-escaped lexemes */
+ 	
+ 	pg_parse_json(&lex, NullSemAction);
  }
  
  /*
***************
*** 329,399 **** 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);
  		}
--- 387,480 ----
  json_lex(JsonLexContext *lex)
  {
  	char	   *s;
! 	char       buff[1024];
  	/* Skip leading whitespace. */
  	s = lex->token_terminator;
  	while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r')
! 	{
! 		if (*s == '\n')
! 			++lex->line_number;
! 		++s;
! 	}
  	lex->token_start = s;
  
  	/* Determine token type. */
! 	if (*s == '\0')
  	{
! 		lex->token_start = NULL;
! 		lex->prev_token_terminator = lex->token_terminator;
! 		lex->token_terminator = s;
! 		lex->token_type =  JSON_TOKEN_END;
! 	}
! 	else if (strchr("{}[],:", s[0]))
! 	{
! 		/* Single-character token, some kind of punctuation mark. */
! 		lex->prev_token_terminator = lex->token_terminator;
! 		lex->token_terminator = s + 1;
! 		switch (s[0])
  		{
! 			case '{': 
! 				lex->token_type = JSON_TOKEN_OBJECT_START; 
! 				break;
! 			case '}':
! 				lex->token_type = JSON_TOKEN_OBJECT_END; 
! 				break;
! 			case '[': 
! 				lex->token_type = JSON_TOKEN_ARRAY_START; 
! 				break;
! 			case ']':
! 				lex->token_type = JSON_TOKEN_ARRAY_END; 
! 				break;
! 			case ',':
! 				lex->token_type = JSON_TOKEN_COMMA; 
! 				break;
! 			case ':':
! 				lex->token_type = JSON_TOKEN_COLON; 
! 				break;
! 			default:
! 				break;
  		}
  	}
  	else if (*s == '"')
  	{
  		/* String. */
  		json_lex_string(lex);
! 		lex->token_type = JSON_TOKEN_STRING;
  	}
  	else if (*s == '-')
  	{
  		/* Negative number. */
  		json_lex_number(lex, s + 1);
! 		lex->token_type = JSON_TOKEN_NUMBER;
  	}
  	else if (*s >= '0' && *s <= '9')
  	{
  		/* Positive number. */
  		json_lex_number(lex, s);
! 		lex->token_type = JSON_TOKEN_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 */ ;
  
+ 		/*
+ 		 * 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);
  		}
***************
*** 402,421 **** json_lex(JsonLexContext *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);
  	}
  }
  
--- 483,505 ----
  		 * 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);
+ 		strncpy(buff, s, p-s);
+ 		buff[p-s] = '\0';
  	}
  }
  
***************
*** 427,432 **** json_lex_string(JsonLexContext *lex)
--- 511,519 ----
  {
  	char	   *s;
  
+ 	if (lex-> strval != NULL)
+ 		resetStringInfo(lex->strval);
+ 
  	for (s = lex->token_start + 1; *s != '"'; s++)
  	{
  		/* Per RFC4627, these characters MUST be escaped. */
***************
*** 485,506 **** 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),
! 						 errmsg("invalid input syntax for type json"),
! 						 errdetail("Escape sequence \"\\%s\" is invalid.",
! 								   extract_mb_char(s)),
! 						 report_json_context(lex)));
  			}
  		}
  	}
  
  	/* Hooray, we found the end of the string! */
  	lex->token_terminator = s + 1;
  }
  
--- 572,652 ----
  								 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
+ 				 */
+                 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 (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;
  }
  
***************
*** 512,528 **** json_lex_string(JsonLexContext *lex)
   * (1) An optional minus sign ('-').
   *
   * (2) Either a single '0', or a string of one or more digits that does not
!  *	   begin with a '0'.
   *
   * (3) An optional decimal part, consisting of a period ('.') followed by
!  *	   one or more digits.	(Note: While this part can be omitted
!  *	   completely, it's not OK to have only the decimal point without
!  *	   any digits afterwards.)
   *
   * (4) An optional exponent part, consisting of 'e' or 'E', optionally
!  *	   followed by '+' or '-', followed by one or more digits.	(Note:
!  *	   As with the decimal part, if 'e' or 'E' is present, it must be
!  *	   followed by at least one digit.)
   *
   * The 's' argument to this function points to the ostensible beginning
   * of part 2 - i.e. the character after any optional minus sign, and the
--- 658,674 ----
   * (1) An optional minus sign ('-').
   *
   * (2) Either a single '0', or a string of one or more digits that does not
!  *     begin with a '0'.
   *
   * (3) An optional decimal part, consisting of a period ('.') followed by
!  *     one or more digits.  (Note: While this part can be omitted
!  *     completely, it's not OK to have only the decimal point without
!  *     any digits afterwards.)
   *
   * (4) An optional exponent part, consisting of 'e' or 'E', optionally
!  *     followed by '+' or '-', followed by one or more digits.  (Note:
!  *     As with the decimal part, if 'e' or 'E' is present, it must be
!  *     followed by at least one digit.)
   *
   * The 's' argument to this function points to the ostensible beginning
   * of part 2 - i.e. the character after any optional minus sign, and the
***************
*** 533,540 **** 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. */
--- 679,686 ----
  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. */
***************
*** 584,596 **** json_lex_number(JsonLexContext *lex, char *s)
  		}
  	}
  
! 	/*
! 	 * 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);
--- 730,739 ----
  		}
  	}
  
! 	/* Check for trailing garbage. */
  	for (p = s; JSON_ALPHANUMERIC_CHAR(*p); p++)
  		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"),
--- 745,757 ----
   * 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"),
--- 765,771 ----
  	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,
--- 774,780 ----
  				 report_json_context(lex)));
  	else
  	{
! 		switch (ctx)
  		{
  			case JSON_PARSE_VALUE:
  				ereport(ERROR,
***************
*** 641,646 **** report_parse_error(JsonParseStack *stack, JsonLexContext *lex)
--- 784,797 ----
  								   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),
***************
*** 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);
  		}
  	}
  }
--- 841,847 ----
  						 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);
--- 936,942 ----
  	 * suffixing "..." if not ending at end of line.
  	 */
  	prefix = (context_start > line_start) ? "..." : "";
! 	suffix = (lex->token_type != JSON_TOKEN_END && *context_end != '\0' && *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,478 ----
+ /*-------------------------------------------------------------------------
+  *
+  * jsonfuncs.c
+  *		Functions to process JSON data type.
+  *
+  * Portions Copyright (c) 1996-2012, 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 "fmgr.h"
+ #include "funcapi.h"
+ #include "lib/stringinfo.h"
+ #include "mb/pg_wchar.h"
+ #include "utils/builtins.h"
+ #include "utils/json.h"
+ #include "utils/jsonapi.h"
+ 
+ 
+ static void okeys_object_start(void *state);
+ static void okeys_object_end(void *state);
+ static void okeys_object_field_start(void *state, char *fname, bool isnull);
+ static void okeys_array_start(void *state);
+ static void okeys_array_end(void *state);
+ static void okeys_scalar(void *state, char *token, JsonTokenType tokentype);
+ 
+ static void get_object_start(void *state);
+ static void get_object_end(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_end(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);
+ static text *get_worker(char *json, char *field, int elem_index, bool normalize_results);
+ 
+ typedef enum
+ {
+ 	JSON_SEARCH_OBJECT = 1,
+ 	JSON_SEARCH_ARRAY
+ }	JsonSearch;
+ 
+ typedef struct
+ {
+ 	int			lex_level;
+ 	char	  **result;
+ 	int			result_size;
+ 	int			result_count;
+ 	int			sent_count;
+ }	okeysState, *OkeysState;
+ 
+ 
+ typedef struct
+ {
+ 	JsonLexContext *lex;
+ 	int			lex_level;
+ 	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;
+ }	getState, *GetState;
+ 
+ 
+ 
+ PG_FUNCTION_INFO_V1(json_object_keys);
+ 
+ Datum
+ json_object_keys(PG_FUNCTION_ARGS)
+ {
+ 	FuncCallContext *funcctx;
+ 	OkeysState	state;
+ 	int			i;
+ 
+ 	if (SRF_IS_FIRSTCALL())
+ 	{
+ 		text	   *json = PG_GETARG_TEXT_P(0);
+ 		char	   *jsonstr = text_to_cstring(json);
+ 		JsonLexContext lex;
+ 		JsonSemAction sem;
+ 
+ 		MemoryContext oldcontext;
+ 
+ 		funcctx = SRF_FIRSTCALL_INIT();
+ 		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+ 
+ 		state = palloc(sizeof(okeysState));
+ 		sem = palloc0(sizeof(jsonSemAction));
+ 
+ 		state->result_size = 256;
+ 		state->result_count = 0;
+ 		state->sent_count = 0;
+ 		state->lex_level = 0;
+ 		state->result = palloc(256 * sizeof(char *));
+ 
+ 		sem->semstate = (void *) state;
+ 		sem->object_start = okeys_object_start;
+ 		sem->object_end = okeys_object_end;
+ 		sem->array_start = okeys_array_start;
+ 		sem->array_end = okeys_array_end;
+ 		sem->scalar = okeys_scalar;
+ 		sem->object_field_start = okeys_object_field_start;
+ 		/* remainder are all NULL, courtesy of palloc0 above */
+ 
+ 		/* Set up lexing context. */
+ 		lex.input = jsonstr;
+ 		lex.token_terminator = lex.input;
+ 		lex.line_number = 1;
+ 		lex.line_start = jsonstr;
+ 		lex.strval = makeStringInfo();
+ 
+ 		pg_parse_json(&lex, sem);
+ 		/* keys are now in state->result */
+ 
+ 		pfree(lex.strval->data);
+ 		pfree(lex.strval);
+ 		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_start(void *state)
+ {
+ 	OkeysState	_state = (OkeysState) state;
+ 
+ 	_state->lex_level++;
+ }
+ 
+ static void
+ okeys_object_end(void *state)
+ {
+ 	OkeysState	_state = (OkeysState) state;
+ 
+ 	_state->lex_level--;
+ }
+ 
+ static void
+ okeys_object_field_start(void *state, char *fname, bool isnull)
+ {
+ 	OkeysState	_state = (OkeysState) state;
+ 
+ 	if (_state->lex_level != 1)
+ 		return;
+ 	if (_state->result_count >= _state->result_size)
+ 	{
+ 		_state->result_size *= 2;
+ 		_state->result =
+ 			repalloc(_state->result, sizeof(char *) * _state->result_size);
+ 	}
+ 	_state->result[_state->result_count++] = pstrdup(fname);
+ }
+ 
+ static void
+ okeys_array_start(void *state)
+ {
+ 	OkeysState	_state = (OkeysState) state;
+ 
+ 	_state->lex_level++;
+ 	if (_state->lex_level == 1)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ 				 errmsg("cannot call json_object_keys on an array")));
+ }
+ 
+ static void
+ okeys_array_end(void *state)
+ {
+ 	OkeysState	_state = (OkeysState) state;
+ 
+ 	_state->lex_level--;
+ }
+ 
+ static void
+ okeys_scalar(void *state, char *token, JsonTokenType tokentype)
+ {
+ 	OkeysState	_state = (OkeysState) state;
+ 
+ 	if (_state->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ 				 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.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_get_ofield);
+ 
+ Datum
+ json_get_ofield(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	text	   *fname = PG_GETARG_TEXT_P(1);
+ 	char	   *jsonstr = text_to_cstring(json);
+ 	char	   *fnamestr = text_to_cstring(fname);
+ 	text	   *result;
+ 
+ 	result = get_worker(jsonstr, fnamestr, -1, false);
+ 
+ 	if (result != NULL)
+ 		PG_RETURN_TEXT_P(result);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ PG_FUNCTION_INFO_V1(json_get_ofield_as_text);
+ 
+ Datum
+ json_get_ofield_as_text(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	text	   *fname = PG_GETARG_TEXT_P(1);
+ 	char	   *jsonstr = text_to_cstring(json);
+ 	char	   *fnamestr = text_to_cstring(fname);
+ 	text	   *result;
+ 
+ 	result = get_worker(jsonstr, fnamestr, -1, true);
+ 
+ 	if (result != NULL)
+ 		PG_RETURN_TEXT_P(result);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ PG_FUNCTION_INFO_V1(json_get_aelem);
+ 
+ Datum
+ json_get_aelem(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	int			element = PG_GETARG_INT32(1);
+ 	char	   *jsonstr = text_to_cstring(json);
+ 	text	   *result;
+ 
+ 	result = get_worker(jsonstr, NULL, element, false);
+ 
+ 	if (result != NULL)
+ 		PG_RETURN_TEXT_P(result);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ 
+ PG_FUNCTION_INFO_V1(json_get_aelem_as_text);
+ 
+ Datum
+ json_get_aelem_as_text(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	int			element = PG_GETARG_INT32(1);
+ 	char	   *jsonstr = text_to_cstring(json);
+ 	text	   *result;
+ 
+ 	result = get_worker(jsonstr, NULL, element, true);
+ 
+ 	if (result != NULL)
+ 		PG_RETURN_TEXT_P(result);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ 
+ static text *
+ get_worker(char *json, char *field, int elem_index, bool normalize_results)
+ {
+ 	GetState	state;
+ 	JsonLexContext lex;
+ 	JsonSemAction sem;
+ 
+ 	state = palloc0(sizeof(getState));
+ 	sem = palloc0(sizeof(jsonSemAction));
+ 
+ 	state->lex_level = 0;
+ 	state->lex = &lex;
+ 	state->normalize_results = normalize_results;
+ 	if (field != NULL)
+ 	{
+ 		state->search_type = JSON_SEARCH_OBJECT;
+ 		state->search_term = field;
+ 	}
+ 	else
+ 	{
+ 		state->search_type = JSON_SEARCH_ARRAY;
+ 		state->search_index = elem_index;
+ 		state->array_index = -1;
+ 	}
+ 
+ 	sem->semstate = (void *) state;
+ 	sem->object_start = get_object_start;
+ 	sem->object_end = get_object_end;
+ 	sem->array_start = get_array_start;
+ 	sem->array_end = get_array_end;
+ 	sem->scalar = get_scalar;
+ 	if (field != NULL)
+ 	{
+ 		sem->object_field_start = get_object_field_start;
+ 		sem->object_field_end = get_object_field_end;
+ 	}
+ 	else
+ 	{
+ 		sem->array_element_start = get_array_element_start;
+ 		sem->array_element_end = get_array_element_end;
+ 	}
+ 
+ 	/* Set up lexing context. */
+ 	lex.input = json;
+ 	lex.token_terminator = lex.input;
+ 	lex.line_number = 1;
+ 	lex.line_start = json;
+ 	lex.strval = makeStringInfo();
+ 
+ 	pg_parse_json(&lex, sem);
+ 
+ 	return state->tresult;
+ }
+ 
+ static void
+ get_object_start(void *state)
+ {
+ 	GetState	_state = (GetState) state;
+ 
+ 	_state->lex_level++;
+ 	if (_state->lex_level == 1 && _state->search_type != JSON_SEARCH_OBJECT)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ 				 errmsg("cannot call json_get(int) on a non-array")));
+ }
+ 
+ static void
+ get_object_end(void *state)
+ {
+ 	GetState	_state = (GetState) state;
+ 
+ 	_state->lex_level--;
+ }
+ 
+ static void
+ get_object_field_start(void *state, char *fname, bool isnull)
+ {
+ 	GetState	_state = (GetState) state;
+ 
+ 	if (_state->lex_level == 1 && _state->search_type == JSON_SEARCH_OBJECT &&
+ 		strcmp(fname, _state->search_term) == 0)
+ 	{
+ 		if (_state->result_start != NULL)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ 					 errmsg("field name is not unique in json object")));
+ 		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_object_field_end(void *state, char *fname, bool isnull)
+ {
+ 	GetState	_state = (GetState) state;
+ 
+ 	if (_state->lex_level == 1 && _state->search_type == JSON_SEARCH_OBJECT &&
+ 	 _state->result_start != NULL && strcmp(fname, _state->search_term) == 0)
+ 	{
+ 		int			len = _state->lex->prev_token_terminator - _state->result_start;
+ 
+ 		_state->tresult = cstring_to_text_with_len(_state->result_start, len);
+ 	}
+ }
+ 
+ static void
+ get_array_start(void *state)
+ {
+ 	GetState	_state = (GetState) state;
+ 
+ 	_state->lex_level++;
+ 	if (_state->lex_level == 1 && _state->search_type != JSON_SEARCH_ARRAY)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ 				 errmsg("cannot call json_get(fieldname) on a non-object")));
+ }
+ 
+ static void
+ get_array_end(void *state)
+ {
+ 	GetState	_state = (GetState) state;
+ 
+ 	_state->lex_level--;
+ }
+ 
+ static void
+ get_array_element_start(void *state, bool isnull)
+ {
+ 	GetState	_state = (GetState) state;
+ 
+ 	if (_state->lex_level == 1 && _state->search_type == JSON_SEARCH_ARRAY)
+ 	{
+ 		_state->array_index++;
+ 		if (_state->array_index == _state->search_index)
+ 		{
+ 			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;
+ 
+ 	if (_state->lex_level == 1 && _state->search_type == JSON_SEARCH_ARRAY &&
+ 		_state->array_index == _state->search_index && _state->result_start != NULL)
+ 	{
+ 		int			len = _state->lex->prev_token_terminator - _state->result_start;
+ 
+ 		_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_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ 				 errmsg("cannot call json_get on a scalar")));
+ 	if (_state->next_scalar)
+ 	{
+ 		_state->tresult = cstring_to_text(token);
+ 		_state->next_scalar = false;
+ 	}
+ 
+ }
*** a/src/include/catalog/pg_operator.h
--- b/src/include/catalog/pg_operator.h
***************
*** 1724,1729 **** DESCR("range difference");
--- 1724,1739 ----
  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");
+ 
  
  /*
   * function prototypes
***************
*** 1733,1740 **** extern void OperatorCreate(const char *operatorName,
  			   Oid leftTypeId,
  			   Oid rightTypeId,
  			   Oid procedureId,
! 			   List *commutatorName,
! 			   List *negatorName,
  			   Oid restrictionId,
  			   Oid joinId,
  			   bool canMerge,
--- 1743,1750 ----
  			   Oid leftTypeId,
  			   Oid rightTypeId,
  			   Oid procedureId,
! 			   List * commutatorName,
! 			   List * negatorName,
  			   Oid restrictionId,
  			   Oid joinId,
  			   bool canMerge,
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 869,875 **** DATA(insert OID = 2331 (  unnest		   PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0
  DESCR("expand array to set of rows");
  DATA(insert OID = 3167 (  array_remove	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2277 "2277 2283" _null_ _null_ _null_ _null_ array_remove _null_ _null_ _null_ ));
  DESCR("remove any occurrences of an element from an array");
! DATA(insert OID = 3168 (  array_replace	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2277 "2277 2283 2283" _null_ _null_ _null_ _null_ array_replace _null_ _null_ _null_ ));
  DESCR("replace any occurrences of an element in an array");
  DATA(insert OID = 2333 (  array_agg_transfn   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2283" _null_ _null_ _null_ _null_ array_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
--- 869,875 ----
  DESCR("expand array to set of rows");
  DATA(insert OID = 3167 (  array_remove	   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2277 "2277 2283" _null_ _null_ _null_ _null_ array_remove _null_ _null_ _null_ ));
  DESCR("remove any occurrences of an element from an array");
! DATA(insert OID = 3168 (  array_replace    PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2277 "2277 2283 2283" _null_ _null_ _null_ _null_ array_replace _null_ _null_ _null_ ));
  DESCR("replace any occurrences of an element in an array");
  DATA(insert OID = 2333 (  array_agg_transfn   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2283" _null_ _null_ _null_ _null_ array_agg_transfn _null_ _null_ _null_ ));
  DESCR("aggregate transition function");
***************
*** 1052,1058 **** DATA(insert OID = 3171 (  lo_tell64		   PGNSP PGUID 12 1 0 0 0 f f f f t f v 1 0
  DESCR("large object position (64 bit)");
  DATA(insert OID = 1004 (  lo_truncate	   PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 23 "23 23" _null_ _null_ _null_ _null_ lo_truncate _null_ _null_ _null_ ));
  DESCR("truncate large object");
! DATA(insert OID = 3172 (  lo_truncate64	   PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 23 "23 20" _null_ _null_ _null_ _null_ lo_truncate64 _null_ _null_ _null_ ));
  DESCR("truncate large object (64 bit)");
  
  DATA(insert OID = 959 (  on_pl			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "600 628" _null_ _null_ _null_ _null_	on_pl _null_ _null_ _null_ ));
--- 1052,1058 ----
  DESCR("large object position (64 bit)");
  DATA(insert OID = 1004 (  lo_truncate	   PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 23 "23 23" _null_ _null_ _null_ _null_ lo_truncate _null_ _null_ _null_ ));
  DESCR("truncate large object");
! DATA(insert OID = 3172 (  lo_truncate64    PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 23 "23 20" _null_ _null_ _null_ _null_ lo_truncate64 _null_ _null_ _null_ ));
  DESCR("truncate large object (64 bit)");
  
  DATA(insert OID = 959 (  on_pl			   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "600 628" _null_ _null_ _null_ _null_	on_pl _null_ _null_ _null_ ));
***************
*** 3473,3479 **** DATA(insert OID = 2301 (  trigger_out		PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0
  DESCR("I/O");
  DATA(insert OID = 3594 (  event_trigger_in	PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 3838 "2275" _null_ _null_ _null_ _null_ event_trigger_in _null_ _null_ _null_ ));
  DESCR("I/O");
! DATA(insert OID = 3595 (  event_trigger_out	PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "3838" _null_ _null_ _null_ _null_ event_trigger_out _null_ _null_ _null_ ));
  DESCR("I/O");
  DATA(insert OID = 2302 (  language_handler_in	PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 2280 "2275" _null_ _null_ _null_ _null_ language_handler_in _null_ _null_ _null_ ));
  DESCR("I/O");
--- 3473,3479 ----
  DESCR("I/O");
  DATA(insert OID = 3594 (  event_trigger_in	PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 3838 "2275" _null_ _null_ _null_ _null_ event_trigger_in _null_ _null_ _null_ ));
  DESCR("I/O");
! DATA(insert OID = 3595 (  event_trigger_out PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "3838" _null_ _null_ _null_ _null_ event_trigger_out _null_ _null_ _null_ ));
  DESCR("I/O");
  DATA(insert OID = 2302 (  language_handler_in	PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 2280 "2275" _null_ _null_ _null_ _null_ language_handler_in _null_ _null_ _null_ ));
  DESCR("I/O");
***************
*** 4103,4108 **** DESCR("map row to json");
--- 4103,4120 ----
  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");
+ 
+ 
  /* 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");
***************
*** 4666,4674 **** DESCR("SP-GiST support for suffix tree over text");
  DATA(insert OID = 4031 (  spg_text_leaf_consistent	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2281 2281" _null_ _null_ _null_ _null_  spg_text_leaf_consistent _null_ _null_ _null_ ));
  DESCR("SP-GiST support for suffix tree over text");
  
! DATA(insert OID = 3469 (  spg_range_quad_config	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_config _null_ _null_ _null_ ));
  DESCR("SP-GiST support for quad tree over range");
! DATA(insert OID = 3470 (  spg_range_quad_choose	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_choose _null_ _null_ _null_ ));
  DESCR("SP-GiST support for quad tree over range");
  DATA(insert OID = 3471 (  spg_range_quad_picksplit	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_picksplit _null_ _null_ _null_ ));
  DESCR("SP-GiST support for quad tree over range");
--- 4678,4686 ----
  DATA(insert OID = 4031 (  spg_text_leaf_consistent	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2281 2281" _null_ _null_ _null_ _null_  spg_text_leaf_consistent _null_ _null_ _null_ ));
  DESCR("SP-GiST support for suffix tree over text");
  
! DATA(insert OID = 3469 (  spg_range_quad_config PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_config _null_ _null_ _null_ ));
  DESCR("SP-GiST support for quad tree over range");
! DATA(insert OID = 3470 (  spg_range_quad_choose PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_choose _null_ _null_ _null_ ));
  DESCR("SP-GiST support for quad tree over range");
  DATA(insert OID = 3471 (  spg_range_quad_picksplit	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_picksplit _null_ _null_ _null_ ));
  DESCR("SP-GiST support for quad tree over range");
*** 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,38 ----
  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);
+ 
  #endif   /* JSON_H */
*** /dev/null
--- b/src/include/utils/jsonapi.h
***************
*** 0 ****
--- 1,82 ----
+ /*-------------------------------------------------------------------------
+  *
+  * jsonapi.h
+  *	  Declarations for JSON API support.
+  *
+  * Portions Copyright (c) 1996-2012, 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;
+ 
+ typedef struct
+ {
+ 	char	   *input;
+ 	char	   *token_start;
+ 	char	   *token_terminator;
+ 	char	   *prev_token_terminator;
+ 	JsonTokenType token_type;
+ 	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);
+ 
+ 
+ /*
+  * any of these actions can be NULL, in whi9ch case nothig is done.
+  */
+ typedef struct
+ {
+ 	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);
+ 
+ #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,541 ----
   {"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","three","four","five"]'),
+ ('object','{"field1":"val1","field2":"val2"}');
+ 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
+ (2 rows)
+ 
*** a/src/test/regress/sql/json.sql
--- b/src/test/regress/sql/json.sql
***************
*** 113,115 **** FROM (SELECT '-Infinity'::float8 AS "float8field") q;
--- 113,189 ----
  -- 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","three","four","five"]'),
+ ('object','{"field1":"val1","field2":"val2"}');
+ 
+ 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';
+ 
#2Robert Haas
robertmhaas@gmail.com
In reply to: Andrew Dunstan (#1)
Re: json api WIP patch

On Wed, Dec 26, 2012 at 3:33 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

Here is a patch for the first part of the JSON API that was recently
discussed. It includes the json parser hook infrastructure and functions
for json_get and friends, plus json_keys.

As is, this exposes the json lexer fully for use by the hook functions. But
I could easily be persuaded that this should be an opaque structure with
some constructor and getter functions - I don't think the hook functions
need or should be able to set anything in the lexer.

Work is proceeding on some of the more advanced functionality discussed.

This seems to contain a large number of spurious whitespace changes.

And maybe some other spurious changes. For example, I'm not sure why
this comment is truncated:

}
}

! 	/*
! 	 * 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);
--- 730,739 ----
  		}
  	}

! /* Check for trailing garbage. */
for (p = s; JSON_ALPHANUMERIC_CHAR(*p); p++)
error = true;
+ lex->prev_token_terminator = lex->token_terminator;
lex->token_terminator = p;
if (error)
report_invalid_token(lex);

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

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

#3Andrew Dunstan
andrew@dunslane.net
In reply to: Robert Haas (#2)
Re: json api WIP patch

On 01/02/2013 04:45 PM, Robert Haas wrote:

On Wed, Dec 26, 2012 at 3:33 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

Here is a patch for the first part of the JSON API that was recently
discussed. It includes the json parser hook infrastructure and functions
for json_get and friends, plus json_keys.

As is, this exposes the json lexer fully for use by the hook functions. But
I could easily be persuaded that this should be an opaque structure with
some constructor and getter functions - I don't think the hook functions
need or should be able to set anything in the lexer.

Work is proceeding on some of the more advanced functionality discussed.

This seems to contain a large number of spurious whitespace changes.

I'm glad you're looking at it :-)

I did do a run of pgindent on the changed files before I cut the patch,
which might have made some of those changes.

I notice a couple of other infelicities too, which are undoubtedly my fault.

The original prototype of this was cut against some older code, and I
then tried to merge it with the current code base, and make sure that
all the regression tests passed. That might well have resulted in a
number of things that need review.

And maybe some other spurious changes. For example, I'm not sure why
this comment is truncated:

Another good question.

I'll make another pass over this and try to remove some of what's
annoying you.

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 api WIP patch

On 01/02/2013 05:51 PM, Andrew Dunstan wrote:

On 01/02/2013 04:45 PM, Robert Haas wrote:

On Wed, Dec 26, 2012 at 3:33 PM, Andrew Dunstan <andrew@dunslane.net>
wrote:

Here is a patch for the first part of the JSON API that was recently
discussed. It includes the json parser hook infrastructure and
functions
for json_get and friends, plus json_keys.

Udated patch that contains most of the functionality I'm after. One
piece left is populate_recordset (populate a set of records from a
single json datum which is an array of objects, in one pass). That
requires a bit of thought.

I hope most of the whitespace issues are fixed.

cheers

andrew

Attachments:

jsonapi2.patchtext/x-patch; name=jsonapi2.patchDownload
*** 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,81 ****
  #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);
--- 24,60 ----
  #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 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 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);
***************
*** 88,93 **** static void array_dim_to_json(StringInfo result, int dim, int ndims, int *dims,
--- 67,122 ----
  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 */
+ 
+ static inline JsonTokenType
+ lex_peek(JsonLexContext *lex)
+ {
+     return lex->token_type;
+ }
+ 
+ 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;
+ }
+ 
+ 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'
  /* letters appearing in numeric output that aren't valid in a JSON number */
***************
*** 100,106 **** static void array_to_json_internal(Datum array, StringInfo result,
  	 (c) == '_' || \
  	 IS_HIGHBIT_SET(c))
  
- 
  /*
   * Input.
   */
--- 129,134 ----
***************
*** 172,325 **** json_recv(PG_FUNCTION_ARGS)
  }
  
  /*
!  * 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;
! 		}
  	}
  }
  
  /*
--- 200,379 ----
  }
  
  /*
!  * parse routines
   */
+ void
+ pg_parse_json(JsonLexContext *lex, JsonSemAction sem)
+ {
+ 
+     /* get the initial token */
+     json_lex(lex);
+ 
+ 
+     /* parse by recursive descent */
+     if (lex_peek(lex) == JSON_TOKEN_OBJECT_START)
+         parse_object(lex, sem);
+     else if (lex_peek(lex) == JSON_TOKEN_ARRAY_START)
+         parse_array(lex, sem);
+     else
+         parse_scalar(lex, sem);/* json can be a bare scalar */
+ 
+     lex_expect(JSON_PARSE_END, lex, JSON_TOKEN_END);
+ 
+ }
+ 
  static void
! parse_scalar(JsonLexContext *lex, JsonSemAction sem)
  {
!     char *val = NULL;
!     json_scalar_action sfunc = sem->scalar;
!     JsonTokenType tok = lex_peek(lex);
! 
!     if (lex_accept(lex, JSON_TOKEN_TRUE, &val) ||
!         lex_accept(lex, JSON_TOKEN_FALSE, &val) ||
!         lex_accept(lex, JSON_TOKEN_NULL, &val) ||
!         lex_accept(lex, JSON_TOKEN_NUMBER, &val) ||
!         lex_accept(lex, JSON_TOKEN_STRING, &val))
!     {
!         if (sfunc != NULL)
!             (*sfunc) (sem->semstate, val, tok);
!     }
!     else
!     {
!         report_parse_error(JSON_PARSE_VALUE, lex);
!     }
! }
  
! static void
! parse_object_field(JsonLexContext *lex, JsonSemAction sem)
! {
  
!     char *fname = NULL; /* keep compiler quiet */
!     json_ofield_action ostart = sem->object_field_start;
!     json_ofield_action oend = sem->object_field_end;
!     bool isnull;
  
!     if (! lex_accept(lex, JSON_TOKEN_STRING, &fname))
!         report_parse_error(JSON_PARSE_STRING, lex); 
  
!     lex_expect(JSON_PARSE_OBJECT_LABEL, lex, JSON_TOKEN_COLON);
  
!     isnull = lex_peek(lex) == JSON_TOKEN_NULL;
  
!     if (ostart != NULL)
!         (*ostart) (sem->semstate, fname, isnull);
  
!     if (lex_peek(lex) == JSON_TOKEN_OBJECT_START)
!         parse_object(lex, sem);
!     else if (lex_peek(lex) == JSON_TOKEN_ARRAY_START)
!         parse_array(lex,sem);
!     else
!         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)
! {
!     
!     json_struct_action ostart = sem->object_start;
!     json_struct_action oend = sem->object_end;
! 
!     if (ostart != NULL)
!         (*ostart) (sem->semstate);
! 
! 	/* we know this will succeeed, just clearing the token */
!     lex_expect(JSON_PARSE_OBJECT_START, lex, JSON_TOKEN_OBJECT_START);
!     if (lex_peek(lex) == JSON_TOKEN_STRING)
!     {
!         parse_object_field(lex, sem);
! 
!         while (lex_accept(lex,JSON_TOKEN_COMMA,NULL))
!                 parse_object_field(lex, sem);
!         
!     }
! 	else if (lex_peek(lex) != JSON_TOKEN_OBJECT_END)
! 	{
! 		/* 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);
+ 
+     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;
+     bool isnull;
+ 
+     isnull = lex_peek(lex) == JSON_TOKEN_NULL;
+ 
+     if (astart != NULL)
+         (*astart) (sem->semstate, isnull);
+ 
+     if (lex_peek(lex) == JSON_TOKEN_OBJECT_START)
+         parse_object(lex, sem);
+     else if (lex_peek(lex) == JSON_TOKEN_ARRAY_START)
+         parse_array(lex, sem);
+     else
+         parse_scalar(lex, sem);
+ 
+     if (aend != NULL)
+         (*aend) (sem->semstate, isnull);
+ }
+ 
+ static void 
+ parse_array(JsonLexContext *lex, JsonSemAction sem)
+ {
+     json_struct_action astart = sem->array_start;
+     json_struct_action aend = sem->array_end;
+ 
+     if (astart != NULL)
+         (*astart) (sem->semstate);
+ 
+     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);
+ 
+     if (aend != NULL)
+         (*aend) (sem->semstate);
+ }
+ 
+ /*
+  * Check whether supplied input is valid JSON.
+  */
+ static void
+ json_validate_cstring(char *input)
+ {
+ 
+ 	JsonLexContext lex;
+ 
+ 	/* Set up lexing context. */
+ 	lex.input = input;
+ 	lex.token_terminator = lex.input;
+ 	lex.line_number = 1;
+ 	lex.line_start = input;
+ 	lex.strval = NULL; /* don't care about de-escaped lexemes */
+ 	
+ 	pg_parse_json(&lex, NullSemAction);
  }
  
  /*
***************
*** 333,399 **** json_lex(JsonLexContext *lex)
  	/* 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);
  		}
--- 387,476 ----
  	/* Skip leading whitespace. */
  	s = lex->token_terminator;
  	while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r')
! 	{
! 		if (*s == '\n')
! 			++lex->line_number;
! 		++s;
! 	}
  	lex->token_start = s;
  
  	/* Determine token type. */
! 	if (*s == '\0')
  	{
! 		lex->token_start = NULL;
! 		lex->prev_token_terminator = lex->token_terminator;
! 		lex->token_terminator = s;
! 		lex->token_type =  JSON_TOKEN_END;
! 	}
! 	else if (strchr("{}[],:", s[0]))
! 	{
! 		/* Single-character token, some kind of punctuation mark. */
! 		lex->prev_token_terminator = lex->token_terminator;
! 		lex->token_terminator = s + 1;
! 		switch (s[0])
  		{
! 			case '{': 
! 				lex->token_type = JSON_TOKEN_OBJECT_START; 
! 				break;
! 			case '}':
! 				lex->token_type = JSON_TOKEN_OBJECT_END; 
! 				break;
! 			case '[': 
! 				lex->token_type = JSON_TOKEN_ARRAY_START; 
! 				break;
! 			case ']':
! 				lex->token_type = JSON_TOKEN_ARRAY_END; 
! 				break;
! 			case ',':
! 				lex->token_type = JSON_TOKEN_COMMA; 
! 				break;
! 			case ':':
! 				lex->token_type = JSON_TOKEN_COLON; 
! 				break;
! 			default:
! 				break;
  		}
  	}
  	else if (*s == '"')
  	{
  		/* String. */
  		json_lex_string(lex);
! 		lex->token_type = JSON_TOKEN_STRING;
  	}
  	else if (*s == '-')
  	{
  		/* Negative number. */
  		json_lex_number(lex, s + 1);
! 		lex->token_type = JSON_TOKEN_NUMBER;
  	}
  	else if (*s >= '0' && *s <= '9')
  	{
  		/* Positive number. */
  		json_lex_number(lex, s);
! 		lex->token_type = JSON_TOKEN_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 */ ;
  
+ 		/*
+ 		 * 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);
  		}
***************
*** 402,419 **** json_lex(JsonLexContext *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);
  	}
--- 479,497 ----
  		 * 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);
  	}
***************
*** 427,432 **** json_lex_string(JsonLexContext *lex)
--- 505,513 ----
  {
  	char	   *s;
  
+ 	if (lex-> strval != NULL)
+ 		resetStringInfo(lex->strval);
+ 
  	for (s = lex->token_start + 1; *s != '"'; s++)
  	{
  		/* Per RFC4627, these characters MUST be escaped. */
***************
*** 485,506 **** 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),
! 						 errmsg("invalid input syntax for type json"),
! 						 errdetail("Escape sequence \"\\%s\" is invalid.",
! 								   extract_mb_char(s)),
! 						 report_json_context(lex)));
  			}
  		}
  	}
  
  	/* Hooray, we found the end of the string! */
  	lex->token_terminator = s + 1;
  }
  
--- 566,646 ----
  								 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
+ 				 */
+                 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 (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;
  }
  
***************
*** 512,528 **** json_lex_string(JsonLexContext *lex)
   * (1) An optional minus sign ('-').
   *
   * (2) Either a single '0', or a string of one or more digits that does not
!  *	   begin with a '0'.
   *
   * (3) An optional decimal part, consisting of a period ('.') followed by
!  *	   one or more digits.	(Note: While this part can be omitted
!  *	   completely, it's not OK to have only the decimal point without
!  *	   any digits afterwards.)
   *
   * (4) An optional exponent part, consisting of 'e' or 'E', optionally
!  *	   followed by '+' or '-', followed by one or more digits.	(Note:
!  *	   As with the decimal part, if 'e' or 'E' is present, it must be
!  *	   followed by at least one digit.)
   *
   * The 's' argument to this function points to the ostensible beginning
   * of part 2 - i.e. the character after any optional minus sign, and the
--- 652,668 ----
   * (1) An optional minus sign ('-').
   *
   * (2) Either a single '0', or a string of one or more digits that does not
!  *     begin with a '0'.
   *
   * (3) An optional decimal part, consisting of a period ('.') followed by
!  *     one or more digits.  (Note: While this part can be omitted
!  *     completely, it's not OK to have only the decimal point without
!  *     any digits afterwards.)
   *
   * (4) An optional exponent part, consisting of 'e' or 'E', optionally
!  *     followed by '+' or '-', followed by one or more digits.  (Note:
!  *     As with the decimal part, if 'e' or 'E' is present, it must be
!  *     followed by at least one digit.)
   *
   * The 's' argument to this function points to the ostensible beginning
   * of part 2 - i.e. the character after any optional minus sign, and the
***************
*** 533,540 **** 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. */
--- 673,680 ----
  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. */
***************
*** 591,596 **** json_lex_number(JsonLexContext *lex, char *s)
--- 731,737 ----
  	 */
  	for (p = s; JSON_ALPHANUMERIC_CHAR(*p); p++)
  		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"),
--- 743,755 ----
   * 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"),
--- 763,769 ----
  	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,
--- 772,778 ----
  				 report_json_context(lex)));
  	else
  	{
! 		switch (ctx)
  		{
  			case JSON_PARSE_VALUE:
  				ereport(ERROR,
***************
*** 641,646 **** report_parse_error(JsonParseStack *stack, JsonLexContext *lex)
--- 782,795 ----
  								   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),
***************
*** 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);
  		}
  	}
  }
--- 839,845 ----
  						 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);
--- 934,940 ----
  	 * suffixing "..." if not ending at end of line.
  	 */
  	prefix = (context_start > line_start) ? "..." : "";
! 	suffix = (lex->token_type != JSON_TOKEN_END && *context_end != '\0' && *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,1668 ----
+ /*-------------------------------------------------------------------------
+  *
+  * jsonfuncs.c
+  *		Functions to process JSON data type.
+  *
+  * Portions Copyright (c) 1996-2012, 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_start(void *state);
+ static void okeys_object_end(void *state);
+ static void okeys_object_field_start(void *state, char *fname, bool isnull);
+ static void okeys_array_start(void *state);
+ static void okeys_array_end(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_end(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_end(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(char *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_array_start(void *state);
+ static void alen_array_end(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_start(void *state);
+ static void each_object_end(void *state);
+ 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_start(void *state);
+ static void unnest_array_end(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(char *jsonstr, char *funcname);
+ 
+ /* semantic action functions for get_json_object_as_hash */
+ static void hash_object_start(void *state);
+ 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);
+ 
+ 
+ 
+ /* 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
+ {
+ 	int			lex_level;
+ 	char	  **result;
+ 	int			result_size;
+ 	int			result_count;
+ 	int			sent_count;
+ }	okeysState, *OkeysState;
+ 
+ /* state for json_get* functions */
+ typedef struct
+ {
+ 	JsonLexContext *lex;
+ 	int			lex_level;
+ 	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
+ {
+ 	int			lex_level;
+ 	int			count;
+ }	alenState, *AlenState;
+ 
+ /* state for json_each */
+ typedef struct
+ {
+ 	JsonLexContext *lex;
+ 	int			lex_level;
+ 	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
+ {
+ 	JsonLexContext *lex;
+ 	int			lex_level;
+ 	Tuplestorestate *tuple_store;
+ 	TupleDesc	ret_tdesc;
+ 	MemoryContext tmp_cxt;
+ 	char	   *result_start;
+ }	unnestState, *UnnestState;
+ 
+ /* state for get_json_object_as_hash */
+ typedef struct
+ {
+ 	JsonLexContext *lex;
+ 	int			lex_level;
+ 	HTAB	   *hash;
+ 	char	   *saved_scalar;
+ 	char	   *function_name;
+ }	jhashState, *JHashState;
+ 
+ /* used to build the hashtable */
+ typedef struct
+ {
+ 	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;
+ 
+ /*
+  * SQL function json_object-keys
+  *
+  * Returns the set of keys for the object argument.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_object_keys);
+ 
+ Datum
+ json_object_keys(PG_FUNCTION_ARGS)
+ {
+ 	FuncCallContext *funcctx;
+ 	OkeysState	state;
+ 	int			i;
+ 
+ 	if (SRF_IS_FIRSTCALL())
+ 	{
+ 		text	   *json = PG_GETARG_TEXT_P(0);
+ 		char	   *jsonstr = text_to_cstring(json);
+ 		JsonLexContext lex;
+ 		JsonSemAction sem;
+ 
+ 		MemoryContext oldcontext;
+ 
+ 		funcctx = SRF_FIRSTCALL_INIT();
+ 		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+ 
+ 		state = palloc(sizeof(okeysState));
+ 		sem = palloc0(sizeof(jsonSemAction));
+ 
+ 		state->result_size = 256;
+ 		state->result_count = 0;
+ 		state->sent_count = 0;
+ 		state->lex_level = 0;
+ 		state->result = palloc(256 * sizeof(char *));
+ 
+ 		sem->semstate = (void *) state;
+ 		sem->object_start = okeys_object_start;
+ 		sem->object_end = okeys_object_end;
+ 		sem->array_start = okeys_array_start;
+ 		sem->array_end = okeys_array_end;
+ 		sem->scalar = okeys_scalar;
+ 		sem->object_field_start = okeys_object_field_start;
+ 		/* remainder are all NULL, courtesy of palloc0 above */
+ 
+ 		/* Set up lexing context. */
+ 		lex.input = jsonstr;
+ 		lex.token_terminator = lex.input;
+ 		lex.line_number = 1;
+ 		lex.line_start = jsonstr;
+ 		lex.strval = makeStringInfo();
+ 
+ 		pg_parse_json(&lex, sem);
+ 		/* keys are now in state->result */
+ 
+ 		pfree(lex.strval->data);
+ 		pfree(lex.strval);
+ 		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_start(void *state)
+ {
+ 	OkeysState	_state = (OkeysState) state;
+ 
+ 	_state->lex_level++;
+ }
+ 
+ static void
+ okeys_object_end(void *state)
+ {
+ 	OkeysState	_state = (OkeysState) state;
+ 
+ 	_state->lex_level--;
+ }
+ 
+ static void
+ okeys_object_field_start(void *state, char *fname, bool isnull)
+ {
+ 	OkeysState	_state = (OkeysState) state;
+ 
+ 	if (_state->lex_level != 1)
+ 		return;
+ 	if (_state->result_count >= _state->result_size)
+ 	{
+ 		_state->result_size *= 2;
+ 		_state->result =
+ 			repalloc(_state->result, sizeof(char *) * _state->result_size);
+ 	}
+ 	_state->result[_state->result_count++] = pstrdup(fname);
+ }
+ 
+ static void
+ okeys_array_start(void *state)
+ {
+ 	OkeysState	_state = (OkeysState) state;
+ 
+ 	_state->lex_level++;
+ 	if (_state->lex_level == 1)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ 				 errmsg("cannot call json_object_keys on an array")));
+ }
+ 
+ static void
+ okeys_array_end(void *state)
+ {
+ 	OkeysState	_state = (OkeysState) state;
+ 
+ 	_state->lex_level--;
+ }
+ 
+ static void
+ okeys_scalar(void *state, char *token, JsonTokenType tokentype)
+ {
+ 	OkeysState	_state = (OkeysState) state;
+ 
+ 	if (_state->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ 				 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.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_get_ofield);
+ 
+ Datum
+ json_get_ofield(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	text	   *fname = PG_GETARG_TEXT_P(1);
+ 	char	   *jsonstr = text_to_cstring(json);
+ 	char	   *fnamestr = text_to_cstring(fname);
+ 	text	   *result;
+ 
+ 	result = get_worker(jsonstr, fnamestr, -1, NULL, -1, false);
+ 
+ 	if (result != NULL)
+ 		PG_RETURN_TEXT_P(result);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ PG_FUNCTION_INFO_V1(json_get_ofield_as_text);
+ 
+ Datum
+ json_get_ofield_as_text(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	text	   *fname = PG_GETARG_TEXT_P(1);
+ 	char	   *jsonstr = text_to_cstring(json);
+ 	char	   *fnamestr = text_to_cstring(fname);
+ 	text	   *result;
+ 
+ 	result = get_worker(jsonstr, fnamestr, -1, NULL, -1, true);
+ 	if (result != NULL)
+ 		PG_RETURN_TEXT_P(result);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ PG_FUNCTION_INFO_V1(json_get_aelem);
+ 
+ Datum
+ json_get_aelem(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	int			element = PG_GETARG_INT32(1);
+ 	char	   *jsonstr = text_to_cstring(json);
+ 	text	   *result;
+ 
+ 	result = get_worker(jsonstr, NULL, element, NULL, -1, false);
+ 
+ 	if (result != NULL)
+ 		PG_RETURN_TEXT_P(result);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ 
+ PG_FUNCTION_INFO_V1(json_get_aelem_as_text);
+ 
+ Datum
+ json_get_aelem_as_text(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	int			element = PG_GETARG_INT32(1);
+ 	char	   *jsonstr = text_to_cstring(json);
+ 	text	   *result;
+ 
+ 	result = get_worker(jsonstr, NULL, element, NULL, -1, true);
+ 
+ 	if (result != NULL)
+ 		PG_RETURN_TEXT_P(result);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ 
+ PG_FUNCTION_INFO_V1(json_get_path);
+ 
+ Datum
+ json_get_path(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	ArrayType  *path = PG_GETARG_ARRAYTYPE_P(1);
+ 	char	   *jsonstr = text_to_cstring(json);
+ 	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(jsonstr, NULL, -1, pathstr, npath, false);
+ 
+ 	if (result != NULL)
+ 		PG_RETURN_TEXT_P(result);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ PG_FUNCTION_INFO_V1(json_get_path_as_text);
+ 
+ Datum
+ json_get_path_as_text(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	ArrayType  *path = PG_GETARG_ARRAYTYPE_P(1);
+ 	char	   *jsonstr = text_to_cstring(json);
+ 	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(jsonstr, NULL, -1, pathstr, npath, true);
+ 
+ 	if (result != NULL)
+ 		PG_RETURN_TEXT_P(result);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ static text *
+ get_worker(char *json,
+ 		   char *field,
+ 		   int elem_index,
+ 		   char **path,
+ 		   int npath,
+ 		   bool normalize_results)
+ {
+ 	GetState	state;
+ 	JsonLexContext lex;
+ 	JsonSemAction sem;
+ 
+ 	state = palloc0(sizeof(getState));
+ 	sem = palloc0(sizeof(jsonSemAction));
+ 
+ 	state->lex_level = 0;
+ 	state->lex = &lex;
+ 	state->normalize_results = normalize_results;
+ 	if (field != NULL)
+ 	{
+ 		state->search_type = JSON_SEARCH_OBJECT;
+ 		state->search_term = field;
+ 	}
+ 	else if (path != NULL)
+ 	{
+ 		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);
+ 		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
+ 	{
+ 		state->search_type = JSON_SEARCH_ARRAY;
+ 		state->search_index = elem_index;
+ 		state->array_index = -1;
+ 	}
+ 
+ 	sem->semstate = (void *) state;
+ 	sem->object_start = get_object_start;
+ 	sem->object_end = get_object_end;
+ 	sem->array_start = get_array_start;
+ 	sem->array_end = get_array_end;
+ 	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;
+ 	}
+ 
+ 	/* Set up lexing context. */
+ 	lex.input = json;
+ 	lex.token_terminator = lex.input;
+ 	lex.line_number = 1;
+ 	lex.line_start = json;
+ 	lex.strval = makeStringInfo();
+ 
+ 	pg_parse_json(&lex, sem);
+ 
+ 	return state->tresult;
+ }
+ 
+ static void
+ get_object_start(void *state)
+ {
+ 	GetState	_state = (GetState) state;
+ 
+ 	_state->lex_level++;
+ 	if (_state->lex_level == 1 && _state->search_type == JSON_SEARCH_ARRAY)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ 				 errmsg("cannot call json_get(int) on a non-array")));
+ }
+ 
+ static void
+ get_object_end(void *state)
+ {
+ 	GetState	_state = (GetState) state;
+ 
+ 	_state->lex_level--;
+ }
+ 
+ static void
+ get_object_field_start(void *state, char *fname, bool isnull)
+ {
+ 	GetState	_state = (GetState) state;
+ 	bool		get_next = false;
+ 
+ 	if (_state->lex_level == 1 && _state->search_type == JSON_SEARCH_OBJECT &&
+ 		strcmp(fname, _state->search_term) == 0)
+ 	{
+ 		get_next = true;
+ 	}
+ 	else if (_state->search_type == JSON_SEARCH_PATH &&
+ 			 _state->lex_level <= _state->npath &&
+ 			 _state->pathok[_state->lex_level - 1] &&
+ 			 strcmp(fname, _state->path[_state->lex_level - 1]) == 0)
+ 	{
+ 		if (_state->lex_level < _state->npath)
+ 			_state->pathok[_state->lex_level] = true;
+ 
+ 		if (_state->lex_level == _state->npath)
+ 			get_next = true;
+ 	}
+ 
+ 	if (get_next)
+ 	{
+ 		if (_state->tresult != NULL || _state->result_start != NULL)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ 					 errmsg("field name is not unique in json object")));
+ 
+ 		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_object_field_end(void *state, char *fname, bool isnull)
+ {
+ 	GetState	_state = (GetState) state;
+ 	bool		get_last = false;
+ 
+ 	if (_state->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 &&
+ 			 _state->lex_level <= _state->npath &&
+ 			 _state->pathok[_state->lex_level - 1] &&
+ 			 strcmp(fname, _state->path[_state->lex_level - 1]) == 0)
+ 	{
+ 		/* done with this field so reset pathok */
+ 		if (_state->lex_level < _state->npath)
+ 			_state->pathok[_state->lex_level] = false;
+ 
+ 		if (_state->lex_level == _state->npath)
+ 			get_last = true;
+ 	}
+ 
+ 	if (get_last && _state->result_start != NULL)
+ 	{
+ 		int			len = _state->lex->prev_token_terminator - _state->result_start;
+ 
+ 		_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;
+ 
+ 	_state->lex_level++;
+ 	if (_state->lex_level == 1 && _state->search_type == JSON_SEARCH_OBJECT)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ 				 errmsg("cannot call json_get(fieldname) on a non-object")));
+ 	else if (_state->search_type == JSON_SEARCH_PATH &&
+ 			 _state->lex_level <= _state->npath)
+ 		_state->array_level_index[_state->lex_level - 1] = -1;
+ }
+ 
+ static void
+ get_array_end(void *state)
+ {
+ 	GetState	_state = (GetState) state;
+ 
+ 	_state->lex_level--;
+ }
+ 
+ static void
+ get_array_element_start(void *state, bool isnull)
+ {
+ 	GetState	_state = (GetState) state;
+ 	bool		get_next = false;
+ 
+ 	if (_state->lex_level == 1 && _state->search_type == JSON_SEARCH_ARRAY)
+ 	{
+ 		_state->array_index++;
+ 		if (_state->array_index == _state->search_index)
+ 			get_next = true;
+ 	}
+ 	else if (_state->search_type == JSON_SEARCH_PATH &&
+ 			 _state->lex_level <= _state->npath &&
+ 			 _state->pathok[_state->lex_level - 1])
+ 	{
+ 		if (++_state->array_level_index[_state->lex_level - 1] ==
+ 			_state->path_level_index[_state->lex_level - 1])
+ 		{
+ 			if (_state->lex_level == _state->npath)
+ 				get_next = true;
+ 			else
+ 				_state->pathok[_state->lex_level] = true;
+ 		}
+ 
+ 	}
+ 
+ 	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;
+ 
+ 	if (_state->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 &&
+ 			 _state->lex_level <= _state->npath &&
+ 			 _state->pathok[_state->lex_level - 1] &&
+ 			 _state->array_level_index[_state->lex_level - 1] ==
+ 			 _state->path_level_index[_state->lex_level - 1])
+ 	{
+ 		/* done with this element so reset pathok */
+ 		if (_state->lex_level < _state->npath)
+ 			_state->pathok[_state->lex_level] = false;
+ 
+ 		if (_state->lex_level == _state->npath)
+ 			get_last = true;
+ 	}
+ 	if (get_last && _state->result_start != NULL)
+ 	{
+ 		int			len = _state->lex->prev_token_terminator - _state->result_start;
+ 
+ 		_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_level == 0 && _state->search_type != JSON_SEARCH_PATH)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ 				 errmsg("cannot call json_get on a scalar")));
+ 	if (_state->next_scalar)
+ 	{
+ 		_state->tresult = cstring_to_text(token);
+ 		_state->next_scalar = false;
+ 	}
+ 
+ }
+ 
+ 
+ /*
+  * SQL function json_array_length
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_array_length);
+ 
+ Datum
+ json_array_length(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	char	   *jsonstr = text_to_cstring(json);
+ 
+ 	AlenState	state;
+ 	JsonLexContext lex;
+ 	JsonSemAction sem;
+ 
+ 	state = palloc0(sizeof(alenState));
+ 	sem = palloc0(sizeof(jsonSemAction));
+ 
+ 	/* palloc0 does this for us */
+ #if 0
+ 	state->lex_level = 0;
+ 	state->count = 0;
+ #endif
+ 
+ 	sem->semstate = (void *) state;
+ 	sem->object_start = alen_object_start;
+ 	sem->array_start = alen_array_start;
+ 	sem->array_end = alen_array_end;
+ 	sem->scalar = alen_scalar;
+ 	sem->array_element_start = alen_array_element_start;
+ 
+ 	/* Set up lexing context. */
+ 	lex.input = jsonstr;
+ 	lex.token_terminator = lex.input;
+ 	lex.line_number = 1;
+ 	lex.line_start = jsonstr;
+ 	lex.strval = NULL;			/* just counting, so this is faster */
+ 
+ 	pg_parse_json(&lex, sem);
+ 
+ 	PG_RETURN_INT32(state->count);
+ }
+ 
+ 
+ static void
+ alen_array_start(void *state)
+ {
+ 	AlenState	_state = (AlenState) state;
+ 
+ 	_state->lex_level++;
+ }
+ 
+ static void
+ alen_array_end(void *state)
+ {
+ 	AlenState	_state = (AlenState) state;
+ 
+ 	_state->lex_level--;
+ }
+ 
+ static void
+ alen_object_start(void *state)
+ {
+ 	AlenState	_state = (AlenState) state;
+ 
+ 	if (_state->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ 				 errmsg("cannot call json_array_length on an object")));
+ }
+ 
+ static void
+ alen_scalar(void *state, char *token, JsonTokenType tokentype)
+ {
+ 	AlenState	_state = (AlenState) state;
+ 
+ 	if (_state->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ 				 errmsg("cannot call json_array_length on a scalar")));
+ }
+ 
+ static void
+ alen_array_element_start(void *state, bool isnull)
+ {
+ 	AlenState	_state = (AlenState) state;
+ 
+ 	if (_state->lex_level == 1)
+ 		_state->count++;
+ }
+ 
+ /*
+  * SQL function json_each
+  *
+  * decompose a json object into key value pairs.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_each);
+ 
+ Datum
+ json_each(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	char	   *jsonstr = text_to_cstring(json);
+ 	JsonLexContext lex;
+ 	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->object_start = each_object_start;
+ 	sem->object_end = each_object_end;
+ 	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;
+ 
+ 	/* Set up lexing context. */
+ 	lex.input = jsonstr;
+ 	lex.token_terminator = lex.input;
+ 	lex.line_number = 1;
+ 	lex.line_start = jsonstr;
+ 	lex.strval = makeStringInfo();
+ 
+ 	state->normalize_results = false;
+ 	state->next_scalar = false;
+ 
+ 	state->lex = &lex;
+ 	state->lex_level = 0;
+ 	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.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_each_as_text);
+ 
+ Datum
+ json_each_as_text(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	char	   *jsonstr = text_to_cstring(json);
+ 	JsonLexContext lex;
+ 	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->object_start = each_object_start;
+ 	sem->object_end = each_object_end;
+ 	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;
+ 
+ 	/* Set up lexing context. */
+ 	lex.input = jsonstr;
+ 	lex.token_terminator = lex.input;
+ 	lex.line_number = 1;
+ 	lex.line_start = jsonstr;
+ 	lex.strval = makeStringInfo();
+ 
+ 	/* next line is what's different from json_each */
+ 	state->normalize_results = true;
+ 	state->next_scalar = false;
+ 
+ 	state->lex = &lex;
+ 	state->lex_level = 0;
+ 	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_start(void *state)
+ {
+ 	EachState	_state = (EachState) state;
+ 
+ 	_state->lex_level++;
+ }
+ static void
+ each_object_end(void *state)
+ {
+ 	EachState	_state = (EachState) state;
+ 
+ 	_state->lex_level--;
+ }
+ 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_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];
+ 	static bool nulls[2] = {false, false};
+ 
+ 	/* skip over nested objects */
+ 	if (_state->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 (_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;
+ 
+ 	if (_state->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ 				 errmsg("cannot call json_each on an array")));
+ }
+ 
+ static void
+ each_scalar(void *state, char *token, JsonTokenType tokentype)
+ {
+ 	EachState	_state = (EachState) state;
+ 
+ 	if (_state->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ 				 errmsg("cannot call json_each on a scalar")));
+ 
+ 	if (_state->next_scalar)
+ 		_state->normalized_scalar = token;
+ }
+ 
+ /*
+  * SQL function json_unnest
+  *
+  * get the elements from a json array
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_unnest);
+ 
+ Datum
+ json_unnest(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	char	   *jsonstr = text_to_cstring(json);
+ 	JsonLexContext lex;
+ 	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->array_start = unnest_array_start;
+ 	sem->array_end = unnest_array_end;
+ 	sem->scalar = unnest_scalar;
+ 	sem->array_element_start = unnest_array_element_start;
+ 	sem->array_element_end = unnest_array_element_end;
+ 
+ 	/* Set up lexing context. */
+ 	lex.input = jsonstr;
+ 	lex.token_terminator = lex.input;
+ 	lex.line_number = 1;
+ 	lex.line_start = jsonstr;
+ 	lex.strval = makeStringInfo();
+ 
+ 	state->lex = &lex;
+ 	state->lex_level = 0;
+ 	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_start(void *state)
+ {
+ 	UnnestState _state = (UnnestState) state;
+ 
+ 	_state->lex_level++;
+ }
+ 
+ static void
+ unnest_array_end(void *state)
+ {
+ 	UnnestState _state = (UnnestState) state;
+ 
+ 	_state->lex_level--;
+ }
+ 
+ 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_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_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;
+ 
+ 	if (_state->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ 				 errmsg("cannot call json_unnest on an object")));
+ }
+ 
+ static void
+ unnest_scalar(void *state, char *token, JsonTokenType tokentype)
+ {
+ 	UnnestState _state = (UnnestState) state;
+ 
+ 	if (_state->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ 				 errmsg("cannot call json_unnest on a scalar")));
+ }
+ 
+ /*
+  * 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.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_populate_record);
+ 
+ Datum
+ json_populate_record(PG_FUNCTION_ARGS)
+ {
+ 	Oid			argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ 	text	   *json = PG_GETARG_TEXT_P(1);
+ 	char	   *jsonstr = text_to_cstring(json);
+ 	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(jsonstr, "json_populate_record");
+ 
+ 	/*
+ 	 * if the input hstore 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(char *jsonstr, char *funcname)
+ {
+ 	HASHCTL		ctl;
+ 	HTAB	   *tab;
+ 	JHashState	state;
+ 	JsonLexContext lex;
+ 	JsonSemAction sem;
+ 
+ 
+ 	memset(&ctl, 0, sizeof(ctl));
+ 	ctl.keysize = NAMEDATALEN;
+ 	ctl.entrysize = sizeof(jsonHashEntry);
+ 	tab = hash_create("json object hashtable",
+ 					  100,
+ 					  &ctl,
+ 					  HASH_ELEM);
+ 
+ 	state = palloc0(sizeof(jhashState));
+ 	sem = palloc0(sizeof(jsonSemAction));
+ 
+ 	/* palloc0 does this for us */
+ #if 0
+ 	state->lex_level = 0;
+ #endif
+ 
+ 	state->function_name = funcname;
+ 	state->hash = tab;
+ 	state->lex = &lex;
+ 
+ 	sem->semstate = (void *) state;
+ 	sem->object_start = hash_object_start;
+ 	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;
+ 
+ 	/* Set up lexing context. */
+ 	lex.input = jsonstr;
+ 	lex.token_terminator = lex.input;
+ 	lex.line_number = 1;
+ 	lex.line_start = jsonstr;
+ 	lex.strval = makeStringInfo();
+ 
+ 	pg_parse_json(&lex, sem);
+ 
+ 	return tab;
+ }
+ 
+ static void
+ hash_object_start(void *state)
+ {
+ 	JHashState	_state = (JHashState) state;
+ 
+ 	_state->lex_level++;
+ }
+ 
+ static void
+ hash_object_field_start(void *state, char *fname, bool isnull)
+ {
+ 	JHashState	_state = (JHashState) state;
+ 
+ 	if (_state->lex->token_type == JSON_TOKEN_ARRAY_START ||
+ 		_state->lex->token_type == JSON_TOKEN_OBJECT_START)
+ 	{
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ 		errmsg("cannot call %s on a nested object", _state->function_name)));
+ 	}
+ }
+ 
+ static void
+ hash_object_field_end(void *state, char *fname, bool isnull)
+ {
+ 	JHashState	_state = (JHashState) state;
+ 	JsonHashEntry hashentry;
+ 	bool		found;
+ 	char		name[NAMEDATALEN];
+ 
+ 	memset(name, 0, NAMEDATALEN);
+ 	strncpy(name, fname, NAMEDATALEN);
+ 
+ 	hashentry = hash_search(_state->hash, name, HASH_ENTER, &found);
+ 
+ 	if (found)
+ 		elog(ERROR, "duplicate key");
+ 
+ 	hashentry->isnull = isnull;
+ 	hashentry->val = _state->saved_scalar;
+ }
+ 
+ static void
+ hash_array_start(void *state)
+ {
+ 	JHashState	_state = (JHashState) state;
+ 
+ 	if (_state->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ 			   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_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ 			   errmsg("cannot call %s on an array", _state->function_name)));
+ 
+ 	_state->saved_scalar = token;
+ }
*** a/src/include/catalog/pg_operator.h
--- b/src/include/catalog/pg_operator.h
***************
*** 1724,1729 **** DESCR("range difference");
--- 1724,1739 ----
  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");
+ 
  
  /*
   * function prototypes
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 4103,4108 **** DESCR("map row to json");
--- 4103,4133 ----
  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 = 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 = 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 2 0 2283 "2283 114" _null_ _null_ _null_ _null_ json_populate_record _null_ _null_ _null_ ));
+ DESCR("get record fields from a json object");
+ 
  /* 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,45 ----
  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);
+ 
  #endif   /* JSON_H */
*** /dev/null
--- b/src/include/utils/jsonapi.h
***************
*** 0 ****
--- 1,82 ----
+ /*-------------------------------------------------------------------------
+  *
+  * jsonapi.h
+  *	  Declarations for JSON API support.
+  *
+  * Portions Copyright (c) 1996-2012, 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;
+ 
+ typedef struct
+ {
+ 	char	   *input;
+ 	char	   *token_start;
+ 	char	   *token_terminator;
+ 	char	   *prev_token_terminator;
+ 	JsonTokenType token_type;
+ 	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);
+ 
+ 
+ /*
+  * any of these actions can be NULL, in whi9ch case nothig is done.
+  */
+ typedef struct
+ {
+ 	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);
+ 
+ #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,681 ----
   {"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","three","four","five"]'),
+ ('object','{"field1":"val1","field2":"val2"}');
+ 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
+ (2 rows)
+ 
+ -- 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}');
+  json_each_as_text 
+ -------------------
+  (f1,"[1,2,3]")
+  (f2,"{""f3"":1}")
+  (f4,null)
+ (3 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  | null
+  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)
+ 
+ --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)
+ 
*** a/src/test/regress/sql/json.sql
--- b/src/test/regress/sql/json.sql
***************
*** 113,115 **** FROM (SELECT '-Infinity'::float8 AS "float8field") q;
--- 113,228 ----
  -- 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","three","four","five"]'),
+ ('object','{"field1":"val1","field2":"val2"}');
+ 
+ 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';
+ 
+ -- 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}');
+ 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);
+ 
+ --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;
#5Pavel Stehule
pavel.stehule@gmail.com
In reply to: Andrew Dunstan (#4)
Re: json api WIP patch

Hello

2013/1/4 Andrew Dunstan <andrew@dunslane.net>:

On 01/02/2013 05:51 PM, Andrew Dunstan wrote:

On 01/02/2013 04:45 PM, Robert Haas wrote:

On Wed, Dec 26, 2012 at 3:33 PM, Andrew Dunstan <andrew@dunslane.net>
wrote:

Here is a patch for the first part of the JSON API that was recently
discussed. It includes the json parser hook infrastructure and
functions
for json_get and friends, plus json_keys.

Udated patch that contains most of the functionality I'm after. One piece
left is populate_recordset (populate a set of records from a single json
datum which is an array of objects, in one pass). That requires a bit of
thought.

I hope most of the whitespace issues are fixed.

it is looking well

I have one note - is it "json_each" good name?

Regards

Pavel

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

--
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: Pavel Stehule (#5)
Re: json api WIP patch

On 01/04/2013 03:36 PM, Pavel Stehule wrote:

Hello

2013/1/4 Andrew Dunstan <andrew@dunslane.net>:

On 01/02/2013 05:51 PM, Andrew Dunstan wrote:

On 01/02/2013 04:45 PM, Robert Haas wrote:

On Wed, Dec 26, 2012 at 3:33 PM, Andrew Dunstan <andrew@dunslane.net>
wrote:

Here is a patch for the first part of the JSON API that was recently
discussed. It includes the json parser hook infrastructure and
functions
for json_get and friends, plus json_keys.

Udated patch that contains most of the functionality I'm after. One piece
left is populate_recordset (populate a set of records from a single json
datum which is an array of objects, in one pass). That requires a bit of
thought.

I hope most of the whitespace issues are fixed.

it is looking well

I have one note - is it "json_each" good name?

Possibly not, although hstore has each(). json_unnest might be even less
felicitous.

I'm expecting a good deal of bikeshedding - I'm trying to ignore those
issues for the most part because the functionality seems much more
important to me than the names.

The more people that pound on it and try to break it the happier I'll be.

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

#7Pavel Stehule
pavel.stehule@gmail.com
In reply to: Andrew Dunstan (#6)
Re: json api WIP patch

2013/1/4 Andrew Dunstan <andrew@dunslane.net>:

On 01/04/2013 03:36 PM, Pavel Stehule wrote:

Hello

2013/1/4 Andrew Dunstan <andrew@dunslane.net>:

On 01/02/2013 05:51 PM, Andrew Dunstan wrote:

On 01/02/2013 04:45 PM, Robert Haas wrote:

On Wed, Dec 26, 2012 at 3:33 PM, Andrew Dunstan <andrew@dunslane.net>
wrote:

Here is a patch for the first part of the JSON API that was recently
discussed. It includes the json parser hook infrastructure and
functions
for json_get and friends, plus json_keys.

Udated patch that contains most of the functionality I'm after. One piece
left is populate_recordset (populate a set of records from a single json
datum which is an array of objects, in one pass). That requires a bit of
thought.

I hope most of the whitespace issues are fixed.

it is looking well

I have one note - is it "json_each" good name?

Possibly not, although hstore has each(). json_unnest might be even less
felicitous.

I understand - but hstore isn't in core - so it should not be precedent

regexp_split_to_table

I am not native speaker, it sounds little bit strange - but maybe
because I am not native speaker :)

Regards

Pavel

I'm expecting a good deal of bikeshedding - I'm trying to ignore those
issues for the most part because the functionality seems much more important
to me than the names.

The more people that pound on it and try to break it the happier I'll be.

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

#8Merlin Moncure
mmoncure@gmail.com
In reply to: Pavel Stehule (#7)
Re: json api WIP patch

On Fri, Jan 4, 2013 at 3:03 PM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

I understand - but hstore isn't in core - so it should not be precedent

regexp_split_to_table

I am not native speaker, it sounds little bit strange - but maybe
because I am not native speaker :)

it's common usage: http://api.jquery.com/jQuery.each/

the patch looks fabulous.  There are a few trivial whitespace issues
yet and I noticed a leaked hstore comment@ 2440:
+ 	/*
+ 	 * if the input hstore 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.
+ 	 */

merlin

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

#9Pavel Stehule
pavel.stehule@gmail.com
In reply to: Merlin Moncure (#8)
Re: json api WIP patch

2013/1/7 Merlin Moncure <mmoncure@gmail.com>:

On Fri, Jan 4, 2013 at 3:03 PM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

I understand - but hstore isn't in core - so it should not be precedent

regexp_split_to_table

I am not native speaker, it sounds little bit strange - but maybe
because I am not native speaker :)

it's common usage: http://api.jquery.com/jQuery.each/

ook

Regards

Pavel

the patch looks fabulous.  There are a few trivial whitespace issues
yet and I noticed a leaked hstore comment@ 2440:
+       /*
+        * if the input hstore 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.
+        */

merlin

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

#10Andrew Dunstan
andrew@dunslane.net
In reply to: Merlin Moncure (#8)
1 attachment(s)
Re: json api WIP patch

On 01/07/2013 10:25 AM, Merlin Moncure wrote:

the patch looks fabulous.  There are a few trivial whitespace issues
yet and I noticed a leaked hstore comment@ 2440:
+ 	/*
+ 	 * if the input hstore 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.
+ 	 */

Here is a patch that has all the functionality I'm intending to provide.

The API is improved some, with the parser now keeping track of the
nesting levels instead of callers having to do so, and a constructor
function provided for JsonLexContext objects.

The processing functions have been extended to provide populate_record()
and populate_recordset() functions.The latter in particular could be
useful in decomposing a piece of json representing an array of flat
objects (a fairly common pattern) into a set of Postgres records in a
single pass.

The main thing I'm going to concentrate on now is making sure that this
doesn't leak memory. I'm sure there's some tightening required in that
area. Any eyeballs there will be greatly appreciated. There are also a
couple of very minor things to clean up.

You (Merlin) have kindly volunteered to work on documentation, so before
we go too far with that any bikeshedding on names, or on the
functionality being provided, should now take place.

cheers

andrew

Attachments:

jsonapi3.patchtext/x-patch; name=jsonapi3.patchDownload
*** 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,122 ----
  #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 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 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 */
! 
! static inline JsonTokenType
! lex_peek(JsonLexContext *lex)
! {
! 	return lex->token_type;
! }
! 
! 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;
! }
! 
! 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,106 **** static void array_to_json_internal(Datum array, StringInfo result,
  	 (c) == '_' || \
  	 IS_HIGHBIT_SET(c))
  
- 
  /*
   * Input.
   */
--- 130,135 ----
***************
*** 172,325 **** json_recv(PG_FUNCTION_ARGS)
  }
  
  /*
!  * 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;
- 		}
  	}
  }
  
  /*
--- 201,394 ----
  }
  
  /*
!  * lex constructor, with or without StringInfo object
!  * for de-escaped lexemes.
   */
+ 
+ JsonLexContext *
+ makeJsonLexContext(char *json, bool need_escapes)
+ {
+ 	JsonLexContext *lex = palloc0(sizeof(JsonLexContext));
+ 
+ 	lex->input = lex->token_terminator = lex->line_start = json;
+ 	lex->line_number = 1;
+ 	if (need_escapes)
+ 		lex->strval = makeStringInfo();
+ 	return lex;
+ }
+ 
+ /*
+  * parse routines
+  */
+ void
+ pg_parse_json(JsonLexContext *lex, JsonSemAction sem)
+ {
+ 	/* get the initial token */
+ 	json_lex(lex);
+ 
+ 
+ 	/* parse by recursive descent */
+ 	if (lex_peek(lex) == JSON_TOKEN_OBJECT_START)
+ 		parse_object(lex, sem);
+ 	else if (lex_peek(lex) == JSON_TOKEN_ARRAY_START)
+ 		parse_array(lex, sem);
+ 	else
+ 		parse_scalar(lex, sem); /* json can be a bare scalar */
+ 
+ 	lex_expect(JSON_PARSE_END, lex, JSON_TOKEN_END);
+ 
+ }
+ 
  static void
! parse_scalar(JsonLexContext *lex, JsonSemAction sem)
  {
! 	char	   *val = NULL;
! 	json_scalar_action sfunc = sem->scalar;
! 	JsonTokenType tok = lex_peek(lex);
! 
! 	if (lex_accept(lex, JSON_TOKEN_TRUE, &val) ||
! 		lex_accept(lex, JSON_TOKEN_FALSE, &val) ||
! 		lex_accept(lex, JSON_TOKEN_NULL, &val) ||
! 		lex_accept(lex, JSON_TOKEN_NUMBER, &val) ||
! 		lex_accept(lex, JSON_TOKEN_STRING, &val))
  	{
! 		if (sfunc != NULL)
! 			(*sfunc) (sem->semstate, val, tok);
! 	}
! 	else
! 	{
! 		report_parse_error(JSON_PARSE_VALUE, lex);
! 	}
! }
  
! static void
! parse_object_field(JsonLexContext *lex, JsonSemAction sem)
! {
! 	char	   *fname = NULL;	/* keep compiler quiet */
! 	json_ofield_action ostart = sem->object_field_start;
! 	json_ofield_action oend = sem->object_field_end;
! 	bool		isnull;
  
! 	if (!lex_accept(lex, JSON_TOKEN_STRING, &fname))
! 		report_parse_error(JSON_PARSE_STRING, lex);
  
! 	lex_expect(JSON_PARSE_OBJECT_LABEL, lex, JSON_TOKEN_COLON);
! 
! 	isnull = lex_peek(lex) == JSON_TOKEN_NULL;
! 
! 	if (ostart != NULL)
! 		(*ostart) (sem->semstate, fname, isnull);
! 
! 	if (lex_peek(lex) == JSON_TOKEN_OBJECT_START)
! 		parse_object(lex, sem);
! 	else if (lex_peek(lex) == JSON_TOKEN_ARRAY_START)
! 		parse_array(lex, sem);
! 	else
! 		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)
! {
! 	json_struct_action ostart = sem->object_start;
! 	json_struct_action oend = sem->object_end;
! 
! 	if (ostart != NULL)
! 		(*ostart) (sem->semstate);
! 
! 	lex->lex_level++;
! 
! 	/* we know this will succeeed, just clearing the token */
! 	lex_expect(JSON_PARSE_OBJECT_START, lex, JSON_TOKEN_OBJECT_START);
! 	if (lex_peek(lex) == JSON_TOKEN_STRING)
! 	{
! 		parse_object_field(lex, sem);
! 
! 		while (lex_accept(lex, JSON_TOKEN_COMMA, NULL))
! 			parse_object_field(lex, sem);
  
  	}
+ 	else if (lex_peek(lex) != JSON_TOKEN_OBJECT_END)
+ 	{
+ 		/* 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;
+ 	bool		isnull;
+ 
+ 	isnull = lex_peek(lex) == JSON_TOKEN_NULL;
+ 
+ 	if (astart != NULL)
+ 		(*astart) (sem->semstate, isnull);
+ 
+ 	if (lex_peek(lex) == JSON_TOKEN_OBJECT_START)
+ 		parse_object(lex, sem);
+ 	else if (lex_peek(lex) == JSON_TOKEN_ARRAY_START)
+ 		parse_array(lex, sem);
+ 	else
+ 		parse_scalar(lex, sem);
+ 
+ 	if (aend != NULL)
+ 		(*aend) (sem->semstate, isnull);
+ }
+ 
+ static void
+ parse_array(JsonLexContext *lex, JsonSemAction sem)
+ {
+ 	json_struct_action astart = sem->array_start;
+ 	json_struct_action aend = sem->array_end;
+ 
+ 	if (astart != NULL)
+ 		(*astart) (sem->semstate);
+ 
+ 	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);
+ }
+ 
+ /*
+  * Check whether supplied input is valid JSON.
+  */
+ static void
+ json_validate_cstring(char *input)
+ {
+ 	JsonLexContext *lex = makeJsonLexContext(input, false);
+ 
+ 	pg_parse_json(lex, NullSemAction);
  }
  
  /*
***************
*** 333,375 **** json_lex(JsonLexContext *lex)
  	/* 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
  	{
--- 402,468 ----
  	/* Skip leading whitespace. */
  	s = lex->token_terminator;
  	while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r')
! 	{
! 		if (*s == '\n')
! 			++lex->line_number;
! 		++s;
! 	}
  	lex->token_start = s;
  
  	/* Determine token type. */
! 	if (*s == '\0')
  	{
! 		lex->token_start = NULL;
! 		lex->prev_token_terminator = lex->token_terminator;
! 		lex->token_terminator = s;
! 		lex->token_type = JSON_TOKEN_END;
! 	}
! 	else if (strchr("{}[],:", s[0]))
! 	{
! 		/* Single-character token, some kind of punctuation mark. */
! 		lex->prev_token_terminator = lex->token_terminator;
! 		lex->token_terminator = s + 1;
! 		switch (s[0])
  		{
! 			case '{':
! 				lex->token_type = JSON_TOKEN_OBJECT_START;
! 				break;
! 			case '}':
! 				lex->token_type = JSON_TOKEN_OBJECT_END;
! 				break;
! 			case '[':
! 				lex->token_type = JSON_TOKEN_ARRAY_START;
! 				break;
! 			case ']':
! 				lex->token_type = JSON_TOKEN_ARRAY_END;
! 				break;
! 			case ',':
! 				lex->token_type = JSON_TOKEN_COMMA;
! 				break;
! 			case ':':
! 				lex->token_type = JSON_TOKEN_COLON;
! 				break;
! 			default:
! 				break;
  		}
  	}
  	else if (*s == '"')
  	{
  		/* String. */
  		json_lex_string(lex);
! 		lex->token_type = JSON_TOKEN_STRING;
  	}
  	else if (*s == '-')
  	{
  		/* Negative number. */
  		json_lex_number(lex, s + 1);
! 		lex->token_type = JSON_TOKEN_NUMBER;
  	}
  	else if (*s >= '0' && *s <= '9')
  	{
  		/* Positive number. */
  		json_lex_number(lex, s);
! 		lex->token_type = JSON_TOKEN_NUMBER;
  	}
  	else
  	{
***************
*** 384,399 **** json_lex(JsonLexContext *lex)
  		 * 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);
  		}
--- 477,491 ----
  		 * unintuitive prefix thereof.
  		 */
  		for (p = s; JSON_ALPHANUMERIC_CHAR(*p); 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);
  		}
***************
*** 402,419 **** json_lex(JsonLexContext *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);
  	}
--- 494,512 ----
  		 * 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);
  	}
***************
*** 427,432 **** json_lex_string(JsonLexContext *lex)
--- 520,528 ----
  {
  	char	   *s;
  
+ 	if (lex->strval != NULL)
+ 		resetStringInfo(lex->strval);
+ 
  	for (s = lex->token_start + 1; *s != '"'; s++)
  	{
  		/* Per RFC4627, these characters MUST be escaped. */
***************
*** 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),
--- 581,642 ----
  								 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
! 				 */
  				lex->token_terminator = s + pg_mblen(s);
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
***************
*** 497,506 **** json_lex_string(JsonLexContext *lex)
--- 645,661 ----
  								   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;
  }
  
***************
*** 585,596 **** json_lex_number(JsonLexContext *lex, char *s)
  	}
  
  	/*
! 	 * 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);
--- 740,752 ----
  	}
  
  	/*
! 	 * 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->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"),
--- 758,770 ----
   * 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"),
--- 778,784 ----
  	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,
--- 787,793 ----
  				 report_json_context(lex)));
  	else
  	{
! 		switch (ctx)
  		{
  			case JSON_PARSE_VALUE:
  				ereport(ERROR,
***************
*** 641,646 **** report_parse_error(JsonParseStack *stack, JsonLexContext *lex)
--- 797,810 ----
  								   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:
--- 817,832 ----
  				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:
--- 841,848 ----
  				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);
  		}
  	}
  }
--- 854,860 ----
  						 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);
--- 949,955 ----
  	 * suffixing "..." if not ending at end of line.
  	 */
  	prefix = (context_start > line_start) ? "..." : "";
! 	suffix = (lex->token_type != JSON_TOKEN_END && *context_end != '\0' && *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,1927 ----
+ /*-------------------------------------------------------------------------
+  *
+  * jsonfuncs.c
+  *		Functions to process JSON data type.
+  *
+  * Portions Copyright (c) 1996-2012, 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(char *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(char *jsonstr, 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.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_object_keys);
+ 
+ Datum
+ json_object_keys(PG_FUNCTION_ARGS)
+ {
+ 	FuncCallContext *funcctx;
+ 	OkeysState	state;
+ 	int			i;
+ 
+ 	if (SRF_IS_FIRSTCALL())
+ 	{
+ 		text	   *json = PG_GETARG_TEXT_P(0);
+ 		char	   *jsonstr = text_to_cstring(json);
+ 		JsonLexContext *lex = makeJsonLexContext(jsonstr, 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;
+ 
+ 	if (_state->lex->lex_level != 1)
+ 		return;
+ 	if (_state->result_count >= _state->result_size)
+ 	{
+ 		_state->result_size *= 2;
+ 		_state->result =
+ 			repalloc(_state->result, sizeof(char *) * _state->result_size);
+ 	}
+ 	_state->result[_state->result_count++] = pstrdup(fname);
+ }
+ 
+ static void
+ okeys_array_start(void *state)
+ {
+ 	OkeysState	_state = (OkeysState) state;
+ 
+ 	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;
+ 
+ 	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.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_get_ofield);
+ 
+ Datum
+ json_get_ofield(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	text	   *fname = PG_GETARG_TEXT_P(1);
+ 	char	   *jsonstr = text_to_cstring(json);
+ 	char	   *fnamestr = text_to_cstring(fname);
+ 	text	   *result;
+ 
+ 	result = get_worker(jsonstr, fnamestr, -1, NULL, -1, false);
+ 
+ 	if (result != NULL)
+ 		PG_RETURN_TEXT_P(result);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ PG_FUNCTION_INFO_V1(json_get_ofield_as_text);
+ 
+ Datum
+ json_get_ofield_as_text(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	text	   *fname = PG_GETARG_TEXT_P(1);
+ 	char	   *jsonstr = text_to_cstring(json);
+ 	char	   *fnamestr = text_to_cstring(fname);
+ 	text	   *result;
+ 
+ 	result = get_worker(jsonstr, fnamestr, -1, NULL, -1, true);
+ 	if (result != NULL)
+ 		PG_RETURN_TEXT_P(result);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ PG_FUNCTION_INFO_V1(json_get_aelem);
+ 
+ Datum
+ json_get_aelem(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	int			element = PG_GETARG_INT32(1);
+ 	char	   *jsonstr = text_to_cstring(json);
+ 	text	   *result;
+ 
+ 	result = get_worker(jsonstr, NULL, element, NULL, -1, false);
+ 
+ 	if (result != NULL)
+ 		PG_RETURN_TEXT_P(result);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ 
+ PG_FUNCTION_INFO_V1(json_get_aelem_as_text);
+ 
+ Datum
+ json_get_aelem_as_text(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	int			element = PG_GETARG_INT32(1);
+ 	char	   *jsonstr = text_to_cstring(json);
+ 	text	   *result;
+ 
+ 	result = get_worker(jsonstr, NULL, element, NULL, -1, true);
+ 
+ 	if (result != NULL)
+ 		PG_RETURN_TEXT_P(result);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ 
+ PG_FUNCTION_INFO_V1(json_get_path);
+ 
+ Datum
+ json_get_path(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	ArrayType  *path = PG_GETARG_ARRAYTYPE_P(1);
+ 	char	   *jsonstr = text_to_cstring(json);
+ 	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(jsonstr, NULL, -1, pathstr, npath, false);
+ 
+ 	if (result != NULL)
+ 		PG_RETURN_TEXT_P(result);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ PG_FUNCTION_INFO_V1(json_get_path_as_text);
+ 
+ Datum
+ json_get_path_as_text(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	ArrayType  *path = PG_GETARG_ARRAYTYPE_P(1);
+ 	char	   *jsonstr = text_to_cstring(json);
+ 	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(jsonstr, NULL, -1, pathstr, npath, true);
+ 
+ 	if (result != NULL)
+ 		PG_RETURN_TEXT_P(result);
+ 	else
+ 		PG_RETURN_NULL();
+ }
+ 
+ static text *
+ get_worker(char *json,
+ 		   char *field,
+ 		   int elem_index,
+ 		   char **path,
+ 		   int npath,
+ 		   bool normalize_results)
+ {
+ 	GetState	state;
+ 	JsonLexContext *lex = makeJsonLexContext(json, true);
+ 	JsonSemAction sem;
+ 
+ 	state = palloc0(sizeof(getState));
+ 	sem = palloc0(sizeof(jsonSemAction));
+ 
+ 	state->lex = lex;
+ 	state->normalize_results = normalize_results;
+ 	if (field != NULL)
+ 	{
+ 		state->search_type = JSON_SEARCH_OBJECT;
+ 		state->search_term = field;
+ 	}
+ 	else if (path != NULL)
+ 	{
+ 		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);
+ 		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
+ 	{
+ 		state->search_type = JSON_SEARCH_ARRAY;
+ 		state->search_index = elem_index;
+ 		state->array_index = -1;
+ 	}
+ 
+ 	sem->semstate = (void *) state;
+ 	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;
+ 
+ 	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)
+ 	{
+ 		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)
+ 	{
+ 		if (lex_level < _state->npath)
+ 			_state->pathok[lex_level] = true;
+ 
+ 		if (lex_level == _state->npath)
+ 			get_next = true;
+ 	}
+ 
+ 	if (get_next)
+ 	{
+ 		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)
+ 		{
+ 			_state->next_scalar = true;
+ 		}
+ 		else
+ 		{
+ 			_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;
+ 
+ 	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;
+ 	}
+ 
+ 	if (get_last && _state->result_start != NULL)
+ 	{
+ 		int			len = _state->lex->prev_token_terminator - _state->result_start;
+ 
+ 		_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;
+ 
+ 	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")));
+ 	else 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)
+ 	{
+ 		_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])
+ 	{
+ 		if (++_state->array_level_index[lex_level - 1] ==
+ 			_state->path_level_index[lex_level - 1])
+ 		{
+ 			if (lex_level == _state->npath)
+ 				get_next = true;
+ 			else
+ 				_state->pathok[lex_level] = true;
+ 		}
+ 
+ 	}
+ 
+ 	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;
+ 
+ 	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;
+ 
+ 		_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)
+ 	{
+ 		_state->tresult = cstring_to_text(token);
+ 		_state->next_scalar = false;
+ 	}
+ 
+ }
+ 
+ /*
+  * SQL function json_array_length
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_array_length);
+ 
+ Datum
+ json_array_length(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	char	   *jsonstr = text_to_cstring(json);
+ 
+ 	AlenState	state;
+ 	JsonLexContext *lex = makeJsonLexContext(jsonstr, 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);
+ }
+ 
+ static void
+ alen_object_start(void *state)
+ {
+ 	AlenState	_state = (AlenState) state;
+ 
+ 	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;
+ 
+ 	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;
+ 
+ 	if (_state->lex->lex_level == 1)
+ 		_state->count++;
+ }
+ 
+ /*
+  * SQL function json_each
+  *
+  * decompose a json object into key value pairs.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_each);
+ 
+ Datum
+ json_each(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	char	   *jsonstr = text_to_cstring(json);
+ 	JsonLexContext *lex = makeJsonLexContext(jsonstr, 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.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_each_as_text);
+ 
+ Datum
+ json_each_as_text(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	char	   *jsonstr = text_to_cstring(json);
+ 	JsonLexContext *lex = makeJsonLexContext(jsonstr, 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];
+ 	static 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 (_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;
+ 
+ 	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;
+ 
+ 	if (_state->lex->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call json_each on a scalar")));
+ 
+ 	if (_state->next_scalar)
+ 		_state->normalized_scalar = token;
+ }
+ 
+ /*
+  * SQL function json_unnest
+  *
+  * get the elements from a json array
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_unnest);
+ 
+ Datum
+ json_unnest(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	char	   *jsonstr = text_to_cstring(json);
+ 	JsonLexContext *lex = makeJsonLexContext(jsonstr, true);
+ 	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;
+ 
+ 	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;
+ 
+ 	if (_state->lex->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call json_unnest on a scalar")));
+ }
+ 
+ /*
+  * 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.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_populate_record);
+ 
+ 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);
+ 	char	   *jsonstr = text_to_cstring(json);
+ 	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(jsonstr, "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(char *jsonstr, char *funcname, bool use_json_as_text)
+ {
+ 	HASHCTL		ctl;
+ 	HTAB	   *tab;
+ 	JHashState	state;
+ 	JsonLexContext *lex = makeJsonLexContext(jsonstr, 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.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_populate_recordset);
+ 
+ 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);
+ 	char	   *jsonstr;
+ 	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;
+ 
+ 	jsonstr = text_to_cstring(json);
+ 	lex = makeJsonLexContext(jsonstr, 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,1739 ----
  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");
+ 
  
  /*
   * function prototypes
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 4103,4108 **** DESCR("map row to json");
--- 4103,4135 ----
  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 = 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 = 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,86 ----
+ /*-------------------------------------------------------------------------
+  *
+  * jsonapi.h
+  *	  Declarations for JSON API support.
+  *
+  * Portions Copyright (c) 1996-2012, 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;
+ 
+ typedef struct JsonLexContext
+ {
+ 	char	   *input;
+ 	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);
+ 
+ 
+ /*
+  * any of these actions can be NULL, in which case nothig is done.
+  */
+ 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 */
+ extern JsonLexContext *makeJsonLexContext(char *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,764 ----
   {"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","three","four","five"]'),
+ ('object','{"field1":"val1","field2":"val2"}');
+ 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
+ (2 rows)
+ 
+ -- 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}');
+  json_each_as_text 
+ -------------------
+  (f1,"[1,2,3]")
+  (f2,"{""f3"":1}")
+  (f4,null)
+ (3 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  | null
+  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)
+ 
+ --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,251 ----
  -- 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","three","four","five"]'),
+ ('object','{"field1":"val1","field2":"val2"}');
+ 
+ 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';
+ 
+ -- 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}');
+ 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);
+ 
+ --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;
#11james
james@mansionfamily.plus.com
In reply to: Andrew Dunstan (#10)
Re: json api WIP patch

The processing functions have been extended to provide populate_record() and populate_recordset() functions.The latter in particular could be useful in decomposing a piece of json representing an array of flat objects (a fairly common pattern) into a set of Postgres records in a single pass.

So this would allow an 'insert into ... select ... from
<unpack-the-JSON>(...)'?

I had been wondering how to do such an insertion efficiently in the
context of SPI, but it seems that there is no SPI_copy equiv that would
allow a query parse and plan to be avoided.

Is this mechanism likely to be as fast as we can get at the moment in
contexts where copy is not feasible?

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

#12Andrew Dunstan
andrew@dunslane.net
In reply to: james (#11)
Re: json api WIP patch

On 01/08/2013 01:45 AM, james wrote:

The processing functions have been extended to provide
populate_record() and populate_recordset() functions.The latter in
particular could be useful in decomposing a piece of json
representing an array of flat objects (a fairly common pattern) into
a set of Postgres records in a single pass.

So this would allow an 'insert into ... select ... from
<unpack-the-JSON>(...)'?

Yes.

I had been wondering how to do such an insertion efficiently in the
context of SPI, but it seems that there is no SPI_copy equiv that
would allow a query parse and plan to be avoided.

Your query above would need to be planned too, although the plan will be
trivial.

Is this mechanism likely to be as fast as we can get at the moment in
contexts where copy is not feasible?

You should not try to use it as a general bulk load facility. And it
will not be as fast as COPY for several reasons, including that the Json
parsing routines are necessarily much heavier than the COPY parse
routines, which have in any case been optimized over quite a long
period. Also, a single json datum is limited to no more than 1Gb. If you
have such a datum, parsing it involves having it in memory and then
taking a copy (I wonder if we could avoid that step - will take a look).
Then each object is decomposed into a hash table of key value pairs,
which it then used to construct the record datum. Each field name in
the result record is used to look up the value in the hash table - this
happens once in the case of populate_record() and once per object in the
array in the case of populate_recordset(). In the latter case the
resulting records are put into a tuplestore structure (which spills to
disk if necessary) which is then returned to the caller when all the
objects in the json array are processed. COPY doesn't have these sorts
of issues. It knows without having to look things up where each datum is
in each record, and it stashes the result straight into the target
table. It can read and insert huge numbers of rows without significant
memory implications.

Both these routines and COPY in non-binary mode use the data type input
routines to convert text values. In some cases (very notably timestamps)
these routines can easily be shown to be fantastically expensive
compared to binary input. This is part of what has led to the creation
of utilities like pg_bulkload.

Perhaps if you give us a higher level view of what you're trying to
achieve we can help you better.

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

#13james
james@mansionfamily.plus.com
In reply to: Andrew Dunstan (#12)
Re: json api WIP patch

I had been wondering how to do such an insertion efficiently in the context of SPI, but it seems that there is no SPI_copy equiv that would allow a query parse and plan to be avoided.

Your query above would need to be planned too, although the plan will be trivial.

Ah yes, I meant that I had not found a way to avoid it (for multi-row
inserts etc) from a stored proc context where I have SPI functions
available.

You should not try to use it as a general bulk load facility. And it will not be as fast as COPY for several reasons, including that the Json parsing routines are necessarily much heavier than the COPY parse routines, which have in any case been optimized over quite a long period. Also, a single json datum is limited to no more than 1Gb. If you have such a datum, parsing it involves having it in memory and then taking a copy (I wonder if we could avoid that step - will take a look). Then each object is decomposed into a hash table of key value pairs, which it then used to construct the record datum. Each field name in the result record is used to look up the value in the hash table - this happens once in the case of populate_record() and once per object in the array in the case of populate_recordset(). In the latter case the resulting records are put into a tuplestore structure (which spills to disk if necessary) which is then returned to the caller when all the objects in

the js
on array are processed. COPY doesn't have these sorts of issues. It knows without having to look things up where each datum is in each record, and it stashes the result straight into the target table. It can read and insert huge numbers of rows without significant memory implications.

Yes - but I don't think I can use COPY from a stored proc context can I?
If I could use binary COPY from a stored proc that has received a
binary param and unpacked to the data, it would be handy.

If SPI provided a way to perform a copy to a temp table and then some
callback on an iterator that yields rows to it, that would do the trick
I guess.

Perhaps if you give us a higher level view of what you're trying to achieve we can help you better.

I had been trying to identify a way to work with record sets where the
records might be used for insert, or for updates or deletion statements,
preferably without forming a large custom SQL statement that must then
be parsed and planned (and which would be a PITA if I wanted to use the
SQL-C preprocessor or some language bindings that like to prepare a
statement and execute with params).

The data I work with has a master-detail structure and insertion
performance matters, so I'm trying to limit manipulations to one
statement per table per logical operation even where there are multiple
detail rows.

Sometimes the network latency can be a pain too and that also suggests
an RPC with unpack and insert locally.

Cheers
James

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

#14Andrew Dunstan
andrew@dunslane.net
In reply to: Andrew Dunstan (#12)
Re: json api WIP patch

On 01/08/2013 09:58 AM, Andrew Dunstan wrote:

If you have such a datum, parsing it involves having it in memory and
then taking a copy (I wonder if we could avoid that step - will take a
look).

Here is a Proof Of Concept patch against my development tip on what's
involved in getting the JSON lexer not to need a nul-terminated string
to parse. This passes regression, incidentally. The downside is that
processing is very slightly more complex, and that json_in() would need
to call strlen() on its input. The upside would be that the processing
routines I've been working on would no longer need to create copies of
their json arguments using text_to_cstring() just so they can get a
null-terminated string to process.

Consequent changes would modify the signature of makeJsonLexContext() so
it's first argument would be a text* instead of a char* (and of course
its logic would change accordingly).

I could go either way. Thoughts?

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

#15Andrew Dunstan
andrew@dunslane.net
In reply to: Andrew Dunstan (#14)
1 attachment(s)
Re: json api WIP patch

On 01/08/2013 03:12 PM, Andrew Dunstan wrote:

On 01/08/2013 09:58 AM, Andrew Dunstan wrote:

If you have such a datum, parsing it involves having it in memory and
then taking a copy (I wonder if we could avoid that step - will take
a look).

Here is a Proof Of Concept patch against my development tip on what's
involved in getting the JSON lexer not to need a nul-terminated string
to parse. This passes regression, incidentally. The downside is that
processing is very slightly more complex, and that json_in() would
need to call strlen() on its input. The upside would be that the
processing routines I've been working on would no longer need to
create copies of their json arguments using text_to_cstring() just so
they can get a null-terminated string to process.

Consequent changes would modify the signature of makeJsonLexContext()
so it's first argument would be a text* instead of a char* (and of
course its logic would change accordingly).

I could go either way. Thoughts?

this time with patch ...

Attachments:

jsonparser.patchtext/x-patch; name=jsonparser.patchDownload
*** a/src/backend/utils/adt/json.c
--- b/src/backend/utils/adt/json.c
***************
*** 212,217 **** makeJsonLexContext(char *json, bool need_escapes)
--- 212,218 ----
  
  	lex->input = lex->token_terminator = lex->line_start = json;
  	lex->line_number = 1;
+ 	lex->input_length = strlen(json);
  	if (need_escapes)
  		lex->strval = makeStringInfo();
  	return lex;
***************
*** 398,416 **** static void
  json_lex(JsonLexContext *lex)
  {
  	char	   *s;
! 
  	/* Skip leading whitespace. */
  	s = lex->token_terminator;
! 	while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r')
  	{
  		if (*s == '\n')
  			++lex->line_number;
  		++s;
  	}
  	lex->token_start = s;
  
  	/* Determine token type. */
! 	if (*s == '\0')
  	{
  		lex->token_start = NULL;
  		lex->prev_token_terminator = lex->token_terminator;
--- 399,420 ----
  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;
***************
*** 476,482 **** json_lex(JsonLexContext *lex)
  		 * whole word as an unexpected token, rather than just some
  		 * unintuitive prefix thereof.
  		 */
! 		for (p = s; JSON_ALPHANUMERIC_CHAR(*p); p++)
  			 /* skip */ ;
  
  		/*
--- 480,486 ----
  		 * 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 */ ;
  
  		/*
***************
*** 519,539 **** static void
  json_lex_string(JsonLexContext *lex)
  {
  	char	   *s;
! 
  	if (lex->strval != NULL)
  		resetStringInfo(lex->strval);
  
! 	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,
--- 523,545 ----
  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,
***************
*** 547,553 **** json_lex_string(JsonLexContext *lex)
  		{
  			/* OK, we have an escape character. */
  			s++;
! 			if (*s == '\0')
  			{
  				lex->token_terminator = s;
  				report_invalid_token(lex);
--- 553,560 ----
  		{
  			/* OK, we have an escape character. */
  			s++;
! 			len++;
! 			if (len >= lex->input_length)
  			{
  				lex->token_terminator = s;
  				report_invalid_token(lex);
***************
*** 560,566 **** json_lex_string(JsonLexContext *lex)
  				for (i = 1; i <= 4; i++)
  				{
  					s++;
! 					if (*s == '\0')
  					{
  						lex->token_terminator = s;
  						report_invalid_token(lex);
--- 567,574 ----
  				for (i = 1; i <= 4; i++)
  				{
  					s++;
! 					len++;
! 					if (len >= lex->input_length)
  					{
  						lex->token_terminator = s;
  						report_invalid_token(lex);
***************
*** 690,696 **** json_lex_number(JsonLexContext *lex, char *s)
--- 698,706 ----
  {
  	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. */
  
***************
*** 702,741 **** json_lex_number(JsonLexContext *lex, char *s)
  		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');
  		}
  	}
  
--- 712,759 ----
  		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');
  		}
  	}
  
***************
*** 744,750 **** json_lex_number(JsonLexContext *lex, char *s)
  	 * here should be considered part of the token for error-reporting
  	 * purposes.
  	 */
! 	for (p = s; JSON_ALPHANUMERIC_CHAR(*p); p++)
  		error = true;
  	lex->prev_token_terminator = lex->token_terminator;
  	lex->token_terminator = p;
--- 762,768 ----
  	 * 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;
*** a/src/include/utils/jsonapi.h
--- b/src/include/utils/jsonapi.h
***************
*** 36,41 **** typedef enum
--- 36,42 ----
  typedef struct JsonLexContext
  {
  	char	   *input;
+ 	int			input_length;
  	char	   *token_start;
  	char	   *token_terminator;
  	char	   *prev_token_terminator;
#16Andrew Dunstan
andrew@dunslane.net
In reply to: james (#13)
Re: json api WIP patch

On 01/08/2013 03:07 PM, james wrote:

Yes - but I don't think I can use COPY from a stored proc context can
I? If I could use binary COPY from a stored proc that has received a
binary param and unpacked to the data, it would be handy.

You can use COPY from a stored procedure, but only to and from files.

If SPI provided a way to perform a copy to a temp table and then some
callback on an iterator that yields rows to it, that would do the
trick I guess.

SPI is useful, but it's certainly possible to avoid its use. After all,
that what almost the whole backend does, including the COPY code. Of
course, it's a lot harder to write that way, which is part of why SPI
exists. Efficiency has its price.

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

#17Peter Eisentraut
peter_e@gmx.net
In reply to: Andrew Dunstan (#10)
Re: json api WIP patch

On 1/7/13 5:15 PM, Andrew Dunstan wrote:

You (Merlin) have kindly volunteered to work on documentation, so before
we go too far with that any bikeshedding on names, or on the
functionality being provided, should now take place.

Hmm, I was going to say, this patch contains no documentation, so I have
no idea what it is supposed to do. "Recently discussed" isn't a good
substitute for describing what the patch is supposed to accomplish.

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

#18james
james@mansionfamily.plus.com
In reply to: Andrew Dunstan (#16)
Re: json api WIP patch

You can use COPY from a stored procedure, but only to and from files.

I think that's in the chocolate fireguard realm though as far as
efficiency for this sort of scenario goes, even if its handled by
retaining an mmap'd file as workspace.

If SPI provided a way to perform a copy to a temp table and then some callback on an iterator that yields rows to it, that would do the trick I guess.

SPI is useful, but it's certainly possible to avoid its use. After all, that what almost the whole backend does, including the COPY code. Of course, it's a lot harder to write that way, which is part of why SPI exists. Efficiency has its price.

So it is possible to use a lower level interface from a C stored proc?
SPI is the (only) documented direct function extension API isn't it?

Is the issue with using the JSON data-to-record set that the parsing can
be costly? Perhaps it can be achieved with B64 of compressed protobuf,
or such. I don't mind if it seems a bit messy - the code can be
generated from the table easily enough, especially if I can use C++. I
guess an allocator that uses SPI_palloc would solve issues with memory
management on error?

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

#19Merlin Moncure
mmoncure@gmail.com
In reply to: Peter Eisentraut (#17)
Re: json api WIP patch

On Tue, Jan 8, 2013 at 3:19 PM, Peter Eisentraut <peter_e@gmx.net> wrote:

On 1/7/13 5:15 PM, Andrew Dunstan wrote:

You (Merlin) have kindly volunteered to work on documentation, so before
we go too far with that any bikeshedding on names, or on the
functionality being provided, should now take place.

Hmm, I was going to say, this patch contains no documentation, so I have
no idea what it is supposed to do. "Recently discussed" isn't a good
substitute for describing what the patch is supposed to accomplish.

Why not? There are functional examples in the docs and the purpose of
the various functions was hashed out pretty well a couple weeks back,
deficiencies corrected, etc.

reference: http://postgresql.1045698.n5.nabble.com/json-accessors-td5733929.html

merlin

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

#20Andrew Dunstan
andrew@dunslane.net
In reply to: Merlin Moncure (#19)
Re: json api WIP patch

On 01/08/2013 04:32 PM, Merlin Moncure wrote:

On Tue, Jan 8, 2013 at 3:19 PM, Peter Eisentraut<peter_e@gmx.net> wrote:

On 1/7/13 5:15 PM, Andrew Dunstan wrote:

You (Merlin) have kindly volunteered to work on documentation, so before
we go too far with that any bikeshedding on names, or on the
functionality being provided, should now take place.

Hmm, I was going to say, this patch contains no documentation, so I have
no idea what it is supposed to do. "Recently discussed" isn't a good
substitute for describing what the patch is supposed to accomplish.

Why not? There are functional examples in the docs and the purpose of
the various functions was hashed out pretty well a couple weeks back,
deficiencies corrected, etc.

reference:http://postgresql.1045698.n5.nabble.com/json-accessors-td5733929.html

Well, at a high level the patch is meant to do two things: provide an
API that can be used to build JSON processing functions easily, and
provide some basic json processing functions built on the API. Those
functions provide similar capabilities to the accessor functions that
hstore has.

Perhaps also this will help. Here is the list of functions and operators
as currently implemented. I also have working operators for the get_path
functions which will be in a future patch.

All these are used in the included regression tests.

Name | Result data type | Argument data types

-------------------------+------------------+------------------------------------------------------------------------

json_array_length | integer | json

json_each | SETOF record | from_json json, OUT key text, OUT value json

json_each_as_text | SETOF record | from_json json, OUT key text, OUT value text

json_get | json | json, integer

json_get | json | json, text

json_get_as_text | text | json, integer

json_get_as_text | text | json, text

json_get_path | json | from_json json, VARIADIC path_elems text[]

json_get_path_as_text | text | from_json json, VARIADIC path_elems text[]

json_object_keys | SETOF text | json

json_populate_record | anyelement | anyelement, json

json_populate_recordset | SETOF anyelement | base anyelement, from_json json, use_json_as_text boolean DEFAULT false

json_unnest | SETOF json | from_json json, OUT value json

Name | Left arg type | Right arg type | Result type | Description

------+---------------+----------------+-------------+--------------------------------

-> | json | integer | json | get json array element

-> | json | text | json | get json object field

->> | json | integer | text | get json array element as text

->> | json | text | text | get json object field as text

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

#21Merlin Moncure
mmoncure@gmail.com
In reply to: Andrew Dunstan (#10)
Re: json api WIP patch

On Mon, Jan 7, 2013 at 4:15 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

You (Merlin) have kindly volunteered to work on documentation, so before we
go too far with that any bikeshedding on names, or on the functionality
being provided, should now take place.

Barring comment/complaint, I'm just going to roll with what we've got.
It seems pretty good to me.

merlin

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

#22Andrew Dunstan
andrew@dunslane.net
In reply to: Andrew Dunstan (#4)
1 attachment(s)
Re: json api WIP patch

On 01/04/2013 03:23 PM, Andrew Dunstan wrote:

On 01/02/2013 05:51 PM, Andrew Dunstan wrote:

On 01/02/2013 04:45 PM, Robert Haas wrote:

On Wed, Dec 26, 2012 at 3:33 PM, Andrew Dunstan
<andrew@dunslane.net> wrote:

Here is a patch for the first part of the JSON API that was recently
discussed. It includes the json parser hook infrastructure and
functions
for json_get and friends, plus json_keys.

Udated patch that contains most of the functionality I'm after. One
piece left is populate_recordset (populate a set of records from a
single json datum which is an array of objects, in one pass). That
requires a bit of thought.

I hope most of the whitespace issues are fixed.

This updated patch contains all the intended functionality, including
operators for the json_get_path functions, so you can say things like

select jsonval->array['f1','0','f2] ...

It also removes any requirement to copy the json value before setting up
the lexer by removing the lexer's requirement to have a nul terminated
string. Instead the lexer is told the input length and relies on that.
For this reason, json_in() now calls cstring_get_text() before rather
than after calling the validation routine, but that's really not
something worth bothering about.

A couple of points worth noting: it's a pity that we have to run CREATE
OR REPLACE FUNCTION in system_views.sql in order to set up default
values for builtin functions. That feels very kludgy. Also, making
operators for variadic functions is a bit of a pain. I had to set up
non-variadic version of the same functions (see json_get_path_op and
json_get_path_as_text_op) just so I could set up the operators. Neither
of these are exactly showstopper items, just mild annoyances.

I will continue hunting memory leaks, but when Merlin gets done with
docco I think we'll be far enough advanced to add this to the commitfest.

cheers

andrew

Attachments:

jsonapi4.patchtext/x-patch; name=jsonapi4.patchDownload
*** 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,121 ----
  #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 void json_lex(JsonLexContext *lex);
  static void json_lex_string(JsonLexContext *lex);
  static void json_lex_number(JsonLexContext *lex, char *s);
! static 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 */
! 
! static inline JsonTokenType
! lex_peek(JsonLexContext *lex)
! {
! 	return lex->token_type;
! }
! 
! 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;
! }
! 
! 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));
  }
  
  /*
--- 129,150 ----
  	 (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,325 **** 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;
! 		}
  	}
  }
  
  /*
--- 183,382 ----
  	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);
  }
  
  /*
!  * lex constructor, with or without StringInfo object
!  * for de-escaped lexemes.
   */
+ 
+ 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;
+ }
+ 
+ /*
+  * parse routines
+  */
+ void
+ pg_parse_json(JsonLexContext *lex, JsonSemAction sem)
+ {
+ 	/* get the initial token */
+ 	json_lex(lex);
+ 
+ 
+ 	/* parse by recursive descent */
+ 	if (lex_peek(lex) == JSON_TOKEN_OBJECT_START)
+ 		parse_object(lex, sem);
+ 	else if (lex_peek(lex) == JSON_TOKEN_ARRAY_START)
+ 		parse_array(lex, sem);
+ 	else
+ 		parse_scalar(lex, sem); /* json can be a bare scalar */
+ 
+ 	lex_expect(JSON_PARSE_END, lex, JSON_TOKEN_END);
+ 
+ }
+ 
  static void
! parse_scalar(JsonLexContext *lex, JsonSemAction sem)
  {
! 	char	   *val = NULL;
! 	json_scalar_action sfunc = sem->scalar;
! 	JsonTokenType tok = lex_peek(lex);
! 
! 	if (lex_accept(lex, JSON_TOKEN_TRUE, &val) ||
! 		lex_accept(lex, JSON_TOKEN_FALSE, &val) ||
! 		lex_accept(lex, JSON_TOKEN_NULL, &val) ||
! 		lex_accept(lex, JSON_TOKEN_NUMBER, &val) ||
! 		lex_accept(lex, JSON_TOKEN_STRING, &val))
  	{
! 		if (sfunc != NULL)
! 			(*sfunc) (sem->semstate, val, tok);
! 	}
! 	else
! 	{
! 		report_parse_error(JSON_PARSE_VALUE, lex);
! 	}
! }
  
! static void
! parse_object_field(JsonLexContext *lex, JsonSemAction sem)
! {
! 	char	   *fname = NULL;	/* keep compiler quiet */
! 	json_ofield_action ostart = sem->object_field_start;
! 	json_ofield_action oend = sem->object_field_end;
! 	bool		isnull;
  
! 	if (!lex_accept(lex, JSON_TOKEN_STRING, &fname))
! 		report_parse_error(JSON_PARSE_STRING, lex);
  
! 	lex_expect(JSON_PARSE_OBJECT_LABEL, lex, JSON_TOKEN_COLON);
  
! 	isnull = lex_peek(lex) == JSON_TOKEN_NULL;
! 
! 	if (ostart != NULL)
! 		(*ostart) (sem->semstate, fname, isnull);
! 
! 	if (lex_peek(lex) == JSON_TOKEN_OBJECT_START)
! 		parse_object(lex, sem);
! 	else if (lex_peek(lex) == JSON_TOKEN_ARRAY_START)
! 		parse_array(lex, sem);
! 	else
! 		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)
! {
! 	json_struct_action ostart = sem->object_start;
! 	json_struct_action oend = sem->object_end;
! 
! 	if (ostart != NULL)
! 		(*ostart) (sem->semstate);
! 
! 	lex->lex_level++;
! 
! 	/* we know this will succeeed, just clearing the token */
! 	lex_expect(JSON_PARSE_OBJECT_START, lex, JSON_TOKEN_OBJECT_START);
! 	if (lex_peek(lex) == JSON_TOKEN_STRING)
! 	{
! 		parse_object_field(lex, sem);
! 
! 		while (lex_accept(lex, JSON_TOKEN_COMMA, NULL))
! 			parse_object_field(lex, sem);
! 
! 	}
! 	else if (lex_peek(lex) != JSON_TOKEN_OBJECT_END)
! 	{
! 		/* 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;
! 	bool		isnull;
! 
! 	isnull = lex_peek(lex) == JSON_TOKEN_NULL;
! 
! 	if (astart != NULL)
! 		(*astart) (sem->semstate, isnull);
! 
! 	if (lex_peek(lex) == JSON_TOKEN_OBJECT_START)
! 		parse_object(lex, sem);
! 	else if (lex_peek(lex) == JSON_TOKEN_ARRAY_START)
! 		parse_array(lex, sem);
! 	else
! 		parse_scalar(lex, sem);
! 
! 	if (aend != NULL)
! 		(*aend) (sem->semstate, isnull);
! }
! 
! static void
! parse_array(JsonLexContext *lex, JsonSemAction sem)
! {
! 	json_struct_action astart = sem->array_start;
! 	json_struct_action aend = sem->array_end;
! 
! 	if (astart != NULL)
! 		(*astart) (sem->semstate);
! 
! 	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);
  }
  
  /*
***************
*** 329,375 **** 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
  	{
--- 386,459 ----
  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 if (strchr("{}[],:", s[0]))
! 	{
! 		/* Single-character token, some kind of punctuation mark. */
! 		lex->prev_token_terminator = lex->token_terminator;
! 		lex->token_terminator = s + 1;
! 		switch (s[0])
  		{
! 			case '{':
! 				lex->token_type = JSON_TOKEN_OBJECT_START;
! 				break;
! 			case '}':
! 				lex->token_type = JSON_TOKEN_OBJECT_END;
! 				break;
! 			case '[':
! 				lex->token_type = JSON_TOKEN_ARRAY_START;
! 				break;
! 			case ']':
! 				lex->token_type = JSON_TOKEN_ARRAY_END;
! 				break;
! 			case ',':
! 				lex->token_type = JSON_TOKEN_COMMA;
! 				break;
! 			case ':':
! 				lex->token_type = JSON_TOKEN_COLON;
! 				break;
! 			default:
! 				break;
  		}
  	}
  	else if (*s == '"')
  	{
  		/* String. */
  		json_lex_string(lex);
! 		lex->token_type = JSON_TOKEN_STRING;
  	}
  	else if (*s == '-')
  	{
  		/* Negative number. */
  		json_lex_number(lex, s + 1);
! 		lex->token_type = JSON_TOKEN_NUMBER;
  	}
  	else if (*s >= '0' && *s <= '9')
  	{
  		/* Positive number. */
  		json_lex_number(lex, s);
! 		lex->token_type = JSON_TOKEN_NUMBER;
  	}
  	else
  	{
***************
*** 383,399 **** json_lex(JsonLexContext *lex)
  		 * 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);
  		}
--- 467,482 ----
  		 * 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);
  		}
***************
*** 402,419 **** json_lex(JsonLexContext *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);
  	}
--- 485,503 ----
  		 * 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);
  	}
***************
*** 426,443 **** 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,
--- 510,532 ----
  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);
--- 540,547 ----
  		{
  			/* 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);
--- 554,561 ----
  				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),
--- 576,637 ----
  								 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
! 				 */
  				lex->token_terminator = s + pg_mblen(s);
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
***************
*** 497,506 **** json_lex_string(JsonLexContext *lex)
--- 640,656 ----
  								   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;
  }
  
***************
*** 535,596 **** 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);
--- 685,760 ----
  {
  	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"),
--- 766,778 ----
   * 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"),
--- 786,792 ----
  	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,
--- 795,801 ----
  				 report_json_context(lex)));
  	else
  	{
! 		switch (ctx)
  		{
  			case JSON_PARSE_VALUE:
  				ereport(ERROR,
***************
*** 641,646 **** report_parse_error(JsonParseStack *stack, JsonLexContext *lex)
--- 805,818 ----
  								   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:
--- 825,840 ----
  				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:
--- 849,856 ----
  				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);
  		}
  	}
  }
--- 862,868 ----
  						 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);
--- 957,963 ----
  	 * 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,1913 ----
+ /*-------------------------------------------------------------------------
+  *
+  * jsonfuncs.c
+  *		Functions to process JSON data type.
+  *
+  * Portions Copyright (c) 1996-2012, 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.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_object_keys);
+ 
+ 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;
+ 
+ 	if (_state->lex->lex_level != 1)
+ 		return;
+ 	if (_state->result_count >= _state->result_size)
+ 	{
+ 		_state->result_size *= 2;
+ 		_state->result =
+ 			repalloc(_state->result, sizeof(char *) * _state->result_size);
+ 	}
+ 	_state->result[_state->result_count++] = pstrdup(fname);
+ }
+ 
+ static void
+ okeys_array_start(void *state)
+ {
+ 	OkeysState	_state = (OkeysState) state;
+ 
+ 	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;
+ 
+ 	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.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_get_ofield);
+ 
+ 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();
+ }
+ 
+ PG_FUNCTION_INFO_V1(json_get_ofield_as_text);
+ 
+ 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();
+ }
+ 
+ PG_FUNCTION_INFO_V1(json_get_aelem);
+ 
+ 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();
+ }
+ 
+ 
+ PG_FUNCTION_INFO_V1(json_get_aelem_as_text);
+ 
+ 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();
+ }
+ 
+ 
+ PG_FUNCTION_INFO_V1(json_get_path);
+ 
+ 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();
+ }
+ 
+ PG_FUNCTION_INFO_V1(json_get_path_as_text);
+ 
+ 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();
+ }
+ 
+ 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;
+ 
+ 	state = palloc0(sizeof(getState));
+ 	sem = palloc0(sizeof(jsonSemAction));
+ 
+ 	state->lex = lex;
+ 	state->normalize_results = normalize_results;
+ 	if (field != NULL)
+ 	{
+ 		state->search_type = JSON_SEARCH_OBJECT;
+ 		state->search_term = field;
+ 	}
+ 	else if (path != NULL)
+ 	{
+ 		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);
+ 		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
+ 	{
+ 		state->search_type = JSON_SEARCH_ARRAY;
+ 		state->search_index = elem_index;
+ 		state->array_index = -1;
+ 	}
+ 
+ 	sem->semstate = (void *) state;
+ 	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;
+ 
+ 	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)
+ 	{
+ 		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)
+ 	{
+ 		if (lex_level < _state->npath)
+ 			_state->pathok[lex_level] = true;
+ 
+ 		if (lex_level == _state->npath)
+ 			get_next = true;
+ 	}
+ 
+ 	if (get_next)
+ 	{
+ 		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)
+ 		{
+ 			_state->next_scalar = true;
+ 		}
+ 		else
+ 		{
+ 			_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;
+ 
+ 	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;
+ 	}
+ 
+ 	if (get_last && _state->result_start != NULL)
+ 	{
+ 		int			len = _state->lex->prev_token_terminator - _state->result_start;
+ 
+ 		_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;
+ 
+ 	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")));
+ 	else 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)
+ 	{
+ 		_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])
+ 	{
+ 		if (++_state->array_level_index[lex_level - 1] ==
+ 			_state->path_level_index[lex_level - 1])
+ 		{
+ 			if (lex_level == _state->npath)
+ 				get_next = true;
+ 			else
+ 				_state->pathok[lex_level] = true;
+ 		}
+ 
+ 	}
+ 
+ 	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;
+ 
+ 	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;
+ 
+ 		_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)
+ 	{
+ 		_state->tresult = cstring_to_text(token);
+ 		_state->next_scalar = false;
+ 	}
+ 
+ }
+ 
+ /*
+  * SQL function json_array_length
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_array_length);
+ 
+ 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);
+ }
+ 
+ static void
+ alen_object_start(void *state)
+ {
+ 	AlenState	_state = (AlenState) state;
+ 
+ 	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;
+ 
+ 	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;
+ 
+ 	if (_state->lex->lex_level == 1)
+ 		_state->count++;
+ }
+ 
+ /*
+  * SQL function json_each
+  *
+  * decompose a json object into key value pairs.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_each);
+ 
+ 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.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_each_as_text);
+ 
+ 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];
+ 	static 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 (_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;
+ 
+ 	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;
+ 
+ 	if (_state->lex->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call json_each on a scalar")));
+ 
+ 	if (_state->next_scalar)
+ 		_state->normalized_scalar = token;
+ }
+ 
+ /*
+  * SQL function json_unnest
+  *
+  * get the elements from a json array
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_unnest);
+ 
+ Datum
+ json_unnest(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	JsonLexContext *lex = makeJsonLexContext(json, true);
+ 	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;
+ 
+ 	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;
+ 
+ 	if (_state->lex->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call json_unnest on a scalar")));
+ }
+ 
+ /*
+  * 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.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_populate_record);
+ 
+ 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.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_populate_recordset);
+ 
+ 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,87 ----
+ /*-------------------------------------------------------------------------
+  *
+  * jsonapi.h
+  *	  Declarations for JSON API support.
+  *
+  * Portions Copyright (c) 1996-2012, 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;
+ 
+ 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);
+ 
+ 
+ /*
+  * any of these actions can be NULL, in which case nothig is done.
+  */
+ 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 */
+ 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,813 ----
   {"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","three","four","five"]'),
+ ('object','{"field1":"val1","field2":"val2"}');
+ 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
+ (2 rows)
+ 
+ -- 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}');
+  json_each_as_text 
+ -------------------
+  (f1,"[1,2,3]")
+  (f2,"{""f3"":1}")
+  (f4,null)
+ (3 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  | null
+  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 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,262 ----
  -- 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","three","four","five"]'),
+ ('object','{"field1":"val1","field2":"val2"}');
+ 
+ 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';
+ 
+ -- 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}');
+ 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 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;
#23Robert Haas
robertmhaas@gmail.com
In reply to: Andrew Dunstan (#22)
Re: json api WIP patch

On Thu, Jan 10, 2013 at 6:42 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

Udated patch that contains most of the functionality I'm after. One piece
left is populate_recordset (populate a set of records from a single json
datum which is an array of objects, in one pass). That requires a bit of
thought.

I hope most of the whitespace issues are fixed.

This updated patch contains all the intended functionality, including
operators for the json_get_path functions, so you can say things like

select jsonval->array['f1','0','f2] ...

It also removes any requirement to copy the json value before setting up the
lexer by removing the lexer's requirement to have a nul terminated string.
Instead the lexer is told the input length and relies on that. For this
reason, json_in() now calls cstring_get_text() before rather than after
calling the validation routine, but that's really not something worth
bothering about.

A couple of points worth noting: it's a pity that we have to run CREATE OR
REPLACE FUNCTION in system_views.sql in order to set up default values for
builtin functions. That feels very kludgy. Also, making operators for
variadic functions is a bit of a pain. I had to set up non-variadic version
of the same functions (see json_get_path_op and json_get_path_as_text_op)
just so I could set up the operators. Neither of these are exactly
showstopper items, just mild annoyances.

I will continue hunting memory leaks, but when Merlin gets done with docco I
think we'll be far enough advanced to add this to the commitfest.

So, how much performance does this lose on json_in() on a large
cstring, as compared with master?

I can't shake the feeling that this is adding a LOT of unnecessary
data copying. For one thing, instead of copying every single lexeme
(including the single-character ones?) out of the original object, we
could just store a pointer to the offset where the object starts and a
length, instead of copying it.

This is also remarkably thin on comments.

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

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

#24Andrew Dunstan
andrew@dunslane.net
In reply to: Robert Haas (#23)
Re: json api WIP patch

On 01/14/2013 11:32 AM, Robert Haas wrote:

So, how much performance does this lose on json_in() on a large
cstring, as compared with master?

That's a good question. I'll try to devise a test.

I can't shake the feeling that this is adding a LOT of unnecessary
data copying. For one thing, instead of copying every single lexeme
(including the single-character ones?) out of the original object, we
could just store a pointer to the offset where the object starts and a
length, instead of copying it.

In the pure pares case (json_in, json_reccv) nothing extra should be
copied. On checking this after reading the above I found that wasn't
quite the case, and some lexemes (scalars and field names, but not
punctuation) were being copied when not needed. I have made a fix (see
<https://bitbucket.org/adunstan/pgdevel/commits/139043dba7e6b15f1f9f7675732bd9dae1fb6497&gt;)
which I will include in the next version I publish.

In the case of string lexemes, we are passing back a de-escaped version,
so just handing back pointers to the beginning and end in the input
string doesn't work.

This is also remarkably thin on comments.

Fair criticism. I'll work on that.

Thanks for looking at this.

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

#25Merlin Moncure
mmoncure@gmail.com
In reply to: Andrew Dunstan (#22)
Re: json api WIP patch

On Thu, Jan 10, 2013 at 5:42 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

On 01/04/2013 03:23 PM, Andrew Dunstan wrote:

On 01/02/2013 05:51 PM, Andrew Dunstan wrote:

On 01/02/2013 04:45 PM, Robert Haas wrote:

On Wed, Dec 26, 2012 at 3:33 PM, Andrew Dunstan <andrew@dunslane.net>
wrote:

Here is a patch for the first part of the JSON API that was recently
discussed. It includes the json parser hook infrastructure and
functions
for json_get and friends, plus json_keys.

Udated patch that contains most of the functionality I'm after. One piece
left is populate_recordset (populate a set of records from a single json
datum which is an array of objects, in one pass). That requires a bit of
thought.

I hope most of the whitespace issues are fixed.

This updated patch contains all the intended functionality, including
operators for the json_get_path functions, so you can say things like

select jsonval->array['f1','0','f2] ...

It also removes any requirement to copy the json value before setting up the
lexer by removing the lexer's requirement to have a nul terminated string.
Instead the lexer is told the input length and relies on that. For this
reason, json_in() now calls cstring_get_text() before rather than after
calling the validation routine, but that's really not something worth
bothering about.

A couple of points worth noting: it's a pity that we have to run CREATE OR
REPLACE FUNCTION in system_views.sql in order to set up default values for
builtin functions. That feels very kludgy. Also, making operators for
variadic functions is a bit of a pain. I had to set up non-variadic version
of the same functions (see json_get_path_op and json_get_path_as_text_op)
just so I could set up the operators. Neither of these are exactly
showstopper items, just mild annoyances.

I will continue hunting memory leaks, but when Merlin gets done with docco I
think we'll be far enough advanced to add this to the commitfest.

While testing this I noticed that integer based 'get' routines are
zero based -- was this intentional? Virtually all other aspects of
SQL are 1 based:

postgres=# select json_get('[1,2,3]', 1);
json_get
----------
2
(1 row)

postgres=# select json_get('[1,2,3]', 0);
json_get
----------
1
(1 row)

merlin

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

#26Andrew Dunstan
andrew@dunslane.net
In reply to: Merlin Moncure (#25)
Re: json api WIP patch

On 01/14/2013 07:36 PM, Merlin Moncure wrote:

While testing this I noticed that integer based 'get' routines are
zero based -- was this intentional? Virtually all other aspects of
SQL are 1 based:

postgres=# select json_get('[1,2,3]', 1);
json_get
----------
2
(1 row)

postgres=# select json_get('[1,2,3]', 0);
json_get
----------
1
(1 row)

Yes. it's intentional. SQL arrays might be 1-based by default, but
JavaScript arrays are not. JsonPath and similar gadgets treat the arrays
as zero-based. I suspect the Json-using community would not thank us for
being overly SQL-centric on this - and I say that as someone who has
always thought zero based arrays were a major design mistake,
responsible for countless off-by-one errors.

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

#27Andrew Dunstan
andrew@dunslane.net
In reply to: Andrew Dunstan (#24)
1 attachment(s)
Re: json api WIP patch

On 01/14/2013 12:52 PM, Andrew Dunstan wrote:

On 01/14/2013 11:32 AM, Robert Haas wrote:

So, how much performance does this lose on json_in() on a large
cstring, as compared with master?

That's a good question. I'll try to devise a test.

I can't shake the feeling that this is adding a LOT of unnecessary
data copying. For one thing, instead of copying every single lexeme
(including the single-character ones?) out of the original object, we
could just store a pointer to the offset where the object starts and a
length, instead of copying it.

In the pure pares case (json_in, json_reccv) nothing extra should be
copied. On checking this after reading the above I found that wasn't
quite the case, and some lexemes (scalars and field names, but not
punctuation) were being copied when not needed. I have made a fix (see
<https://bitbucket.org/adunstan/pgdevel/commits/139043dba7e6b15f1f9f7675732bd9dae1fb6497&gt;)
which I will include in the next version I publish.

In the case of string lexemes, we are passing back a de-escaped
version, so just handing back pointers to the beginning and end in the
input string doesn't work.

After a couple of iterations, some performance enhancements to the json
parser and lexer have ended up with a net performance improvement over
git tip. On our test rig, the json parse test runs at just over 13s per
10000 parses on git tip and approx 12.55s per 10000 parses with the
attached patch.

Truth be told, I think the lexer changes have more than paid for the
small cost of the switch to an RD parser. But since the result is a net
performance win PLUS some enhanced functionality, I think we should be
all good.

cheers

andrew

Attachments:

current_jsonapi.patchtext/x-patch; name=current_jsonapi.patchDownload
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 607a72f..0f51ba6 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -773,3 +773,11 @@ COMMENT ON FUNCTION ts_debug(text) IS
 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';
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index a929f4a..41a8982 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -19,8 +19,8 @@ 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 \
+	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 \
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index c700da4..098ca84 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -24,69 +24,98 @@
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/json.h"
+#include "utils/jsonapi.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 */
+/*
+ * 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 */
-} 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);
+	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 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);
+				  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);
+					   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 */
+
+static inline JsonTokenType
+lex_peek(JsonLexContext *lex)
+{
+	return lex->token_type;
+}
+
+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;
+}
+
+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,19 +129,22 @@ 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);
+	char	   *json = PG_GETARG_CSTRING(0);
+	text       *result = cstring_to_text(json);
+	JsonLexContext *lex;
 
-	json_validate_cstring(text);
+	/* validate it */
+	lex = makeJsonLexContext(result,false);
+	pg_parse_json(lex, NullSemAction);
 
 	/* Internal representation is the same as text, for now */
-	PG_RETURN_TEXT_P(cstring_to_text(text));
+	PG_RETURN_TEXT_P(result);
 }
 
 /*
@@ -151,293 +183,406 @@ json_recv(PG_FUNCTION_ARGS)
 	text	   *result;
 	char	   *str;
 	int			nbytes;
+	JsonLexContext *lex;
 
 	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);
+	result = palloc(nbytes + VARHDRSZ);
 	SET_VARSIZE(result, nbytes + VARHDRSZ);
 	memcpy(VARDATA(result), str, nbytes);
-	str = VARDATA(result);
-	str[nbytes] = '\0';
 
 	/* Validate it. */
-	json_validate_cstring(str);
+	lex = makeJsonLexContext(result, false);
+	pg_parse_json(lex, NullSemAction);
 
 	PG_RETURN_TEXT_P(result);
 }
 
 /*
- * Check whether supplied input is valid JSON.
+ * lex constructor, with or without StringInfo object
+ * for de-escaped lexemes.
  */
-static void
-json_validate_cstring(char *input)
+
+JsonLexContext *
+makeJsonLexContext(text *json, bool need_escapes)
 {
-	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 (;;)
+	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;
+}
+
+/*
+ * parse 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)
 	{
-		JsonStackOp op;
+		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 */
+	}
 
-		/* Fetch next token. */
-		json_lex(&lex);
+	lex_expect(JSON_PARSE_END, lex, JSON_TOKEN_END);
 
-		/* 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);
-		}
+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);
 
-		/* 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;
-		}
+	valaddr  = sfunc == NULL ? NULL : &val;
+
+	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);
 }
 
-/*
- * Lex one token from the input stream.
- */
 static void
-json_lex(JsonLexContext *lex)
+parse_object_field(JsonLexContext *lex, JsonSemAction sem)
 {
-	char	   *s;
+	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;
 
-	/* Skip leading whitespace. */
-	s = lex->token_terminator;
-	while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r')
-		s++;
-	lex->token_start = s;
+	if (ostart != NULL || oend != NULL)
+		fnameaddr = &fname;
 
-	/* Determine token type. */
-	if (strchr("{}[],:", s[0]) != NULL)
+	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)
 	{
-		/* 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;
+		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);
 	}
-	else if (*s == '"')
+
+	if (oend != NULL)
+		(*oend) (sem->semstate, fname, isnull);
+
+	if (fname != NULL)
+		pfree(fname);
+}
+
+static void
+parse_object(JsonLexContext *lex, JsonSemAction sem)
+{
+	json_struct_action ostart = sem->object_start;
+	json_struct_action oend = sem->object_end;
+	JsonTokenType tok;
+
+	if (ostart != NULL)
+		(*ostart) (sem->semstate);
+
+	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)
 	{
-		/* String. */
-		json_lex_string(lex);
-		lex->token_type = JSON_VALUE_STRING;
+		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);
 	}
-	else if (*s == '-')
+
+	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);
+
+	switch(tok)
 	{
-		/* Negative number. */
-		json_lex_number(lex, s + 1);
-		lex->token_type = JSON_VALUE_NUMBER;
+		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);
 	}
-	else if (*s >= '0' && *s <= '9')
+
+	if (aend != NULL)
+		(*aend) (sem->semstate, isnull);
+}
+
+static void
+parse_array(JsonLexContext *lex, JsonSemAction sem)
+{
+	json_struct_action astart = sem->array_start;
+	json_struct_action aend = sem->array_end;
+
+	if (astart != NULL)
+		(*astart) (sem->semstate);
+
+	lex->lex_level++;
+
+	lex_expect(JSON_PARSE_ARRAY_START, lex, JSON_TOKEN_ARRAY_START);
+	if (lex_peek(lex) != JSON_TOKEN_ARRAY_END)
 	{
-		/* Positive number. */
-		json_lex_number(lex, s);
-		lex->token_type = JSON_VALUE_NUMBER;
+
+		parse_array_element(lex, sem);
+
+		while (lex_accept(lex, JSON_TOKEN_COMMA, NULL))
+			parse_array_element(lex, sem);
 	}
-	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 */ ;
+	lex_expect(JSON_PARSE_ARRAY_NEXT, lex, JSON_TOKEN_ARRAY_END);
 
-		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);
-		}
+	lex->lex_level--;
 
-		/*
-		 * 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);
+	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 void
+static inline void
 json_lex_string(JsonLexContext *lex)
 {
 	char	   *s;
+	int         len;
+	if (lex->strval != NULL)
+		resetStringInfo(lex->strval);
 
-	for (s = lex->token_start + 1; *s != '"'; s++)
+	len = lex->token_start - lex->input;
+	len++;
+	for (s = lex->token_start + 1; *s != '"'; s++, len++)
 	{
-		/* Per RFC4627, these characters MUST be escaped. */
-		if ((unsigned char) *s < 32)
+		/* Premature end of the string. */
+		if (len >= lex->input_length)
 		{
-			/* A NUL byte marks the (premature) end of the string. */
-			if (*s == '\0')
-			{
-				lex->token_terminator = s;
-				report_invalid_token(lex);
-			}
+			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,7 +596,8 @@ json_lex_string(JsonLexContext *lex)
 		{
 			/* OK, we have an escape character. */
 			s++;
-			if (*s == '\0')
+			len++;
+			if (len >= lex->input_length)
 			{
 				lex->token_terminator = s;
 				report_invalid_token(lex);
@@ -464,7 +610,8 @@ json_lex_string(JsonLexContext *lex)
 				for (i = 1; i <= 4; i++)
 				{
 					s++;
-					if (*s == '\0')
+					len++;
+					if (len >= lex->input_length)
 					{
 						lex->token_terminator = s;
 						report_invalid_token(lex);
@@ -485,10 +632,62 @@ json_lex_string(JsonLexContext *lex)
 								 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)
 			{
-				/* Not a valid string escape, so error out. */
+				/*
+				 * Simpler processing if we're not bothered about de-escaping
+				 */
 				lex->token_terminator = s + pg_mblen(s);
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
@@ -497,10 +696,17 @@ json_lex_string(JsonLexContext *lex)
 								   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,67 +736,81 @@ json_lex_string(JsonLexContext *lex)
  *
  *-------------------------------------------------------------------------
  */
-static void
+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++;
-		} while (*s >= '0' && *s <= '9');
+			len++;
+		} while (*s >= '0' && *s <= '9' && len < lex->input_length);
 	}
 	else
 		error = true;
 
 	/* Part (3): parse optional decimal portion. */
-	if (*s == '.')
+	if (len < lex->input_length && *s == '.')
 	{
 		s++;
-		if (*s < '0' || *s > '9')
+		len++;
+		if (len == lex->input_length || *s < '0' || *s > '9')
 			error = true;
 		else
 		{
 			do
 			{
 				s++;
-			} while (*s >= '0' && *s <= '9');
+				len++;
+			} while (*s >= '0' && *s <= '9' && len < lex->input_length);
 		}
 	}
 
 	/* Part (4): parse optional exponent. */
-	if (*s == 'e' || *s == 'E')
+	if (len < lex->input_length && (*s == 'e' || *s == 'E'))
 	{
 		s++;
-		if (*s == '+' || *s == '-')
+		len++;
+		if (len < lex->input_length && (*s == '+' || *s == '-'))
+		{
 			s++;
-		if (*s < '0' || *s > '9')
+			len++;
+		}
+		if (len == lex->input_length || *s < '0' || *s > '9')
 			error = true;
 		else
 		{
 			do
 			{
 				s++;
-			} while (*s >= '0' && *s <= '9');
+				len++;
+			} while (len < lex->input_length && *s >= '0' && *s <= '9');
 		}
 	}
 
 	/*
-	 * Check for trailing garbage.  As in json_lex(), any alphanumeric stuff
+	 * 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++)
+	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,13 +822,13 @@ 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)
+report_parse_error(JsonParseContext ctx, JsonLexContext *lex)
 {
 	char	   *token;
 	int			toklen;
 
 	/* Handle case where the input ended prematurely. */
-	if (lex->token_start == NULL)
+	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,7 +842,7 @@ report_parse_error(JsonParseStack *stack, JsonLexContext *lex)
 	token[toklen] = '\0';
 
 	/* Complain, with the appropriate detail message. */
-	if (stack == NULL)
+	if (ctx == JSON_PARSE_END)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
 				 errmsg("invalid input syntax for type json"),
@@ -631,7 +851,7 @@ report_parse_error(JsonParseStack *stack, JsonLexContext *lex)
 				 report_json_context(lex)));
 	else
 	{
-		switch (stack->state)
+		switch (ctx)
 		{
 			case JSON_PARSE_VALUE:
 				ereport(ERROR,
@@ -641,6 +861,14 @@ report_parse_error(JsonParseStack *stack, JsonLexContext *lex)
 								   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,16 +881,16 @@ 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),
+					  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),
+					 errdetail("Expected string or \"}\", but found \"%s\".",
+							   token),
 						 report_json_context(lex)));
 				break;
 			case JSON_PARSE_OBJECT_LABEL:
@@ -677,8 +905,8 @@ 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),
+					  errdetail("Expected \",\" or \"}\", but found \"%s\".",
+								token),
 						 report_json_context(lex)));
 				break;
 			case JSON_PARSE_OBJECT_COMMA:
@@ -690,8 +918,7 @@ report_parse_error(JsonParseStack *stack, JsonLexContext *lex)
 						 report_json_context(lex)));
 				break;
 			default:
-				elog(ERROR, "unexpected json parse state: %d",
-					 (int) stack->state);
+				elog(ERROR, "unexpected json parse state: %d", ctx);
 		}
 	}
 }
@@ -786,7 +1013,7 @@ 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') ? "..." : "";
+	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);
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
new file mode 100644
index 0000000..81d481e
--- /dev/null
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -0,0 +1,1913 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonfuncs.c
+ *		Functions to process JSON data type.
+ *
+ * Portions Copyright (c) 1996-2012, 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.
+ */
+
+PG_FUNCTION_INFO_V1(json_object_keys);
+
+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;
+
+	if (_state->lex->lex_level != 1)
+		return;
+	if (_state->result_count >= _state->result_size)
+	{
+		_state->result_size *= 2;
+		_state->result =
+			repalloc(_state->result, sizeof(char *) * _state->result_size);
+	}
+	_state->result[_state->result_count++] = pstrdup(fname);
+}
+
+static void
+okeys_array_start(void *state)
+{
+	OkeysState	_state = (OkeysState) state;
+
+	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;
+
+	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.
+ */
+
+PG_FUNCTION_INFO_V1(json_get_ofield);
+
+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();
+}
+
+PG_FUNCTION_INFO_V1(json_get_ofield_as_text);
+
+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();
+}
+
+PG_FUNCTION_INFO_V1(json_get_aelem);
+
+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();
+}
+
+
+PG_FUNCTION_INFO_V1(json_get_aelem_as_text);
+
+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();
+}
+
+
+PG_FUNCTION_INFO_V1(json_get_path);
+
+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();
+}
+
+PG_FUNCTION_INFO_V1(json_get_path_as_text);
+
+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();
+}
+
+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;
+
+	state = palloc0(sizeof(getState));
+	sem = palloc0(sizeof(jsonSemAction));
+
+	state->lex = lex;
+	state->normalize_results = normalize_results;
+	if (field != NULL)
+	{
+		state->search_type = JSON_SEARCH_OBJECT;
+		state->search_term = field;
+	}
+	else if (path != NULL)
+	{
+		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);
+		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
+	{
+		state->search_type = JSON_SEARCH_ARRAY;
+		state->search_index = elem_index;
+		state->array_index = -1;
+	}
+
+	sem->semstate = (void *) state;
+	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;
+
+	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)
+	{
+		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)
+	{
+		if (lex_level < _state->npath)
+			_state->pathok[lex_level] = true;
+
+		if (lex_level == _state->npath)
+			get_next = true;
+	}
+
+	if (get_next)
+	{
+		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)
+		{
+			_state->next_scalar = true;
+		}
+		else
+		{
+			_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;
+
+	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;
+	}
+
+	if (get_last && _state->result_start != NULL)
+	{
+		int			len = _state->lex->prev_token_terminator - _state->result_start;
+
+		_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;
+
+	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")));
+	else 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)
+	{
+		_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])
+	{
+		if (++_state->array_level_index[lex_level - 1] ==
+			_state->path_level_index[lex_level - 1])
+		{
+			if (lex_level == _state->npath)
+				get_next = true;
+			else
+				_state->pathok[lex_level] = true;
+		}
+
+	}
+
+	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;
+
+	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;
+
+		_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)
+	{
+		_state->tresult = cstring_to_text(token);
+		_state->next_scalar = false;
+	}
+
+}
+
+/*
+ * SQL function json_array_length
+ */
+
+PG_FUNCTION_INFO_V1(json_array_length);
+
+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);
+}
+
+static void
+alen_object_start(void *state)
+{
+	AlenState	_state = (AlenState) state;
+
+	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;
+
+	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;
+
+	if (_state->lex->lex_level == 1)
+		_state->count++;
+}
+
+/*
+ * SQL function json_each
+ *
+ * decompose a json object into key value pairs.
+ */
+
+PG_FUNCTION_INFO_V1(json_each);
+
+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.
+ */
+
+PG_FUNCTION_INFO_V1(json_each_as_text);
+
+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];
+	static 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 (_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;
+
+	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;
+
+	if (_state->lex->lex_level == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("cannot call json_each on a scalar")));
+
+	if (_state->next_scalar)
+		_state->normalized_scalar = token;
+}
+
+/*
+ * SQL function json_unnest
+ *
+ * get the elements from a json array
+ */
+
+PG_FUNCTION_INFO_V1(json_unnest);
+
+Datum
+json_unnest(PG_FUNCTION_ARGS)
+{
+	text	   *json = PG_GETARG_TEXT_P(0);
+	JsonLexContext *lex = makeJsonLexContext(json, true);
+	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;
+
+	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;
+
+	if (_state->lex->lex_level == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("cannot call json_unnest on a scalar")));
+}
+
+/*
+ * 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.
+ */
+
+PG_FUNCTION_INFO_V1(json_populate_record);
+
+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.
+ */
+
+PG_FUNCTION_INFO_V1(json_populate_recordset);
+
+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;
+	}
+}
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index abfec5c..82231e1 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -1724,6 +1724,20 @@ DESCR("range difference");
 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
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index d1b22d1..faacdfb 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4103,6 +4103,37 @@ DESCR("map row to json");
 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");
diff --git a/src/include/utils/json.h b/src/include/utils/json.h
index 0f38147..cf8c7c1 100644
--- a/src/include/utils/json.h
+++ b/src/include/utils/json.h
@@ -17,6 +17,7 @@
 #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,4 +28,19 @@ extern Datum row_to_json(PG_FUNCTION_ARGS);
 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 */
diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h
new file mode 100644
index 0000000..9944960
--- /dev/null
+++ b/src/include/utils/jsonapi.h
@@ -0,0 +1,87 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonapi.h
+ *	  Declarations for JSON API support.
+ *
+ * Portions Copyright (c) 1996-2012, 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;
+
+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);
+
+
+/*
+ * any of these actions can be NULL, in which case nothig is done.
+ */
+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 */
+extern JsonLexContext *makeJsonLexContext(text *json, bool need_escapes);
+
+#endif   /* JSONAPI_H */
diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out
index 2dfe7bb..47b3386 100644
--- a/src/test/regress/expected/json.out
+++ b/src/test/regress/expected/json.out
@@ -433,3 +433,381 @@ FROM (SELECT '{"a":1,"b": [2,3,4,"d","e","f"],"c":{"p":1,"q":2}}'::json AS "json
  {"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","three","four","five"]'),
+('object','{"field1":"val1","field2":"val2"}');
+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
+(2 rows)
+
+-- 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}');
+ json_each_as_text 
+-------------------
+ (f1,"[1,2,3]")
+ (f2,"{""f3"":1}")
+ (f4,null)
+(3 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  | null
+ 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 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
diff --git a/src/test/regress/sql/json.sql b/src/test/regress/sql/json.sql
index 52be0cf..7878cfd 100644
--- a/src/test/regress/sql/json.sql
+++ b/src/test/regress/sql/json.sql
@@ -113,3 +113,150 @@ FROM (SELECT '-Infinity'::float8 AS "float8field") q;
 -- 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","three","four","five"]'),
+('object','{"field1":"val1","field2":"val2"}');
+
+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';
+
+-- 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}');
+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 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;
#28Andrew Dunstan
andrew@dunslane.net
In reply to: Andrew Dunstan (#27)
1 attachment(s)
Re: json api WIP patch

On 01/14/2013 11:02 PM, Andrew Dunstan wrote:

On 01/14/2013 12:52 PM, Andrew Dunstan wrote:

On 01/14/2013 11:32 AM, Robert Haas wrote:

So, how much performance does this lose on json_in() on a large
cstring, as compared with master?

That's a good question. I'll try to devise a test.

I can't shake the feeling that this is adding a LOT of unnecessary
data copying. For one thing, instead of copying every single lexeme
(including the single-character ones?) out of the original object, we
could just store a pointer to the offset where the object starts and a
length, instead of copying it.

In the pure pares case (json_in, json_reccv) nothing extra should be
copied. On checking this after reading the above I found that wasn't
quite the case, and some lexemes (scalars and field names, but not
punctuation) were being copied when not needed. I have made a fix
(see
<https://bitbucket.org/adunstan/pgdevel/commits/139043dba7e6b15f1f9f7675732bd9dae1fb6497&gt;)
which I will include in the next version I publish.

In the case of string lexemes, we are passing back a de-escaped
version, so just handing back pointers to the beginning and end in
the input string doesn't work.

After a couple of iterations, some performance enhancements to the
json parser and lexer have ended up with a net performance improvement
over git tip. On our test rig, the json parse test runs at just over
13s per 10000 parses on git tip and approx 12.55s per 10000 parses
with the attached patch.

Truth be told, I think the lexer changes have more than paid for the
small cost of the switch to an RD parser. But since the result is a
net performance win PLUS some enhanced functionality, I think we
should be all good.

Latest version of this patch, including some documentation, mainly from
Merlin Moncure but tweaked by me.

cheers

andrew

Attachments:

jsonapi5.patchxtext/plain; charset=UTF-8; name=jsonapi5.patchxDownload
*** 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,121 ----
  #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 */
! 
! static inline JsonTokenType
! lex_peek(JsonLexContext *lex)
! {
! 	return lex->token_type;
! }
! 
! 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;
! }
! 
! 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));
  }
  
  /*
--- 129,150 ----
  	 (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,
--- 183,593 ----
  	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);
  }
  
  /*
!  * lex constructor, with or without StringInfo object
!  * for de-escaped lexemes.
!  */
! 
! 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;
! }
! 
! /*
!  * parse 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);
+ 
+ }
+ 
+ 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;
+ 
+ 	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)
  {
! 	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)
! {
! 	json_struct_action ostart = sem->object_start;
! 	json_struct_action oend = sem->object_end;
! 	JsonTokenType tok;
  
! 	if (ostart != NULL)
! 		(*ostart) (sem->semstate);
! 
! 	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);
+ 
+ 	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)
+ {
+ 	json_struct_action astart = sem->array_start;
+ 	json_struct_action aend = sem->array_end;
+ 
+ 	if (astart != NULL)
+ 		(*astart) (sem->semstate);
+ 
+ 	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);
--- 601,608 ----
  		{
  			/* 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);
--- 615,622 ----
  				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),
--- 637,698 ----
  								 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
! 				 */
  				lex->token_terminator = s + pg_mblen(s);
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
***************
*** 497,506 **** json_lex_string(JsonLexContext *lex)
--- 701,717 ----
  								   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);
--- 741,821 ----
   *
   *-------------------------------------------------------------------------
   */
! 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"),
--- 827,839 ----
   * 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"),
--- 847,853 ----
  	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,
--- 856,862 ----
  				 report_json_context(lex)));
  	else
  	{
! 		switch (ctx)
  		{
  			case JSON_PARSE_VALUE:
  				ereport(ERROR,
***************
*** 641,646 **** report_parse_error(JsonParseStack *stack, JsonLexContext *lex)
--- 866,879 ----
  								   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:
--- 886,901 ----
  				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:
--- 910,917 ----
  				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);
  		}
  	}
  }
--- 923,929 ----
  						 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);
--- 1018,1024 ----
  	 * 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,1915 ----
+ /*-------------------------------------------------------------------------
+  *
+  * jsonfuncs.c
+  *		Functions to process JSON data type.
+  *
+  * Portions Copyright (c) 1996-2012, 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.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_object_keys);
+ 
+ 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;
+ 
+ 	if (_state->lex->lex_level != 1)
+ 		return;
+ 	if (_state->result_count >= _state->result_size)
+ 	{
+ 		_state->result_size *= 2;
+ 		_state->result =
+ 			repalloc(_state->result, sizeof(char *) * _state->result_size);
+ 	}
+ 	_state->result[_state->result_count++] = pstrdup(fname);
+ }
+ 
+ static void
+ okeys_array_start(void *state)
+ {
+ 	OkeysState	_state = (OkeysState) state;
+ 
+ 	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;
+ 
+ 	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.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_get_ofield);
+ 
+ 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();
+ }
+ 
+ PG_FUNCTION_INFO_V1(json_get_ofield_as_text);
+ 
+ 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();
+ }
+ 
+ PG_FUNCTION_INFO_V1(json_get_aelem);
+ 
+ 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();
+ }
+ 
+ 
+ PG_FUNCTION_INFO_V1(json_get_aelem_as_text);
+ 
+ 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();
+ }
+ 
+ 
+ PG_FUNCTION_INFO_V1(json_get_path);
+ 
+ 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();
+ }
+ 
+ PG_FUNCTION_INFO_V1(json_get_path_as_text);
+ 
+ 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();
+ }
+ 
+ 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;
+ 
+ 	state = palloc0(sizeof(getState));
+ 	sem = palloc0(sizeof(jsonSemAction));
+ 
+ 	state->lex = lex;
+ 	state->normalize_results = normalize_results;
+ 	if (field != NULL)
+ 	{
+ 		state->search_type = JSON_SEARCH_OBJECT;
+ 		state->search_term = field;
+ 	}
+ 	else if (path != NULL)
+ 	{
+ 		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);
+ 		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
+ 	{
+ 		state->search_type = JSON_SEARCH_ARRAY;
+ 		state->search_index = elem_index;
+ 		state->array_index = -1;
+ 	}
+ 
+ 	sem->semstate = (void *) state;
+ 	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;
+ 
+ 	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)
+ 	{
+ 		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)
+ 	{
+ 		if (lex_level < _state->npath)
+ 			_state->pathok[lex_level] = true;
+ 
+ 		if (lex_level == _state->npath)
+ 			get_next = true;
+ 	}
+ 
+ 	if (get_next)
+ 	{
+ 		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)
+ 		{
+ 			_state->next_scalar = true;
+ 		}
+ 		else
+ 		{
+ 			_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;
+ 
+ 	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;
+ 	}
+ 
+ 	if (get_last && _state->result_start != NULL)
+ 	{
+ 		int			len = _state->lex->prev_token_terminator - _state->result_start;
+ 
+ 		_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;
+ 
+ 	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")));
+ 	else 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)
+ 	{
+ 		_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])
+ 	{
+ 		if (++_state->array_level_index[lex_level - 1] ==
+ 			_state->path_level_index[lex_level - 1])
+ 		{
+ 			if (lex_level == _state->npath)
+ 				get_next = true;
+ 			else
+ 				_state->pathok[lex_level] = true;
+ 		}
+ 
+ 	}
+ 
+ 	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;
+ 
+ 	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;
+ 
+ 		_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)
+ 	{
+ 		_state->tresult = cstring_to_text(token);
+ 		_state->next_scalar = false;
+ 	}
+ 
+ }
+ 
+ /*
+  * SQL function json_array_length
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_array_length);
+ 
+ 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);
+ }
+ 
+ static void
+ alen_object_start(void *state)
+ {
+ 	AlenState	_state = (AlenState) state;
+ 
+ 	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;
+ 
+ 	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;
+ 
+ 	if (_state->lex->lex_level == 1)
+ 		_state->count++;
+ }
+ 
+ /*
+  * SQL function json_each
+  *
+  * decompose a json object into key value pairs.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_each);
+ 
+ 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.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_each_as_text);
+ 
+ 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];
+ 	static 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 (_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;
+ 
+ 	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;
+ 
+ 	if (_state->lex->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call json_each on a scalar")));
+ 
+ 	if (_state->next_scalar)
+ 		_state->normalized_scalar = token;
+ }
+ 
+ /*
+  * SQL function json_unnest
+  *
+  * get the elements from a json array
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_unnest);
+ 
+ Datum
+ json_unnest(PG_FUNCTION_ARGS)
+ {
+ 	text	   *json = PG_GETARG_TEXT_P(0);
+ 	JsonLexContext *lex = makeJsonLexContext(json, true);
+ 	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;
+ 
+ 	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;
+ 
+ 	if (_state->lex->lex_level == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("cannot call json_unnest on a scalar")));
+ }
+ 
+ /*
+  * 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.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_populate_record);
+ 
+ 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.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_populate_recordset);
+ 
+ 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,87 ----
+ /*-------------------------------------------------------------------------
+  *
+  * jsonapi.h
+  *	  Declarations for JSON API support.
+  *
+  * Portions Copyright (c) 1996-2012, 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;
+ 
+ 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);
+ 
+ 
+ /*
+  * any of these actions can be NULL, in which case nothig is done.
+  */
+ 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 */
+ 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,813 ----
   {"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","three","four","five"]'),
+ ('object','{"field1":"val1","field2":"val2"}');
+ 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
+ (2 rows)
+ 
+ -- 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}');
+  json_each_as_text 
+ -------------------
+  (f1,"[1,2,3]")
+  (f2,"{""f3"":1}")
+  (f4,null)
+ (3 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  | null
+  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 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,262 ----
  -- 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","three","four","five"]'),
+ ('object','{"field1":"val1","field2":"val2"}');
+ 
+ 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';
+ 
+ -- 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}');
+ 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 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;
#29David Fetter
david@fetter.org
In reply to: Andrew Dunstan (#26)
Re: json api WIP patch

On Mon, Jan 14, 2013 at 07:52:56PM -0500, Andrew Dunstan wrote:

On 01/14/2013 07:36 PM, Merlin Moncure wrote:

While testing this I noticed that integer based 'get' routines are
zero based -- was this intentional? Virtually all other aspects of
SQL are 1 based:

postgres=# select json_get('[1,2,3]', 1);
json_get
----------
2
(1 row)

postgres=# select json_get('[1,2,3]', 0);
json_get
----------
1
(1 row)

Yes. it's intentional. SQL arrays might be 1-based by default, but
JavaScript arrays are not. JsonPath and similar gadgets treat the
arrays as zero-based. I suspect the Json-using community would not
thank us for being overly SQL-centric on this - and I say that as
someone who has always thought zero based arrays were a major design
mistake, responsible for countless off-by-one errors.

Perhaps we could compromise by making arrays 0.5-based.

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

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

#30Merlin Moncure
mmoncure@gmail.com
In reply to: David Fetter (#29)
Re: json api WIP patch

On Tue, Jan 15, 2013 at 1:04 PM, David Fetter <david@fetter.org> wrote:

On Mon, Jan 14, 2013 at 07:52:56PM -0500, Andrew Dunstan wrote:

On 01/14/2013 07:36 PM, Merlin Moncure wrote:

While testing this I noticed that integer based 'get' routines are
zero based -- was this intentional? Virtually all other aspects of
SQL are 1 based:

postgres=# select json_get('[1,2,3]', 1);
json_get
----------
2
(1 row)

postgres=# select json_get('[1,2,3]', 0);
json_get
----------
1
(1 row)

Yes. it's intentional. SQL arrays might be 1-based by default, but
JavaScript arrays are not. JsonPath and similar gadgets treat the
arrays as zero-based. I suspect the Json-using community would not
thank us for being overly SQL-centric on this - and I say that as
someone who has always thought zero based arrays were a major design
mistake, responsible for countless off-by-one errors.

Perhaps we could compromise by making arrays 0.5-based.

Well, I'm not prepared to argue with Andrew in this one. It was
surprising behavior to me, but that's sample size one.

merlin

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

#31Andrew Dunstan
andrew@dunslane.net
In reply to: Merlin Moncure (#30)
Re: json api WIP patch

On 01/15/2013 02:47 PM, Merlin Moncure wrote:

On Tue, Jan 15, 2013 at 1:04 PM, David Fetter <david@fetter.org> wrote:

On Mon, Jan 14, 2013 at 07:52:56PM -0500, Andrew Dunstan wrote:

On 01/14/2013 07:36 PM, Merlin Moncure wrote:

While testing this I noticed that integer based 'get' routines are
zero based -- was this intentional? Virtually all other aspects of
SQL are 1 based:

postgres=# select json_get('[1,2,3]', 1);
json_get
----------
2
(1 row)

postgres=# select json_get('[1,2,3]', 0);
json_get
----------
1
(1 row)

Yes. it's intentional. SQL arrays might be 1-based by default, but
JavaScript arrays are not. JsonPath and similar gadgets treat the
arrays as zero-based. I suspect the Json-using community would not
thank us for being overly SQL-centric on this - and I say that as
someone who has always thought zero based arrays were a major design
mistake, responsible for countless off-by-one errors.

Perhaps we could compromise by making arrays 0.5-based.

Well, I'm not prepared to argue with Andrew in this one. It was
surprising behavior to me, but that's sample size one.

I doubt I'm very representative either. People like David Wheeler, Taras
Mitran, Joe Van Dyk, and the Heroku guys would be better people to ask
than me. I'm quite prepared to change it if that's the consensus.

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

#32Daniel Farina
daniel@heroku.com
In reply to: Andrew Dunstan (#31)
Re: json api WIP patch

On Tue, Jan 15, 2013 at 12:17 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

On 01/15/2013 02:47 PM, Merlin Moncure wrote:

On Tue, Jan 15, 2013 at 1:04 PM, David Fetter <david@fetter.org> wrote:

On Mon, Jan 14, 2013 at 07:52:56PM -0500, Andrew Dunstan wrote:

On 01/14/2013 07:36 PM, Merlin Moncure wrote:

While testing this I noticed that integer based 'get' routines are
zero based -- was this intentional? Virtually all other aspects of
SQL are 1 based:

postgres=# select json_get('[1,2,3]', 1);
json_get
----------
2
(1 row)

postgres=# select json_get('[1,2,3]', 0);
json_get
----------
1
(1 row)

Yes. it's intentional. SQL arrays might be 1-based by default, but
JavaScript arrays are not. JsonPath and similar gadgets treat the
arrays as zero-based. I suspect the Json-using community would not
thank us for being overly SQL-centric on this - and I say that as
someone who has always thought zero based arrays were a major design
mistake, responsible for countless off-by-one errors.

Perhaps we could compromise by making arrays 0.5-based.

Well, I'm not prepared to argue with Andrew in this one. It was
surprising behavior to me, but that's sample size one.

I doubt I'm very representative either. People like David Wheeler, Taras
Mitran, Joe Van Dyk, and the Heroku guys would be better people to ask than
me. I'm quite prepared to change it if that's the consensus.

Hello.

I'm inclined to go with the same gut feeling you had (zero-based-indexing).

Here is the background for my reasoning:

The downside of zero-based-indexing is that people who want to use
multiple sequential container types will inevitably have to deal with
detailed and not easily type-checked integer coordinates that mean
different things in each domain that will, no doubt, lead to a number
of off-by-one errors. Nevertheless, this cost is already paid because
one of the first things many people will do in programs generating SQL
queries is try to zero-index a SQL array, swear a bit after figuring
things out (because a NULL will be generated, not an error), and then
adjust all the offsets. So, this is not a new problem. On many
occasions I'm sure this has caused off-by-one bugs, or the NULLs
slipped through testing and delivered funny results, yet the world
moves on.

On the other hand, the downside of going down the road of 1-based
indexing and attempting to attain relative sameness to SQL arrays, it
would also feel like one would be obliged to implement SQL array
infelicities like 'out of bounds' being SQL NULL rather than an error,
related to other spectres like non-rectangular nested arrays. SQL
array semantics are complex and The Committee can change them or --
slightly more likely -- add interactions, so it seems like a general
expectation that Postgres container types that happen to have any
reasonable ordinal addressing will implement some level of same-ness
with SQL arrays is a very messy one. As such, if it becomes customary
to implement one-based indexing of containers, I think such customs
are best carefully circumscribed so that attempts to be 'like' SQL
arrays are only as superficial as that.

What made me come down on the side of zero-based indexing in spite of
the weaknesses are these two reasons:

* The number of people who use JSON and zero-based-indexing is very
large, and furthermore, within that set the number that know that
SQL even defines array support -- much less that Postgres implements
it -- is much smaller. Thus, one is targeting cohesion with a fairly
alien concept that is not understood by the audience.

* Maintaining PL integrated code that uses both 1-based indexing in PG
functions and 0-based indexing in embedded languages that are likely
to be combined with JSON -- doesn't sound very palatable, and the
use of such PLs (e.g. plv8) seems pretty likely, too. That can
probably be a rich source of bugs and frustration.

If one wants SQL array semantics, it seems like the right way to get
them is coercion to a SQL array value. Then one will receive SQL
array semantics exactly.

--
fdr

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

#33Gavin Flower
GavinFlower@archidevsys.co.nz
In reply to: David Fetter (#29)
Re: json api WIP patch

On 16/01/13 08:04, David Fetter wrote:

On Mon, Jan 14, 2013 at 07:52:56PM -0500, Andrew Dunstan wrote:

On 01/14/2013 07:36 PM, Merlin Moncure wrote:

While testing this I noticed that integer based 'get' routines are
zero based -- was this intentional? Virtually all other aspects of
SQL are 1 based:

postgres=# select json_get('[1,2,3]', 1);
json_get
----------
2
(1 row)

postgres=# select json_get('[1,2,3]', 0);
json_get
----------
1
(1 row)

Yes. it's intentional. SQL arrays might be 1-based by default, but
JavaScript arrays are not. JsonPath and similar gadgets treat the
arrays as zero-based. I suspect the Json-using community would not
thank us for being overly SQL-centric on this - and I say that as
someone who has always thought zero based arrays were a major design
mistake, responsible for countless off-by-one errors.

Perhaps we could compromise by making arrays 0.5-based.

Cheers,
David.

I think that is far to rational, perhaps the reciprocal of the golden
ratio(0.618033...) would be more appropriate?

I used to be insistent that arrays should start with 1, now I find
starting at 0 far more natural - because evrytime you start an array at
1, the computer has to subtract 1 in order to calculate the entry. Also
both Java & C are zero based.

I first learnt FORTRAN IV which is 1 based, had a shock when I was
learning Algol and found it was 0 based - many moons ago...

Cheers,
Gavin

#34David E. Wheeler
david.wheeler@pgexperts.com
In reply to: Andrew Dunstan (#31)
Re: json api WIP patch

On Jan 15, 2013, at 12:17 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

I doubt I'm very representative either. People like David Wheeler, Taras Mitran, Joe Van Dyk, and the Heroku guys would be better people to ask than me. I'm quite prepared to change it if that's the consensus.

They’re JSON arrays, not SQL arrays, and JSON arrays are based on JavaScript, where they are 0-based.

Best,

David

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

#35Andrew Dunstan
andrew@dunslane.net
In reply to: Andrew Dunstan (#28)
1 attachment(s)
Re: json api WIP patch

On 01/15/2013 11:31 AM, Andrew Dunstan wrote:

On 01/14/2013 11:02 PM, Andrew Dunstan wrote:

On 01/14/2013 12:52 PM, Andrew Dunstan wrote:

On 01/14/2013 11:32 AM, Robert Haas wrote:

So, how much performance does this lose on json_in() on a large
cstring, as compared with master?

That's a good question. I'll try to devise a test.

I can't shake the feeling that this is adding a LOT of unnecessary
data copying. For one thing, instead of copying every single lexeme
(including the single-character ones?) out of the original object, we
could just store a pointer to the offset where the object starts and a
length, instead of copying it.

In the pure pares case (json_in, json_reccv) nothing extra should be
copied. On checking this after reading the above I found that wasn't
quite the case, and some lexemes (scalars and field names, but not
punctuation) were being copied when not needed. I have made a fix
(see
<https://bitbucket.org/adunstan/pgdevel/commits/139043dba7e6b15f1f9f7675732bd9dae1fb6497&gt;)
which I will include in the next version I publish.

In the case of string lexemes, we are passing back a de-escaped
version, so just handing back pointers to the beginning and end in
the input string doesn't work.

After a couple of iterations, some performance enhancements to the
json parser and lexer have ended up with a net performance
improvement over git tip. On our test rig, the json parse test runs
at just over 13s per 10000 parses on git tip and approx 12.55s per
10000 parses with the attached patch.

Truth be told, I think the lexer changes have more than paid for the
small cost of the switch to an RD parser. But since the result is a
net performance win PLUS some enhanced functionality, I think we
should be all good.

Latest version of this patch, including some documentation, mainly
from Merlin Moncure but tweaked by me.

Now with more comments.

cheers

andrew

Attachments:

jsonapi6.patchtext/x-patch; name=jsonapi6.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,2089 ----
+ /*-------------------------------------------------------------------------
+  *
+  * 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.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_object_keys);
+ 
+ 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
+  */
+ PG_FUNCTION_INFO_V1(json_get_ofield);
+ 
+ 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
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_get_ofield_as_text);
+ 
+ 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
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_get_aelem);
+ 
+ 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
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_get_aelem_as_text);
+ 
+ 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.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_get_path);
+ 
+ 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.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_get_path_as_text);
+ 
+ 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;
+ 
+ 		_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;
+ 
+ 		_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
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_array_length);
+ 
+ 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 on 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.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_each);
+ 
+ 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
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_each_as_text);
+ 
+ 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];
+ 	static 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 (_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
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_unnest);
+ 
+ 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.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_populate_record);
+ 
+ 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.
+  */
+ 
+ PG_FUNCTION_INFO_V1(json_populate_recordset);
+ 
+ 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,813 ----
   {"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","three","four","five"]'),
+ ('object','{"field1":"val1","field2":"val2"}');
+ 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
+ (2 rows)
+ 
+ -- 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}');
+  json_each_as_text 
+ -------------------
+  (f1,"[1,2,3]")
+  (f2,"{""f3"":1}")
+  (f4,null)
+ (3 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  | null
+  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 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,262 ----
  -- 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","three","four","five"]'),
+ ('object','{"field1":"val1","field2":"val2"}');
+ 
+ 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';
+ 
+ -- 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}');
+ 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 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;
#36Robert Haas
robertmhaas@gmail.com
In reply to: Andrew Dunstan (#27)
Re: json api WIP patch

On Mon, Jan 14, 2013 at 11:02 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

After a couple of iterations, some performance enhancements to the json
parser and lexer have ended up with a net performance improvement over git
tip. On our test rig, the json parse test runs at just over 13s per 10000
parses on git tip and approx 12.55s per 10000 parses with the attached
patch.

Truth be told, I think the lexer changes have more than paid for the small
cost of the switch to an RD parser. But since the result is a net
performance win PLUS some enhanced functionality, I think we should be all
good.

Yeah, that sounds great. Thanks for putting in the effort.

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

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

#37Andrew Dunstan
andrew@dunslane.net
In reply to: Andrew Dunstan (#35)
1 attachment(s)
Re: json api WIP patch

On 01/15/2013 05:45 PM, Andrew Dunstan wrote:

Latest version of this patch, including some documentation, mainly
from Merlin Moncure but tweaked by me.

Now with more comments.

New version attached. The only change is to remove some unnecessary uses
of PG_FUNCTION_INFO_V1 that were the result of overenthusiastic copy and
paste.

cheers

andrew

Attachments:

jsonapi7.patchtext/x-patch; name=jsonapi7.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,2052 ----
+ /*-------------------------------------------------------------------------
+  *
+  * 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;
+ 
+ 		_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;
+ 
+ 		_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];
+ 	static 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 (_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,813 ----
   {"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","three","four","five"]'),
+ ('object','{"field1":"val1","field2":"val2"}');
+ 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
+ (2 rows)
+ 
+ -- 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}');
+  json_each_as_text 
+ -------------------
+  (f1,"[1,2,3]")
+  (f2,"{""f3"":1}")
+  (f4,null)
+ (3 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  | null
+  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 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,262 ----
  -- 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","three","four","five"]'),
+ ('object','{"field1":"val1","field2":"val2"}');
+ 
+ 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';
+ 
+ -- 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}');
+ 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 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;
#38Peter Eisentraut
peter_e@gmx.net
In reply to: Andrew Dunstan (#22)
Re: json api WIP patch

On 1/10/13 6:42 PM, Andrew Dunstan wrote:

This updated patch contains all the intended functionality, including
operators for the json_get_path functions, so you can say things like

select jsonval->array['f1','0','f2] ...

I would like to not create any -> operators, so that that syntax could
be used in the future for method invocation or something similar (it's
in the SQL standard).

I also don't find the proposed use to be very intuitive. You invented
lots of other function names -- why not invent a few more for this
purpose that are clearer?

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

#39Andrew Dunstan
andrew@dunslane.net
In reply to: Peter Eisentraut (#38)
Re: json api WIP patch

On 01/31/2013 05:06 PM, Peter Eisentraut wrote:

On 1/10/13 6:42 PM, Andrew Dunstan wrote:

This updated patch contains all the intended functionality, including
operators for the json_get_path functions, so you can say things like

select jsonval->array['f1','0','f2] ...

I would like to not create any -> operators, so that that syntax could
be used in the future for method invocation or something similar (it's
in the SQL standard).

This is the first time I have heard that we should stay away from this.
We have operators with this name in hstore, which is why I chose it.

Have we officially deprecated '->'? I know we deprecated "=>", but I
simply don't recall anything about '->'.

I also don't find the proposed use to be very intuitive. You invented
lots of other function names -- why not invent a few more for this
purpose that are clearer?

I'm happy to take opinions about this, and I expected some bikeshedding,
but your reaction is contrary to everything others have told me. Mostly
they love the operators.

I guess that '~>' and '~>>' would work as well as '->' 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

#40Merlin Moncure
mmoncure@gmail.com
In reply to: Andrew Dunstan (#39)
Re: json api WIP patch

On Thu, Jan 31, 2013 at 4:20 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

On 01/31/2013 05:06 PM, Peter Eisentraut wrote:

On 1/10/13 6:42 PM, Andrew Dunstan wrote:

This updated patch contains all the intended functionality, including
operators for the json_get_path functions, so you can say things like

select jsonval->array['f1','0','f2] ...

I would like to not create any -> operators, so that that syntax could
be used in the future for method invocation or something similar (it's
in the SQL standard).

This is the first time I have heard that we should stay away from this. We
have operators with this name in hstore, which is why I chose it.

Have we officially deprecated '->'? I know we deprecated "=>", but I simply
don't recall anything about '->'.

I also don't find the proposed use to be very intuitive. You invented
lots of other function names -- why not invent a few more for this
purpose that are clearer?

I'm happy to take opinions about this, and I expected some bikeshedding, but
your reaction is contrary to everything others have told me. Mostly they
love the operators.

I guess that '~>' and '~>>' would work as well as '->' and '->>'.

also hstore implements ->

quick off-topic aside: is colon (:) reserved for any purpose as an
operator in SQL?

merlin

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

#41Tom Lane
tgl@sss.pgh.pa.us
In reply to: Merlin Moncure (#40)
Re: json api WIP patch

Merlin Moncure <mmoncure@gmail.com> writes:

On Thu, Jan 31, 2013 at 4:20 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

On 01/31/2013 05:06 PM, Peter Eisentraut wrote:

I would like to not create any -> operators, so that that syntax could
be used in the future for method invocation or something similar (it's
in the SQL standard).

This is the first time I have heard that we should stay away from this. We
have operators with this name in hstore, which is why I chose it.

I'm not happy about this either. It's bad enough that we're thinking
about taking away =>, but to disallow -> as well? My inclination is to
just say no, we're not implementing that. Even if we remove the contrib
operators named that way, it's insane to suppose that nobody has chosen
these names for user-defined operators in their applications.

quick off-topic aside: is colon (:) reserved for any purpose as an
operator in SQL?

We disallow it as an operator character, because of the conflict with
parameter/variable syntax in ecpg and psql. It was allowed before
PG 7.0, IIRC.

regards, tom lane

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

#42David E. Wheeler
david@justatheory.com
In reply to: Andrew Dunstan (#39)
Re: json api WIP patch

On Jan 31, 2013, at 2:20 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

I'm happy to take opinions about this, and I expected some bikeshedding, but your reaction is contrary to everything others have told me. Mostly they love the operators.

I guess that '~>' and '~>>' would work as well as '->' and '->>'.

Or +> and +>>, since ~ is set very high and small by some fonts (where the fontmakers though of it as a kind of superscript character).

I suppose that := is out of the question?

Best,

David

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

#43Andrew Dunstan
andrew@dunslane.net
In reply to: David E. Wheeler (#42)
Re: json api WIP patch

On 01/31/2013 07:16 PM, David E. Wheeler wrote:

On Jan 31, 2013, at 2:20 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

I'm happy to take opinions about this, and I expected some bikeshedding, but your reaction is contrary to everything others have told me. Mostly they love the operators.

I guess that '~>' and '~>>' would work as well as '->' and '->>'.

Or +> and +>>, since ~ is set very high and small by some fonts (where the fontmakers though of it as a kind of superscript character).

I suppose that := is out of the question?

Even if it were I would not on any account use it. As an old Ada
programmer my mind just revolts at the idea of using this for anything
but assignment.

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

#44Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Dunstan (#43)
Re: json api WIP patch

Andrew Dunstan <andrew@dunslane.net> writes:

On 01/31/2013 07:16 PM, David E. Wheeler wrote:

I suppose that := is out of the question?

Even if it were I would not on any account use it. As an old Ada
programmer my mind just revolts at the idea of using this for anything
but assignment.

Ada or no, its use in plpgsql would render that a seriously bad idea.

regards, tom lane

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

#45David E. Wheeler
david@justatheory.com
In reply to: Tom Lane (#44)
Re: json api WIP patch

On Jan 31, 2013, at 4:32 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Ada or no, its use in plpgsql would render that a seriously bad idea.

I assumed that its use in function params would be the main reason not to use it.

David

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

#46Gavin Flower
GavinFlower@archidevsys.co.nz
In reply to: Andrew Dunstan (#43)
Re: json api WIP patch

On 01/02/13 13:26, Andrew Dunstan wrote:

On 01/31/2013 07:16 PM, David E. Wheeler wrote:

On Jan 31, 2013, at 2:20 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

I'm happy to take opinions about this, and I expected some
bikeshedding, but your reaction is contrary to everything others
have told me. Mostly they love the operators.

I guess that '~>' and '~>>' would work as well as '->' and '->>'.

Or +> and +>>, since ~ is set very high and small by some fonts
(where the fontmakers though of it as a kind of superscript character).

I suppose that := is out of the question?

Even if it were I would not on any account use it. As an old Ada
programmer my mind just revolts at the idea of using this for anything
but assignment.

cheers

andrew

Ancient Algol 60 programmer here, otherwise ditto!

#47Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#41)
Re: json api WIP patch

On Thu, Jan 31, 2013 at 7:12 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Merlin Moncure <mmoncure@gmail.com> writes:

On Thu, Jan 31, 2013 at 4:20 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

On 01/31/2013 05:06 PM, Peter Eisentraut wrote:

I would like to not create any -> operators, so that that syntax could
be used in the future for method invocation or something similar (it's
in the SQL standard).

This is the first time I have heard that we should stay away from this. We
have operators with this name in hstore, which is why I chose it.

I'm not happy about this either. It's bad enough that we're thinking
about taking away =>, but to disallow -> as well? My inclination is to
just say no, we're not implementing that. Even if we remove the contrib
operators named that way, it's insane to suppose that nobody has chosen
these names for user-defined operators in their applications.

I think it's smarter for us to ship functions, and let users wrap them
in operators if they so choose. It's not difficult for people who
want it to do it, and that way we dodge this whole mess.

The thing that was really awful about hstore's => operator is that it
was =>(text, text), meaning that if somebody else invented, say,
xstore, they could not do the same thing that hstore did without
colliding with hstore. I think we ought to have an ironclad rule that
any operators introduced in our core distribution for particular types
must have at least one argument of that type. Otherwise, we'll end up
with a free-for-all where everyone tries to grab the "good" operator
names (of which there are only a handful) for their type, and types
we're adding five years from now will be stuck with no operator names
at all, or dumb stuff like ~~~~>.

But even leaving that aside, I'm surprised to hear so many people
dismissing SQL standards compliance so blithely. We've certainly
spent a lot of blood, sweat, and tears on minor standards-compliance
issues over they years - why is it OK to not care about this
particular issue when we've spent so much effort caring about other
ones?

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

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

#48Daniel Farina
daniel@heroku.com
In reply to: Robert Haas (#47)
Re: json api WIP patch

On Fri, Feb 1, 2013 at 2:08 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Thu, Jan 31, 2013 at 7:12 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Merlin Moncure <mmoncure@gmail.com> writes:

On Thu, Jan 31, 2013 at 4:20 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

On 01/31/2013 05:06 PM, Peter Eisentraut wrote:

I would like to not create any -> operators, so that that syntax could
be used in the future for method invocation or something similar (it's
in the SQL standard).

This is the first time I have heard that we should stay away from this. We
have operators with this name in hstore, which is why I chose it.

I'm not happy about this either. It's bad enough that we're thinking
about taking away =>, but to disallow -> as well? My inclination is to
just say no, we're not implementing that. Even if we remove the contrib
operators named that way, it's insane to suppose that nobody has chosen
these names for user-defined operators in their applications.

I think it's smarter for us to ship functions, and let users wrap them
in operators if they so choose. It's not difficult for people who
want it to do it, and that way we dodge this whole mess.

Normally I'd agree with you, but I think there's a complexity here
that is very frown-inducing, although I'm not positively inclined to
state that it's so great that your suggested solution is not the least
of all evils:

/messages/by-id/8551.1331580169@sss.pgh.pa.us

The problem being: even though pg_operator resolves to functions in
pg_proc, they have distinct identities as far as the planner is
concerned w.r.t selectivity estimation and index selection. Already
there is a slight hazard that some ORMs that want to grow hstore
support will spell it "fetchval" and others "->". So far, infix
syntax seems to be the common default, but a minor disagreement among
ORMs or different user preferences will be destructive.

Another way to look at this is that by depriving people multiple
choices in the default install, that hazard goes away. But it also
means that, practically, CREATE OPERATOR is going to be hazardous to
use because almost all software is probably not going to assume the
existence of any non-default installed operators for JSON, and those
that do will not receive the benefit of indexes from software using
the other notation. So, I think that if one takes the 'when in doubt,
leave it out' approach you seem to be proposing, I also think that
very little profitable use of CREATE OPERATOR will take place -- ORMs
et al will choose the lowest common denominator (for good sensible
reason) and flirting with CREATE OPERATOR will probably cause
surprising plans, so I doubt it'll take hold.

--
fdr

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

#49Tom Lane
tgl@sss.pgh.pa.us
In reply to: Daniel Farina (#48)
Re: json api WIP patch

Daniel Farina <daniel@heroku.com> writes:

On Fri, Feb 1, 2013 at 2:08 PM, Robert Haas <robertmhaas@gmail.com> wrote:

I think it's smarter for us to ship functions, and let users wrap them
in operators if they so choose. It's not difficult for people who

The problem being: even though pg_operator resolves to functions in
pg_proc, they have distinct identities as far as the planner is
concerned w.r.t selectivity estimation and index selection.

Yeah, this is surely not a workable policy unless we first move all
those planner smarts to apply to functions not operators. And rewrite
all the index AM APIs to use functions not operators, too. Now this is
something that's been a wish-list item right along, but actually doing
it has always looked like a great deal of work for rather small reward.

regards, tom lane

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

#50Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#49)
Re: json api WIP patch

On Fri, Feb 1, 2013 at 6:03 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Daniel Farina <daniel@heroku.com> writes:

On Fri, Feb 1, 2013 at 2:08 PM, Robert Haas <robertmhaas@gmail.com> wrote:

I think it's smarter for us to ship functions, and let users wrap them
in operators if they so choose. It's not difficult for people who

The problem being: even though pg_operator resolves to functions in
pg_proc, they have distinct identities as far as the planner is
concerned w.r.t selectivity estimation and index selection.

Yeah, this is surely not a workable policy unless we first move all
those planner smarts to apply to functions not operators. And rewrite
all the index AM APIs to use functions not operators, too. Now this is
something that's been a wish-list item right along, but actually doing
it has always looked like a great deal of work for rather small reward.

Hmm. Well, if the operators are going to be indexable, then I agree
that's an issue, but isn't -> just a key-extraction operator? That
wouldn't be something you could index anyway.

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

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

#51Andrew Dunstan
andrew@dunslane.net
In reply to: Robert Haas (#50)
Re: json api WIP patch

On 02/03/2013 08:20 PM, Robert Haas wrote:

On Fri, Feb 1, 2013 at 6:03 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Daniel Farina <daniel@heroku.com> writes:

On Fri, Feb 1, 2013 at 2:08 PM, Robert Haas <robertmhaas@gmail.com> wrote:

I think it's smarter for us to ship functions, and let users wrap them
in operators if they so choose. It's not difficult for people who

The problem being: even though pg_operator resolves to functions in
pg_proc, they have distinct identities as far as the planner is
concerned w.r.t selectivity estimation and index selection.

Yeah, this is surely not a workable policy unless we first move all
those planner smarts to apply to functions not operators. And rewrite
all the index AM APIs to use functions not operators, too. Now this is
something that's been a wish-list item right along, but actually doing
it has always looked like a great deal of work for rather small reward.

Hmm. Well, if the operators are going to be indexable, then I agree
that's an issue, but isn't -> just a key-extraction operator? That
wouldn't be something you could index anyway.

Er, what? It gives you the value corresponding to a key (or the numbered
array element).

With the Json operators I provided you're more likely to use ->> in an
index, because it returns de-escaped text rather than json, but I don't
see any reason in principle why -> couldn't be used.

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

#52Merlin Moncure
mmoncure@gmail.com
In reply to: Robert Haas (#47)
Re: json api WIP patch

On Fri, Feb 1, 2013 at 4:08 PM, Robert Haas <robertmhaas@gmail.com> wrote:

But even leaving that aside, I'm surprised to hear so many people
dismissing SQL standards compliance so blithely. We've certainly
spent a lot of blood, sweat, and tears on minor standards-compliance
issues over they years - why is it OK to not care about this
particular issue when we've spent so much effort caring about other
ones?

Does the SQL Standard suggest you can't extend the language with
operators? Or does it reserve certain characters for future use? And
if so, is there a list?

merlin

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

#53Robert Haas
robertmhaas@gmail.com
In reply to: Andrew Dunstan (#51)
Re: json api WIP patch

On Sun, Feb 3, 2013 at 9:05 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

Yeah, this is surely not a workable policy unless we first move all
those planner smarts to apply to functions not operators. And rewrite
all the index AM APIs to use functions not operators, too. Now this is
something that's been a wish-list item right along, but actually doing
it has always looked like a great deal of work for rather small reward.

Hmm. Well, if the operators are going to be indexable, then I agree
that's an issue, but isn't -> just a key-extraction operator? That
wouldn't be something you could index anyway.

Er, what? It gives you the value corresponding to a key (or the numbered
array element).

That's what I figured.

With the Json operators I provided you're more likely to use ->> in an
index, because it returns de-escaped text rather than json, but I don't see
any reason in principle why -> couldn't be used.

The point is that if you're talking about indexing something like
myeav->'andrew' you could equally well index json_get(myeav,
'andrew'). So there's no real need for it to be an operator rather
than a function.

The case in which it would matter is if it were something that could
be used as an index predicate, like:

Index Scan
-> Index Cond: myeav->'andrew'

As of now, the query planner won't consider such a plan if it's only a
function and not an operator. So if we had a case like that, the use
of operator notation could be justified on performance grounds. If we
don't, I argue that it's better to stick with functional notation,
because the number of sensible function names is much larger than the
number of sensible operator names, and if we start using operator
notation every time someone thinks it will look nicer that way, we
will very quickly either run out of nice-looking operator names or
start overloading them heavily.

The SQL standards considerations seem worth thinking about, too.
We've certainly gone through a lot of pain working toward eliminating
=> as an operator name, and if the SQL standard has commandeered ->
for some purpose or other, I'd really rather not add to the headaches
involved should we ever decide to reclaim it.

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

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

#54Andrew Dunstan
andrew@dunslane.net
In reply to: Robert Haas (#53)
Re: json api WIP patch

On 02/04/2013 10:47 AM, Robert Haas wrote:

The SQL standards considerations seem worth thinking about, too.
We've certainly gone through a lot of pain working toward eliminating
=> as an operator name, and if the SQL standard has commandeered ->
for some purpose or other, I'd really rather not add to the headaches
involved should we ever decide to reclaim it.

OK, but I'd like to know what is going to be safe. There's no way to
future-proof the language. I'm quite prepared to replace -> with
something else, and if I do then ->> will need to be adjusted
accordingly, I think.

My suggestion would be ~> and ~>>. I know David Wheeler didn't like that
on the ground that some fonts elevate ~ rather than aligning it in the
middle as most monospaced fonts do, but I'm tempted just to say "then
use a different font." Other possibilities that come to mind are +> and
+>>, although I think they're less attractive. But I'll be guided by the
consensus, assuming there is one ;-)

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

#55Benedikt Grundmann
bgrundmann@janestreet.com
In reply to: Andrew Dunstan (#54)
Re: json api WIP patch

On Mon, Feb 4, 2013 at 4:10 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

On 02/04/2013 10:47 AM, Robert Haas wrote:

The SQL standards considerations seem worth thinking about, too.
We've certainly gone through a lot of pain working toward eliminating
=> as an operator name, and if the SQL standard has commandeered ->
for some purpose or other, I'd really rather not add to the headaches
involved should we ever decide to reclaim it.

OK, but I'd like to know what is going to be safe. There's no way to
future-proof the language. I'm quite prepared to replace -> with something
else, and if I do then ->> will need to be adjusted accordingly, I think.

My suggestion would be ~> and ~>>. I know David Wheeler didn't like that
on the ground that some fonts elevate ~ rather than aligning it in the
middle as most monospaced fonts do, but I'm tempted just to say "then use a
different font." Other possibilities that come to mind are +> and +>>,
although I think they're less attractive. But I'll be guided by the
consensus, assuming there is one ;-)

As a user I would be much in favor of just functions and no additional

operators if the sole difference is syntactical. I think custom operators
are much harder to remember than function names (assuming reasonably well
chosen function names).

Now Robert seems to suggest that there will also be speed / planner
difference which seems sad (I would have expected operators to be just
syntactical sugar for specially named functions and once we are past the
parser there should be no difference).

#56David E. Wheeler
david@justatheory.com
In reply to: Andrew Dunstan (#54)
Re: json api WIP patch

On Feb 4, 2013, at 8:10 AM, Andrew Dunstan <andrew@dunslane.net> wrote:

My suggestion would be ~> and ~>>. I know David Wheeler didn't like that on the ground that some fonts elevate ~ rather than aligning it in the middle as most monospaced fonts do, but I'm tempted just to say "then use a different font." Other possibilities that come to mind are +> and +>>, although I think they're less attractive. But I'll be guided by the consensus, assuming there is one ;-)

On the contrary, I quite like ~>. I've used it in pair.

http://pgxn.org/extension/pair

But others have complained about the font issue when I've suggested it for things in the past.

My fonts don't suck. :-)

I can live with +> and +>>.

David

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

#57Merlin Moncure
mmoncure@gmail.com
In reply to: Benedikt Grundmann (#55)
Re: json api WIP patch

On Mon, Feb 4, 2013 at 10:18 AM, Benedikt Grundmann
<bgrundmann@janestreet.com> wrote:

On Mon, Feb 4, 2013 at 4:10 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

On 02/04/2013 10:47 AM, Robert Haas wrote:

The SQL standards considerations seem worth thinking about, too.
We've certainly gone through a lot of pain working toward eliminating
=> as an operator name, and if the SQL standard has commandeered ->
for some purpose or other, I'd really rather not add to the headaches
involved should we ever decide to reclaim it.

OK, but I'd like to know what is going to be safe. There's no way to
future-proof the language. I'm quite prepared to replace -> with something
else, and if I do then ->> will need to be adjusted accordingly, I think.

My suggestion would be ~> and ~>>. I know David Wheeler didn't like that
on the ground that some fonts elevate ~ rather than aligning it in the
middle as most monospaced fonts do, but I'm tempted just to say "then use a
different font." Other possibilities that come to mind are +> and +>>,
although I think they're less attractive. But I'll be guided by the
consensus, assuming there is one ;-)

As a user I would be much in favor of just functions and no additional
operators if the sole difference is syntactical. I think custom operators
are much harder to remember than function names (assuming reasonably well
chosen function names).

couple quick observations:

*) just about all postgres extension types expose operators -- problem
is not specific to json (and therefore IMO irrelevant to 9.3 release
of enhancements)

*) hstore exposes ->. I use it all over the place. I find operator
to be terse and readable -- much more so than function definition.
Ok, operator such as "@-@" is pretty silly, but "->" for get is
natural. The cat is out of the bag, so removing -> for 9.3 for
production seems pretty fruitless.

*) Removing -> (breaking all my and countless others' hstore dependent
code) should not happen until there is a very good reason. This was
extensively discussed in development of hstore. Breaking
compatibility sucks -- my company is just wrapping up a 12 month code
overhaul so we could get off 8.1.

*) it's bad enough that we drift from sql naming conventions and all
type manipulation functions (except in hstore) with type name.
json_get etc. at least using operators allow avoiding some of that
unnecessary verbosity. what's next: text_concatenate('foo', 'bar')?

merlin

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

#58Andrew Dunstan
andrew@dunslane.net
In reply to: Merlin Moncure (#57)
Re: json api WIP patch

On 02/04/2013 12:57 PM, Merlin Moncure wrote:

*) it's bad enough that we drift from sql naming conventions and all
type manipulation functions (except in hstore) with type name.
json_get etc. at least using operators allow avoiding some of that
unnecessary verbosity. what's next: text_concatenate('foo', 'bar')?

This names aren't set in stone either. I've been expecting some
bikeshedding there, and I'm surprised there hasn't been more.

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

#59Robert Haas
robertmhaas@gmail.com
In reply to: Andrew Dunstan (#54)
Re: json api WIP patch

On Mon, Feb 4, 2013 at 11:10 AM, Andrew Dunstan <andrew@dunslane.net> wrote:

On 02/04/2013 10:47 AM, Robert Haas wrote:

The SQL standards considerations seem worth thinking about, too.
We've certainly gone through a lot of pain working toward eliminating
=> as an operator name, and if the SQL standard has commandeered ->
for some purpose or other, I'd really rather not add to the headaches
involved should we ever decide to reclaim it.

OK, but I'd like to know what is going to be safe. There's no way to
future-proof the language. I'm quite prepared to replace -> with something
else, and if I do then ->> will need to be adjusted accordingly, I think.

My suggestion would be ~> and ~>>. I know David Wheeler didn't like that on
the ground that some fonts elevate ~ rather than aligning it in the middle
as most monospaced fonts do, but I'm tempted just to say "then use a
different font." Other possibilities that come to mind are +> and +>>,
although I think they're less attractive. But I'll be guided by the
consensus, assuming there is one ;-)

I suspect both of those are pretty safe from an SQL standards point of
view. Of course, as Tom is often wont to point out, the SQL standards
committee sometimes does bizarre things, so nothing's perfect, but I'd
be rather shocked if any of those got tapped to mean something else.

That having been said, I still don't see value in adding operators at
all. Good old function call notation seems perfectly adequate from
where I sit. Sure, it's a little more verbose, but when you try to
too hard make things concise then you end up having to explain to your
users why \ditS is a sensible thing for them to type into psql, or why
s@\W@sprintf"%%%02x",ord($&)@e in Perl. I recognize that I may lose
this argument, but I've worked with a couple of languages where
operators can be overloaded (C++) or defined (ML) and it's just never
seemed to work out very well. YMMV, of course.

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

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

#60Merlin Moncure
mmoncure@gmail.com
In reply to: Andrew Dunstan (#58)
Re: json api WIP patch

On Mon, Feb 4, 2013 at 12:07 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

On 02/04/2013 12:57 PM, Merlin Moncure wrote:

*) it's bad enough that we drift from sql naming conventions and all
type manipulation functions (except in hstore) with type name.
json_get etc. at least using operators allow avoiding some of that
unnecessary verbosity. what's next: text_concatenate('foo', 'bar')?

This names aren't set in stone either. I've been expecting some bikeshedding
there, and I'm surprised there hasn't been more.

Well -- heh (asked to bikeshed: joy!) -- I felt like my objections
were noted and am more interested in getting said functionality out
the door than splitting hairs over names. Type prefix issue is under
the same umbrella as use of the -> operator, that is, *not
specifically related to this patch, and not worth holding up this
patch over*. In both cases it's essentially crying over spilt milk.

My only remaining nit with the proposal is with json_unnest().

SQL unnest() produces list of scalars regardless of dimensionality --
json unnest unwraps one level only (contrast: pl/pgsql array 'slice').
So I think 'json_array_each', or perhaps json_slice() is a better fit
there.

merlin

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

#61Will Leinweber
will@heroku.com
In reply to: Robert Haas (#59)
Re: json api WIP patch

On Mon, Feb 4, 2013 at 11:38 AM, Robert Haas <robertmhaas@gmail.com> wrote:

I suspect both of those are pretty safe from an SQL standards point of
view. Of course, as Tom is often wont to point out, the SQL standards
committee sometimes does bizarre things, so nothing's perfect, but I'd
be rather shocked if any of those got tapped to mean something else.

That having been said, I still don't see value in adding operators at
all. Good old function call notation seems perfectly adequate from
where I sit. Sure, it's a little more verbose, but when you try to
too hard make things concise then you end up having to explain to your
users why \ditS is a sensible thing for them to type into psql, or why
s@\W@sprintf"%%%02x",ord($&)@e in Perl. I recognize that I may lose
this argument, but I've worked with a couple of languages where
operators can be overloaded (C++) or defined (ML) and it's just never
seemed to work out very well. YMMV, of course.

For what my opinion is worth I absolute agree with just having function
names. The -> in hstore is kind of nice, but it lead me to a whole lot of
greif when I couldn't figure out how to create an index using it (turns out
you have to use _double_ parens, who knew?), but could create an index on
fetchval and assumed that postgres would figure it out.

Also a for quite a while it felt just like incantation of when I'd need
parens around those operatiors or not. Now that I sorta-kinda-not-really
understand the operation precedence rules in postgres/sql standard, I've
mostly given up on using cute operators because their much more of a pain
on a day-to-day basis.

#62Andrew Dunstan
andrew@dunslane.net
In reply to: Merlin Moncure (#60)
Re: json api WIP patch

On 02/04/2013 02:59 PM, Merlin Moncure wrote:

On Mon, Feb 4, 2013 at 12:07 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

On 02/04/2013 12:57 PM, Merlin Moncure wrote:

*) it's bad enough that we drift from sql naming conventions and all
type manipulation functions (except in hstore) with type name.
json_get etc. at least using operators allow avoiding some of that
unnecessary verbosity. what's next: text_concatenate('foo', 'bar')?

This names aren't set in stone either. I've been expecting some bikeshedding
there, and I'm surprised there hasn't been more.

Well -- heh (asked to bikeshed: joy!) -- I felt like my objections
were noted and am more interested in getting said functionality out
the door than splitting hairs over names. Type prefix issue is under
the same umbrella as use of the -> operator, that is, *not
specifically related to this patch, and not worth holding up this
patch over*. In both cases it's essentially crying over spilt milk.

My only remaining nit with the proposal is with json_unnest().

SQL unnest() produces list of scalars regardless of dimensionality --
json unnest unwraps one level only (contrast: pl/pgsql array 'slice').
So I think 'json_array_each', or perhaps json_slice() is a better fit
there.

how about json_array_elements()?

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

#63Daniel Farina
daniel@heroku.com
In reply to: Robert Haas (#59)
Re: json api WIP patch

On Mon, Feb 4, 2013 at 11:38 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Feb 4, 2013 at 11:10 AM, Andrew Dunstan <andrew@dunslane.net> wrote:

On 02/04/2013 10:47 AM, Robert Haas wrote:

The SQL standards considerations seem worth thinking about, too.
We've certainly gone through a lot of pain working toward eliminating
=> as an operator name, and if the SQL standard has commandeered ->
for some purpose or other, I'd really rather not add to the headaches
involved should we ever decide to reclaim it.

OK, but I'd like to know what is going to be safe. There's no way to
future-proof the language. I'm quite prepared to replace -> with something
else, and if I do then ->> will need to be adjusted accordingly, I think.

My suggestion would be ~> and ~>>. I know David Wheeler didn't like that on
the ground that some fonts elevate ~ rather than aligning it in the middle
as most monospaced fonts do, but I'm tempted just to say "then use a
different font." Other possibilities that come to mind are +> and +>>,
although I think they're less attractive. But I'll be guided by the
consensus, assuming there is one ;-)

I suspect both of those are pretty safe from an SQL standards point of
view. Of course, as Tom is often wont to point out, the SQL standards
committee sometimes does bizarre things, so nothing's perfect, but I'd
be rather shocked if any of those got tapped to mean something else.

That having been said, I still don't see value in adding operators at
all. Good old function call notation seems perfectly adequate from
where I sit. Sure, it's a little more verbose, but when you try to
too hard make things concise then you end up having to explain to your
users why \ditS is a sensible thing for them to type into psql, or why
s@\W@sprintf"%%%02x",ord($&)@e in Perl. I recognize that I may lose
this argument, but I've worked with a couple of languages where
operators can be overloaded (C++) or defined (ML) and it's just never
seemed to work out very well. YMMV, of course.

I also basically feel this way, although I know I tend more towards
notational brutalism than many. I think we shouldn't kid ourselves
that non-default operators will be used, and for
current-implementation reasons (that maybe could be fixed by someone
determined) it's not really at the pleasure of the author to use them
via CREATE OPERATOR either.

So, I basically subscribe to view that we should investigate what
total reliance on prefix syntax looks like. I guess it'll make nested
navigation horribly ugly, though...positively lisp-esque. That' s one
consideration hstore doesn't have that may make use of infix notations
considerably more useful for json than hstore.

--
fdr

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

#64Andrew Dunstan
andrew@dunslane.net
In reply to: Daniel Farina (#63)
Re: json api WIP patch

On 02/04/2013 03:16 PM, Daniel Farina wrote:

On Mon, Feb 4, 2013 at 11:38 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Feb 4, 2013 at 11:10 AM, Andrew Dunstan <andrew@dunslane.net> wrote:

On 02/04/2013 10:47 AM, Robert Haas wrote:

The SQL standards considerations seem worth thinking about, too.
We've certainly gone through a lot of pain working toward eliminating
=> as an operator name, and if the SQL standard has commandeered ->
for some purpose or other, I'd really rather not add to the headaches
involved should we ever decide to reclaim it.

OK, but I'd like to know what is going to be safe. There's no way to
future-proof the language. I'm quite prepared to replace -> with something
else, and if I do then ->> will need to be adjusted accordingly, I think.

My suggestion would be ~> and ~>>. I know David Wheeler didn't like that on
the ground that some fonts elevate ~ rather than aligning it in the middle
as most monospaced fonts do, but I'm tempted just to say "then use a
different font." Other possibilities that come to mind are +> and +>>,
although I think they're less attractive. But I'll be guided by the
consensus, assuming there is one ;-)

I suspect both of those are pretty safe from an SQL standards point of
view. Of course, as Tom is often wont to point out, the SQL standards
committee sometimes does bizarre things, so nothing's perfect, but I'd
be rather shocked if any of those got tapped to mean something else.

That having been said, I still don't see value in adding operators at
all. Good old function call notation seems perfectly adequate from
where I sit. Sure, it's a little more verbose, but when you try to
too hard make things concise then you end up having to explain to your
users why \ditS is a sensible thing for them to type into psql, or why
s@\W@sprintf"%%%02x",ord($&)@e in Perl. I recognize that I may lose
this argument, but I've worked with a couple of languages where
operators can be overloaded (C++) or defined (ML) and it's just never
seemed to work out very well. YMMV, of course.

I also basically feel this way, although I know I tend more towards
notational brutalism than many. I think we shouldn't kid ourselves
that non-default operators will be used, and for
current-implementation reasons (that maybe could be fixed by someone
determined) it's not really at the pleasure of the author to use them
via CREATE OPERATOR either.

So, I basically subscribe to view that we should investigate what
total reliance on prefix syntax looks like. I guess it'll make nested
navigation horribly ugly, though...positively lisp-esque. That' s one
consideration hstore doesn't have that may make use of infix notations
considerably more useful for json than hstore.

We don't have the luxury of designing things like this in or out from
scratch. Creation of operators has been a part of PostgreSQL for a good
while longer than my involvement, and a great many people expect to be
able to use it. I can just imagine the outrage at any suggestion of
removing it.

So, please, let's get real. A "total reliance on prefix syntax" isn't
going to happen, and investigating what it would look like seems to me
just so much wasted time and effort.

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

#65Merlin Moncure
mmoncure@gmail.com
In reply to: Andrew Dunstan (#62)
Re: json api WIP patch

On Mon, Feb 4, 2013 at 2:10 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

My only remaining nit with the proposal is with json_unnest().

SQL unnest() produces list of scalars regardless of dimensionality --
json unnest unwraps one level only (contrast: pl/pgsql array 'slice').
So I think 'json_array_each', or perhaps json_slice() is a better fit
there.

how about json_array_elements()?

that works (although it's a little verbose for my taste). maybe
json_unwrap, json_array_unwrap, etc.

merlin

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

#66Daniel Farina
daniel@heroku.com
In reply to: Andrew Dunstan (#64)
Re: json api WIP patch

On Mon, Feb 4, 2013 at 12:37 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

On 02/04/2013 03:16 PM, Daniel Farina wrote:

On Mon, Feb 4, 2013 at 11:38 AM, Robert Haas <robertmhaas@gmail.com>
wrote:

On Mon, Feb 4, 2013 at 11:10 AM, Andrew Dunstan <andrew@dunslane.net>
wrote:

On 02/04/2013 10:47 AM, Robert Haas wrote:

The SQL standards considerations seem worth thinking about, too.
We've certainly gone through a lot of pain working toward eliminating
=> as an operator name, and if the SQL standard has commandeered ->
for some purpose or other, I'd really rather not add to the headaches
involved should we ever decide to reclaim it.

OK, but I'd like to know what is going to be safe. There's no way to
future-proof the language. I'm quite prepared to replace -> with
something
else, and if I do then ->> will need to be adjusted accordingly, I
think.

My suggestion would be ~> and ~>>. I know David Wheeler didn't like that
on
the ground that some fonts elevate ~ rather than aligning it in the
middle
as most monospaced fonts do, but I'm tempted just to say "then use a
different font." Other possibilities that come to mind are +> and +>>,
although I think they're less attractive. But I'll be guided by the
consensus, assuming there is one ;-)

I suspect both of those are pretty safe from an SQL standards point of
view. Of course, as Tom is often wont to point out, the SQL standards
committee sometimes does bizarre things, so nothing's perfect, but I'd
be rather shocked if any of those got tapped to mean something else.

That having been said, I still don't see value in adding operators at
all. Good old function call notation seems perfectly adequate from
where I sit. Sure, it's a little more verbose, but when you try to
too hard make things concise then you end up having to explain to your
users why \ditS is a sensible thing for them to type into psql, or why
s@\W@sprintf"%%%02x",ord($&)@e in Perl. I recognize that I may lose
this argument, but I've worked with a couple of languages where
operators can be overloaded (C++) or defined (ML) and it's just never
seemed to work out very well. YMMV, of course.

I also basically feel this way, although I know I tend more towards
notational brutalism than many. I think we shouldn't kid ourselves
that non-default operators will be used, and for
current-implementation reasons (that maybe could be fixed by someone
determined) it's not really at the pleasure of the author to use them
via CREATE OPERATOR either.

So, I basically subscribe to view that we should investigate what
total reliance on prefix syntax looks like. I guess it'll make nested
navigation horribly ugly, though...positively lisp-esque. That' s one
consideration hstore doesn't have that may make use of infix notations
considerably more useful for json than hstore.

We don't have the luxury of designing things like this in or out from
scratch. Creation of operators has been a part of PostgreSQL for a good
while longer than my involvement, and a great many people expect to be able
to use it. I can just imagine the outrage at any suggestion of removing it.

I am only referring to referring the restriction that the planner
can't understand that fetchval() and '->' mean the same thing for,
say, hstore. Hence, use of non-default CREATE OPERATOR may become
more useful some day, instead of basically being a pitfall when
someone reasonably thinks they could use either spelling of the same
functionality and the optimizer will figure it out.

I'm not suggesting removal of any feature.

My reference to "total reliance of prefix syntax" refers only to the
JSON operators, since the previous correspondence from Robert was
about how function call syntax alone may be sufficient. This phrase
refers to the same idea he is proposing.

I also included a weakness to that idea, which is that nesting in JSON
makes the situation worse than the common compared case, hstore.

--
fdr

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

#67Andrew Dunstan
andrew@dunslane.net
In reply to: Daniel Farina (#66)
Re: json api WIP patch

On 02/04/2013 04:19 PM, Daniel Farina wrote:

On Mon, Feb 4, 2013 at 12:37 PM, Andrew Dunstan <andrew@dunslane.net> wrote:

On 02/04/2013 03:16 PM, Daniel Farina wrote:

On Mon, Feb 4, 2013 at 11:38 AM, Robert Haas <robertmhaas@gmail.com>
wrote:

On Mon, Feb 4, 2013 at 11:10 AM, Andrew Dunstan <andrew@dunslane.net>
wrote:

On 02/04/2013 10:47 AM, Robert Haas wrote:

The SQL standards considerations seem worth thinking about, too.
We've certainly gone through a lot of pain working toward eliminating
=> as an operator name, and if the SQL standard has commandeered ->
for some purpose or other, I'd really rather not add to the headaches
involved should we ever decide to reclaim it.

OK, but I'd like to know what is going to be safe. There's no way to
future-proof the language. I'm quite prepared to replace -> with
something
else, and if I do then ->> will need to be adjusted accordingly, I
think.

My suggestion would be ~> and ~>>. I know David Wheeler didn't like that
on
the ground that some fonts elevate ~ rather than aligning it in the
middle
as most monospaced fonts do, but I'm tempted just to say "then use a
different font." Other possibilities that come to mind are +> and +>>,
although I think they're less attractive. But I'll be guided by the
consensus, assuming there is one ;-)

I suspect both of those are pretty safe from an SQL standards point of
view. Of course, as Tom is often wont to point out, the SQL standards
committee sometimes does bizarre things, so nothing's perfect, but I'd
be rather shocked if any of those got tapped to mean something else.

That having been said, I still don't see value in adding operators at
all. Good old function call notation seems perfectly adequate from
where I sit. Sure, it's a little more verbose, but when you try to
too hard make things concise then you end up having to explain to your
users why \ditS is a sensible thing for them to type into psql, or why
s@\W@sprintf"%%%02x",ord($&)@e in Perl. I recognize that I may lose
this argument, but I've worked with a couple of languages where
operators can be overloaded (C++) or defined (ML) and it's just never
seemed to work out very well. YMMV, of course.

I also basically feel this way, although I know I tend more towards
notational brutalism than many. I think we shouldn't kid ourselves
that non-default operators will be used, and for
current-implementation reasons (that maybe could be fixed by someone
determined) it's not really at the pleasure of the author to use them
via CREATE OPERATOR either.

So, I basically subscribe to view that we should investigate what
total reliance on prefix syntax looks like. I guess it'll make nested
navigation horribly ugly, though...positively lisp-esque. That' s one
consideration hstore doesn't have that may make use of infix notations
considerably more useful for json than hstore.

We don't have the luxury of designing things like this in or out from
scratch. Creation of operators has been a part of PostgreSQL for a good
while longer than my involvement, and a great many people expect to be able
to use it. I can just imagine the outrage at any suggestion of removing it.

I am only referring to referring the restriction that the planner
can't understand that fetchval() and '->' mean the same thing for,
say, hstore. Hence, use of non-default CREATE OPERATOR may become
more useful some day, instead of basically being a pitfall when
someone reasonably thinks they could use either spelling of the same
functionality and the optimizer will figure it out.

I'm not suggesting removal of any feature.

My reference to "total reliance of prefix syntax" refers only to the
JSON operators, since the previous correspondence from Robert was
about how function call syntax alone may be sufficient. This phrase
refers to the same idea he is proposing.

I also included a weakness to that idea, which is that nesting in JSON
makes the situation worse than the common compared case, hstore.

I see. OK, sorry for misunderstanding.

I suspect, BTW that mostly people will use get_path*() (or rather, its
equivalent operator ;-) ) rather than operator chaining:

select myjson->>'{"authors",0,"name"}'::text[];

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

#68Hannu Krosing
hannu@krosing.net
In reply to: Andrew Dunstan (#39)
Re: json api WIP patch

On 01/31/2013 11:20 PM, Andrew Dunstan wrote:

I'm happy to take opinions about this, and I expected
some bikeshedding, but your reaction is contrary to
everything others have told me. Mostly they love the operators.

What I would really like is if we extended postgresql core and made
a few more constructs definable as overloadable operator:

1) array / dictionary element lookup
a[b]
CREATE OPERATOR [] (...)

2) attribute lookup
a.b
CREATE OPERATOR . (...)

then you could make json lookups either step-by-step using

CREATE OPERATOR [] (
PROCEDURE = json_array_lookup, LEFTARG = json, RIGHTARG = int)

and

CREATE OPERATOR [] (
PROCEDURE = json_dict_lookup, LEFTARG = json, RIGHTARG = text)

fourthname = myjson[4]['name']

or perhaps a single

CREATE OPERATOR [] (
PROCEDURE = json_deep_lookup, LEFTARG = json, RIGHTARG = VARIADIC
"any")

fourthname = myjson[4, 'name']

though I suspect that we do not support type VARIADIC "any" in operator
definitions

---------
Hannu

I guess that '~>' and '~>>' would work as well as '->' 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

#69Pavel Stehule
pavel.stehule@gmail.com
In reply to: Hannu Krosing (#68)
Re: json api WIP patch

2013/2/5 Hannu Krosing <hannu@krosing.net>:

On 01/31/2013 11:20 PM, Andrew Dunstan wrote:

I'm happy to take opinions about this, and I expected
some bikeshedding, but your reaction is contrary to
everything others have told me. Mostly they love the operators.

What I would really like is if we extended postgresql core and made
a few more constructs definable as overloadable operator:

1) array / dictionary element lookup
a[b]
CREATE OPERATOR [] (...)

2) attribute lookup
a.b
CREATE OPERATOR . (...)

then you could make json lookups either step-by-step using

CREATE OPERATOR [] (
PROCEDURE = json_array_lookup, LEFTARG = json, RIGHTARG = int)

and

CREATE OPERATOR [] (
PROCEDURE = json_dict_lookup, LEFTARG = json, RIGHTARG = text)

fourthname = myjson[4]['name']

or perhaps a single

CREATE OPERATOR [] (
PROCEDURE = json_deep_lookup, LEFTARG = json, RIGHTARG = VARIADIC "any")

fourthname = myjson[4, 'name']

it is near to full collection implementation - and can be nice to have
it. For this moment we should to return to this topic.

My preference is using well named functions (prefer it against
operator) and operator that are not in collision with current ANSI SQL

I don't see any nice on design select
myjson->>'{"authors",0,"name"}'::text[]; - more it is ugly as
dinosaurs

better and more usual

myjson['authors']['0']['name']

or

myjson['authors/0/name']

Regards

Pavel

though I suspect that we do not support type VARIADIC "any" in operator
definitions

---------
Hannu

I guess that '~>' and '~>>' would work as well as '->' 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

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

#70Andrew Dunstan
andrew@dunslane.net
In reply to: Pavel Stehule (#69)
Re: json api WIP patch

On 02/05/2013 02:09 AM, Pavel Stehule wrote:

I don't see any nice on design select
myjson->>'{"authors",0,"name"}'::text[]; - more it is ugly as
dinosaurs

I rather like dinosaurs. Beauty is, as they say, in the eye of the beholder.

Let me also point out that you can say (somewhat less efficiently):

myjson->'authors'->0->>'name'

which is not terribly inelegant.

better and more usual

myjson['authors']['0']['name']

or

myjson['authors/0/name']

Well, nothing like that is going to happen in this release. If you or
someone wants to work on a general subscripting facility for arbitrary
data types then I look forward to seeing it.

Let me also point out that the most important part of this patch is the
part that almost nobody has commented on, namely the parser changes and
API that the actual visible functions are built on. Writing JSON
accessor / transformation functions without this framework is hard, and
often redundant. I'm much more concerned to get this framework and some
basic accessor functions (and preferably operators) added than bothered
about how the latter are precisely spelled.

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

#71Pavel Stehule
pavel.stehule@gmail.com
In reply to: Andrew Dunstan (#70)
Re: json api WIP patch

2013/2/5 Andrew Dunstan <andrew@dunslane.net>:

On 02/05/2013 02:09 AM, Pavel Stehule wrote:

I don't see any nice on design select
myjson->>'{"authors",0,"name"}'::text[]; - more it is ugly as
dinosaurs

I rather like dinosaurs. Beauty is, as they say, in the eye of the beholder.

Let me also point out that you can say (somewhat less efficiently):

myjson->'authors'->0->>'name'

which is not terribly inelegant.

better and more usual

myjson['authors']['0']['name']

or

myjson['authors/0/name']

Well, nothing like that is going to happen in this release. If you or
someone wants to work on a general subscripting facility for arbitrary data
types then I look forward to seeing it.

Let me also point out that the most important part of this patch is the part
that almost nobody has commented on, namely the parser changes and API that
the actual visible functions are built on. Writing JSON accessor /
transformation functions without this framework is hard, and often
redundant. I'm much more concerned to get this framework and some basic
accessor functions (and preferably operators) added than bothered about how
the latter are precisely spelled.

C API and implementation can be changed or fixed without hard issues
- it is usual so about SQL interface is hard discussion.

Regards

Pavel

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