Commits

Andrew Dunstan committed b07b50b

Sync with upstream code.

  • Participants
  • Parent commits a896c62
  • Branches master

Comments (0)

Files changed (2)

 
 PG_MODULE_MAGIC;
 
-typedef enum
+typedef enum					/* types of JSON values */
 {
-	JSON_VALUE_INVALID,
+	JSON_VALUE_INVALID,			/* non-value tokens are reported as this */
 	JSON_VALUE_STRING,
 	JSON_VALUE_NUMBER,
 	JSON_VALUE_OBJECT,
 	JSON_VALUE_NULL
 } JsonValueType;
 
-typedef struct
+typedef struct					/* state of JSON lexer */
 {
-	char	   *input;
-	char	   *token_start;
-	char	   *token_terminator;
-	JsonValueType	token_type;
-	int			line_number;
-	char	   *line_start;
+	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
+typedef enum					/* states of JSON parser */
 {
 	JSON_PARSE_VALUE,			/* expecting a value */
 	JSON_PARSE_ARRAY_START,		/* saw '[', expecting value or ']' */
 	JSON_PARSE_OBJECT_COMMA		/* saw object ',', expecting next label */
 } JsonParseState;
 
-typedef struct JsonParseStack
+typedef struct JsonParseStack	/* the parser state has to be stackable */
 {
-	JsonParseState	state;
+	JsonParseState state;
+	/* currently only need the state enum, but maybe someday more stuff */
 } JsonParseStack;
 
-typedef enum
+typedef enum					/* required operations on state stack */
 {
-	JSON_STACKOP_NONE,
-	JSON_STACKOP_PUSH,
-	JSON_STACKOP_PUSH_WITH_PUSHBACK,
-	JSON_STACKOP_POP
+	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_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, 
+static void composite_to_json(Datum composite, StringInfo result,
 							  bool use_line_feeds);
-static void array_to_json_internal(Datum array, 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);
 static void  escape_json(StringInfo buf, const char *str);
 static Oid JSONOID;
 
 #define TYPCATEGORY_JSON 'j'
 /* letters appearing in numeric output that aren't valid in a JSON number */
 #define NON_NUMERIC_LETTER "NnAaIiFfTtYy"
+/* chars to consider as part of an alphanumeric token */
+#define JSON_ALPHANUMERIC_CHAR(c)  \
+	(((c) >= 'a' && (c) <= 'z') || \
+	 ((c) >= 'A' && (c) <= 'Z') || \
+	 ((c) >= '0' && (c) <= '9') || \
+	 (c) == '_' || \
+	 IS_HIGHBIT_SET(c))
+
+
 /*
  * Input.
  */
 Datum
 json_in(PG_FUNCTION_ARGS)
 {
-	char    *text = PG_GETARG_CSTRING(0);
+	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));
 }
 
 Datum
 json_out(PG_FUNCTION_ARGS)
 {
-	Datum	txt = PG_GETARG_DATUM(0);
+	/* we needn't detoast because text_to_cstring will handle that */
+	Datum		txt = PG_GETARG_DATUM(0);
 
 	PG_RETURN_CSTRING(TextDatumGetCString(txt));
 }
 Datum
 json_send(PG_FUNCTION_ARGS)
 {
+	text	   *t = PG_GETARG_TEXT_PP(0);
 	StringInfoData buf;
-	text   *t = PG_GETARG_TEXT_PP(0);
 
 	pq_begintypsend(&buf);
 	pq_sendtext(&buf, VARDATA_ANY(t), VARSIZE_ANY_EXHDR(t));
 static void
 json_validate_cstring(char *input)
 {
-	JsonLexContext	lex;
+	JsonLexContext lex;
 	JsonParseStack *stack,
-				   *stacktop;
-	int				stacksize;
+			   *stacktop;
+	int			stacksize;
 
 	/* Set up lexing context. */
 	lex.input = input;
 	lex.token_terminator = lex.input;
-	lex.line_number = 1;
-	lex.line_start = input;
 
 	/* Set up parse stack. */
 	stacksize = 32;
-	stacktop = palloc(sizeof(JsonParseStack) * stacksize);
+	stacktop = (JsonParseStack *) palloc(sizeof(JsonParseStack) * stacksize);
 	stack = stacktop;
 	stack->state = JSON_PARSE_VALUE;
 
 	/* Main parsing loop. */
 	for (;;)
 	{
-		JsonStackOp	op;
+		JsonStackOp op;
 
 		/* Fetch next token. */
 		json_lex(&lex);
 					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] == '{')
+				else if (lex.token_start[0] == '[' ||
+						 lex.token_start[0] == '{')
 				{
 					stack->state = JSON_PARSE_ARRAY_NEXT;
 					op = JSON_STACKOP_PUSH_WITH_PUSHBACK;
 			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] == '}')
+				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] == ':')
+				if (lex.token_type == JSON_VALUE_INVALID &&
+					lex.token_start[0] == ':')
 				{
 					stack->state = JSON_PARSE_OBJECT_NEXT;
 					op = JSON_STACKOP_PUSH;
 				break;
 			default:
 				elog(ERROR, "unexpected json parse state: %d",
-						(int) stack->state);
+					 (int) stack->state);
 		}
 
-		/* Push or pop the stack, if needed. */
+		/* Push or pop the state stack, if needed. */
 		switch (op)
 		{
 			case JSON_STACKOP_PUSH:
 			case JSON_STACKOP_PUSH_WITH_PUSHBACK:
-				++stack;
+				stack++;
 				if (stack >= &stacktop[stacksize])
 				{
-					int		stackoffset = stack - stacktop;
-					stacksize = stacksize + 32;
-					stacktop = repalloc(stacktop,
-										sizeof(JsonParseStack) * 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;
 						report_parse_error(NULL, &lex);
 					return;
 				}
-				--stack;
+				stack--;
 				break;
 			case JSON_STACKOP_NONE:
 				/* nothing to do */
 	/* Skip leading whitespace. */
 	s = lex->token_terminator;
 	while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r')
-	{
-		if (*s == '\n')
-			++lex->line_number;
-		++s;
-	}
+		s++;
 	lex->token_start = s;
 
 	/* Determine token type. */
-	if (strchr("{}[],:", s[0]))
+	if (strchr("{}[],:", s[0]) != NULL)
 	{
-		/* strchr() doesn't return false on a NUL input. */
+		/* 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 = NULL;
+			lex->token_terminator = s;
 		}
 		else
 		{
 	}
 	else
 	{
-		char   *p;
+		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
+		 * 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; (*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z')
-			|| (*p >= '0' && *p <= '9') || *p == '_' || IS_HIGHBIT_SET(*p);
-			++p)
-			;
+		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)
 		{
+			/*
+			 * 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);
 		}
 static void
 json_lex_string(JsonLexContext *lex)
 {
-	char	   *s = lex->token_start + 1;
+	char	   *s;
 
-	for (s = lex->token_start + 1; *s != '"'; ++s)
+	for (s = lex->token_start + 1; *s != '"'; s++)
 	{
 		/* Per RFC4627, these characters MUST be escaped. */
-		if (*s < 32)
+		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,
 					(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
 					 errmsg("invalid input syntax for type json"),
-					 errdetail_internal("line %d: Character \"%c\" must be escaped.",
-						lex->line_number, *s)));
+					 errdetail("Character with value 0x%02x must be escaped.",
+							   (unsigned char) *s),
+					 report_json_context(lex)));
 		}
 		else if (*s == '\\')
 		{
 			/* OK, we have an escape character. */
-			++s;
+			s++;
 			if (*s == '\0')
 			{
 				lex->token_terminator = s;
 			}
 			else if (*s == 'u')
 			{
-				int		i;
-				int		ch = 0;
+				int			i;
+				int			ch = 0;
 
-				for (i = 1; i <= 4; ++i)
+				for (i = 1; i <= 4; i++)
 				{
-					if (s[i] == '\0')
+					s++;
+					if (*s == '\0')
 					{
-						lex->token_terminator = s + i;
+						lex->token_terminator = s;
 						report_invalid_token(lex);
 					}
-					else if (s[i] >= '0' && s[i] <= '9')
-						ch = (ch * 16) + (s[i] - '0');
-					else if (s[i] >= 'a' && s[i] <= 'f')
-						ch = (ch * 16) + (s[i] - 'a') + 10;
-					else if (s[i] >= 'A' && s[i] <= 'F')
-						ch = (ch * 16) + (s[i] - 'A') + 10;
+					else if (*s >= '0' && *s <= '9')
+						ch = (ch * 16) + (*s - '0');
+					else if (*s >= 'a' && *s <= 'f')
+						ch = (ch * 16) + (*s - 'a') + 10;
+					else if (*s >= 'A' && *s <= 'F')
+						ch = (ch * 16) + (*s - 'A') + 10;
 					else
 					{
+						lex->token_terminator = s + pg_mblen(s);
 						ereport(ERROR,
 								(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
 								 errmsg("invalid input syntax for type json"),
-								 errdetail_internal("line %d: \"\\u\" must be followed by four hexadecimal digits.",
-									lex->line_number)));
+								 errdetail("\"\\u\" must be followed by four hexadecimal digits."),
+								 report_json_context(lex)));
 					}
 				}
-
-				/* Account for the four additional bytes we just parsed. */
-				s += 4;
 			}
-			else if (!strchr("\"\\/bfnrt", *s))
+			else if (strchr("\"\\/bfnrt", *s) == NULL)
 			{
-				/* Error out. */
+				/* 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_internal("line %d: Invalid escape \"\\%s\".",
-							lex->line_number, extract_mb_char(s))));
+						 errdetail("Escape sequence \"\\%s\" is invalid.",
+								   extract_mb_char(s)),
+						 report_json_context(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'.
+ *	   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.)
+ *	   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.)
+ *	   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
 static void
 json_lex_number(JsonLexContext *lex, char *s)
 {
-	bool	error = false;
-	char   *p;
+	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;
+		s++;
 	else if (*s >= '1' && *s <= '9')
 	{
 		do
 		{
-			++s;
+			s++;
 		} while (*s >= '0' && *s <= '9');
 	}
 	else
 	/* Part (3): parse optional decimal portion. */
 	if (*s == '.')
 	{
-		++s;
+		s++;
 		if (*s < '0' || *s > '9')
 			error = true;
 		else
 		{
 			do
 			{
-				++s;
+				s++;
 			} while (*s >= '0' && *s <= '9');
 		}
 	}
 	/* Part (4): parse optional exponent. */
 	if (*s == 'e' || *s == 'E')
 	{
-		++s;
+		s++;
 		if (*s == '+' || *s == '-')
-			++s;
+			s++;
 		if (*s < '0' || *s > '9')
 			error = true;
 		else
 		{
 			do
 			{
-				++s;
+				s++;
 			} while (*s >= '0' && *s <= '9');
 		}
 	}
 
-	/* Check for trailing garbage. */
-	for (p = s; (*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z')
-		|| (*p >= '0' && *p <= '9') || *p == '_' || IS_HIGHBIT_SET(*p); ++p)
-		;
+	/*
+	 * 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 (p > s || error)
+	if (error)
 		report_invalid_token(lex);
 }
 
 /*
  * Report a parse error.
+ *
+ * lex->token_start and lex->token_terminator must identify the current token.
  */
 static void
 report_parse_error(JsonParseStack *stack, JsonLexContext *lex)
 {
-	char   *detail = NULL;
-	char   *token = NULL;
-	int		toklen;
+	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: \"%s\"",
-					lex->input),
-	 			 errdetail_internal("The input string ended unexpectedly.")));
+				 errmsg("invalid input syntax for type json"),
+				 errdetail("The input string ended unexpectedly."),
+				 report_json_context(lex)));
 
-	/* Work out the offending token. */
+	/* Separate out the current token. */
 	toklen = lex->token_terminator - lex->token_start;
 	token = palloc(toklen + 1);
 	memcpy(token, lex->token_start, toklen);
 	token[toklen] = '\0';
 
-	/* Select correct detail message. */
+	/* Complain, with the appropriate detail message. */
 	if (stack == NULL)
-		detail = "line %d: Expected end of input, but found \"%s\".";
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+				 errmsg("invalid input syntax for type json"),
+				 errdetail("Expected end of input, but found \"%s\".",
+						   token),
+				 report_json_context(lex)));
 	else
 	{
 		switch (stack->state)
 		{
 			case JSON_PARSE_VALUE:
-				detail = "line %d: Expected string, number, object, array, true, false, or null, but found \"%s\".";
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+						 errmsg("invalid input syntax for type json"),
+						 errdetail("Expected JSON value, but found \"%s\".",
+								   token),
+						 report_json_context(lex)));
 				break;
 			case JSON_PARSE_ARRAY_START:
-				detail = "line %d: Expected array element or \"]\", but found \"%s\".";
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+						 errmsg("invalid input syntax for type json"),
+						 errdetail("Expected array element or \"]\", but found \"%s\".",
+								   token),
+						 report_json_context(lex)));
 				break;
 			case JSON_PARSE_ARRAY_NEXT:
-				detail = "line %d: Expected \",\" or \"]\", but found \"%s\".";
+				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:
-				detail = "line %d: Expected string or \"}\", but found \"%s\".";
+				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:
-				detail = "line %d: Expected \":\", but found \"%s\".";
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+						 errmsg("invalid input syntax for type json"),
+						 errdetail("Expected \":\", but found \"%s\".",
+								   token),
+						 report_json_context(lex)));
 				break;
 			case JSON_PARSE_OBJECT_NEXT:
-				detail = "line %d: Expected \",\" or \"}\", but found \"%s\".";
+				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:
-				detail = "line %d: Expected string, but found \"%s\".";
+				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;
+			default:
+				elog(ERROR, "unexpected json parse state: %d",
+					 (int) stack->state);
 		}
 	}
-
-	ereport(ERROR,
-			(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-			 errmsg("invalid input syntax for type json: \"%s\"",
-				lex->input),
- 			 errdetail_internal(detail, lex->line_number, token)));
 }
 
 /*
  * Report an invalid input token.
+ *
+ * lex->token_start and lex->token_terminator must identify the token.
  */
 static void
 report_invalid_token(JsonLexContext *lex)
 {
-	char   *token;
-	int		toklen;
+	char	   *token;
+	int			toklen;
 
+	/* Separate out the offending token. */
 	toklen = lex->token_terminator - lex->token_start;
 	token = palloc(toklen + 1);
 	memcpy(token, lex->token_start, toklen);
 	ereport(ERROR,
 			(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
 			 errmsg("invalid input syntax for type json"),
-			 errdetail_internal("line %d: Token \"%s\" is invalid.",
-				lex->line_number, token)));
+			 errdetail("Token \"%s\" is invalid.", token),
+			 report_json_context(lex)));
+}
+
+/*
+ * Report a CONTEXT line for bogus JSON input.
+ *
+ * lex->token_terminator must be set to identify the spot where we detected
+ * the error.  Note that lex->token_start might be NULL, in case we recognized
+ * error at EOF.
+ *
+ * The return value isn't meaningful, but we make it non-void so that this
+ * can be invoked inside ereport().
+ */
+static int
+report_json_context(JsonLexContext *lex)
+{
+	const char *context_start;
+	const char *context_end;
+	const char *line_start;
+	int			line_number;
+	char	   *ctxt;
+	int			ctxtlen;
+	const char *prefix;
+	const char *suffix;
+
+	/* Choose boundaries for the part of the input we will display */
+	context_start = lex->input;
+	context_end = lex->token_terminator;
+	line_start = context_start;
+	line_number = 1;
+	for (;;)
+	{
+		/* Always advance over newlines (context_end test is just paranoia) */
+		if (*context_start == '\n' && context_start < context_end)
+		{
+			context_start++;
+			line_start = context_start;
+			line_number++;
+			continue;
+		}
+		/* Otherwise, done as soon as we are close enough to context_end */
+		if (context_end - context_start < 50)
+			break;
+		/* Advance to next multibyte character */
+		if (IS_HIGHBIT_SET(*context_start))
+			context_start += pg_mblen(context_start);
+		else
+			context_start++;
+	}
+
+	/*
+	 * We add "..." to indicate that the excerpt doesn't start at the
+	 * beginning of the line ... but if we're within 3 characters of the
+	 * beginning of the line, we might as well just show the whole line.
+	 */
+	if (context_start - line_start <= 3)
+		context_start = line_start;
+
+	/* Get a null-terminated copy of the data to present */
+	ctxtlen = context_end - context_start;
+	ctxt = palloc(ctxtlen + 1);
+	memcpy(ctxt, context_start, ctxtlen);
+	ctxt[ctxtlen] = '\0';
+
+	/*
+	 * Show the context, prefixing "..." if not starting at start of line, and
+	 * 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);
 }
 
 /*
 static char *
 extract_mb_char(char *s)
 {
-	char   *res;
-	int		len;
+	char	   *res;
+	int			len;
 
 	len = pg_mblen(s);
 	res = palloc(len + 1);
 }
 
 /*
- * Turn a scalar Datum into JSON. Hand off a non-scalar datum to
- * composite_to_json or array_to_json_internal as appropriate.
+ * Turn a scalar Datum into JSON, appending the string to "result".
+ *
+ * Hand off a non-scalar datum to composite_to_json or array_to_json_internal
+ * as appropriate.
  */
-static inline void
-datum_to_json(Datum val, bool is_null, StringInfo result, TYPCATEGORY tcategory,
-			  Oid typoutputfunc)
+static void
+datum_to_json(Datum val, bool is_null, StringInfo result,
+			  TYPCATEGORY tcategory, Oid typoutputfunc)
 {
-
-	char *outputstr;
+	char	   *outputstr;
 
 	if (is_null)
 	{
-		appendStringInfoString(result,"null");
+		appendStringInfoString(result, "null");
 		return;
 	}
 
 			break;
 		case TYPCATEGORY_BOOLEAN:
 			if (DatumGetBool(val))
-				appendStringInfoString(result,"true");
+				appendStringInfoString(result, "true");
 			else
-				appendStringInfoString(result,"false");
+				appendStringInfoString(result, "false");
 			break;
 		case TYPCATEGORY_NUMERIC:
 			outputstr = OidOutputFunctionCall(typoutputfunc, val);
+
 			/*
-			 * Don't call escape_json here if it's a valid JSON
-			 * number. Numeric output should usually be a valid 
-			 * JSON number and JSON numbers shouldn't be quoted. 
-			 * Quote cases like "Nan" and "Infinity", however.
+			 * Don't call escape_json here if it's a valid JSON number.
+			 * Numeric output should usually be a valid JSON number and JSON
+			 * numbers shouldn't be quoted. Quote cases like "Nan" and
+			 * "Infinity", however.
 			 */
-			if (strpbrk(outputstr,NON_NUMERIC_LETTER) == NULL)
+			if (strpbrk(outputstr, NON_NUMERIC_LETTER) == NULL)
 				appendStringInfoString(result, outputstr);
 			else
 				escape_json(result, outputstr);
 			outputstr = OidOutputFunctionCall(typoutputfunc, val);
 			escape_json(result, outputstr);
 			pfree(outputstr);
+			break;
 	}
 }
 
  * ourselves recursively to process the next dimension.
  */
 static void
-array_dim_to_json(StringInfo result, int dim, int ndims,int * dims, Datum *vals,
-				  bool *nulls, int * valcount, TYPCATEGORY tcategory, 
+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)
 {
-
-	int i;
-	char *sep;
+	int			i;
+	const char *sep;
 
 	Assert(dim < ndims);
 
 	for (i = 1; i <= dims[dim]; i++)
 	{
 		if (i > 1)
-			appendStringInfoString(result,sep);
+			appendStringInfoString(result, sep);
 
 		if (dim + 1 == ndims)
 		{
 		else
 		{
 			/*
-			 * Do we want line feeds on inner dimensions of arrays?
-			 * For now we'll say no.
+			 * Do we want line feeds on inner dimensions of arrays? For now
+			 * we'll say no.
 			 */
-			array_dim_to_json(result, dim+1, ndims, dims, vals, nulls,
+			array_dim_to_json(result, dim + 1, ndims, dims, vals, nulls,
 							  valcount, tcategory, typoutputfunc, false);
 		}
 	}
 	int		   *dim;
 	int			ndim;
 	int			nitems;
-	int         count = 0;
+	int			count = 0;
 	Datum	   *elements;
-	bool       *nulls;
-
+	bool	   *nulls;
 	int16		typlen;
 	bool		typbyval;
 	char		typalign,
 
 	if (nitems <= 0)
 	{
-		appendStringInfoString(result,"[]");
+		appendStringInfoString(result, "[]");
 		return;
 	}
 
 static void
 composite_to_json(Datum composite, StringInfo result, bool use_line_feeds)
 {
-    HeapTupleHeader td;
-    Oid         tupType;
-    int32       tupTypmod;
-    TupleDesc   tupdesc;
-    HeapTupleData tmptup, *tuple;
-	int         i;
-	bool        needsep = false;
-	char       *sep;
+	HeapTupleHeader td;
+	Oid			tupType;
+	int32		tupTypmod;
+	TupleDesc	tupdesc;
+	HeapTupleData tmptup,
+			   *tuple;
+	int			i;
+	bool		needsep = false;
+	const char *sep;
 
 	sep = use_line_feeds ? ",\n " : ",";
 
-    td = DatumGetHeapTupleHeader(composite);
+	td = DatumGetHeapTupleHeader(composite);
 
-    /* Extract rowtype info and find a tupdesc */
-    tupType = HeapTupleHeaderGetTypeId(td);
-    tupTypmod = HeapTupleHeaderGetTypMod(td);
-    tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+	/* Extract rowtype info and find a tupdesc */
+	tupType = HeapTupleHeaderGetTypeId(td);
+	tupTypmod = HeapTupleHeaderGetTypMod(td);
+	tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
 
-    /* Build a temporary HeapTuple control structure */
-    tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
-    tmptup.t_data = td;
+	/* Build a temporary HeapTuple control structure */
+	tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
+	tmptup.t_data = td;
 	tuple = &tmptup;
 
-	appendStringInfoChar(result,'{');
+	appendStringInfoChar(result, '{');
 
-    for (i = 0; i < tupdesc->natts; i++)
-    {
-        Datum       val, origval;
-        bool        isnull;
-        char       *attname;
+	for (i = 0; i < tupdesc->natts; i++)
+	{
+		Datum		val,
+					origval;
+		bool		isnull;
+		char	   *attname;
 		TYPCATEGORY tcategory;
 		Oid			typoutput;
 		bool		typisvarlena;
 
 		if (tupdesc->attrs[i]->attisdropped)
-            continue;
+			continue;
 
 		if (needsep)
-			appendStringInfoString(result,sep);
+			appendStringInfoString(result, sep);
 		needsep = true;
 
-        attname = NameStr(tupdesc->attrs[i]->attname);
-		escape_json(result,attname);
-		appendStringInfoChar(result,':');
+		attname = NameStr(tupdesc->attrs[i]->attname);
+		escape_json(result, attname);
+		appendStringInfoChar(result, ':');
 
-        origval = heap_getattr(tuple, i + 1, tupdesc, &isnull);
+		origval = heap_getattr(tuple, i + 1, tupdesc, &isnull);
 
 		if (tupdesc->attrs[i]->atttypid == RECORDARRAYOID)
 			tcategory = TYPCATEGORY_ARRAY;
 						  &typoutput, &typisvarlena);
 
 		/*
-		 * If we have a toasted datum, forcibly detoast it here to avoid memory
-		 * leakage inside the type's output routine.
+		 * If we have a toasted datum, forcibly detoast it here to avoid
+		 * memory leakage inside the type's output routine.
 		 */
-		if (typisvarlena && ! isnull)
+		if (typisvarlena && !isnull)
 			val = PointerGetDatum(PG_DETOAST_DATUM(origval));
 		else
 			val = origval;
 			pfree(DatumGetPointer(val));
 	}
 
-	appendStringInfoChar(result,'}');
-    ReleaseTupleDesc(tupdesc);
+	appendStringInfoChar(result, '}');
+	ReleaseTupleDesc(tupdesc);
 }
 
 static inline void
 extern Datum
 array_to_json(PG_FUNCTION_ARGS)
 {
-	Datum    array = PG_GETARG_DATUM(0);
+	Datum		array = PG_GETARG_DATUM(0);
 	StringInfo	result;
 
 	get_json_oid(fcinfo);
 	array_to_json_internal(array, result, false);
 
 	PG_RETURN_TEXT_P(cstring_to_text(result->data));
-};
+}
 
 /*
  * SQL function array_to_json(row, prettybool)
 extern Datum
 array_to_json_pretty(PG_FUNCTION_ARGS)
 {
-	Datum    array = PG_GETARG_DATUM(0);
-	bool     use_line_feeds = PG_GETARG_BOOL(1);
+	Datum		array = PG_GETARG_DATUM(0);
+	bool		use_line_feeds = PG_GETARG_BOOL(1);
 	StringInfo	result;
 
 	get_json_oid(fcinfo);
 	array_to_json_internal(array, result, use_line_feeds);
 
 	PG_RETURN_TEXT_P(cstring_to_text(result->data));
-};
+}
 
 /*
  * SQL function row_to_json(row)
 extern Datum
 row_to_json(PG_FUNCTION_ARGS)
 {
-	Datum    array = PG_GETARG_DATUM(0);
+	Datum		array = PG_GETARG_DATUM(0);
 	StringInfo	result;
 
 	get_json_oid(fcinfo);
 	composite_to_json(array, result, false);
 
 	PG_RETURN_TEXT_P(cstring_to_text(result->data));
-};
+}
 
 /*
  * SQL function row_to_json(row, prettybool)
 extern Datum
 row_to_json_pretty(PG_FUNCTION_ARGS)
 {
-	Datum    array = PG_GETARG_DATUM(0);
-	bool     use_line_feeds = PG_GETARG_BOOL(1);
+	Datum		array = PG_GETARG_DATUM(0);
+	bool		use_line_feeds = PG_GETARG_BOOL(1);
 	StringInfo	result;
 
 	get_json_oid(fcinfo);
 	composite_to_json(array, result, use_line_feeds);
 
 	PG_RETURN_TEXT_P(cstring_to_text(result->data));
-};
+}
 
 /*
  * Produce a JSON string literal, properly escaping characters in the text.
 	}
 	appendStringInfoCharMacro(buf, '\"');
 }
-

File test/expected/json91.out

 ERROR:  invalid input syntax for type json
 LINE 1: SELECT $$''$$::json;
                ^
-DETAIL:  line 1: Token "'" is invalid.
+DETAIL:  Token "'" is invalid.
+CONTEXT:  JSON data, line 1: '...
 SELECT '"abc"'::json;			-- OK
  json  
 -------
 ERROR:  invalid input syntax for type json
 LINE 1: SELECT '"abc'::json;
                ^
-DETAIL:  line 1: Token ""abc" is invalid.
+DETAIL:  Token ""abc" is invalid.
+CONTEXT:  JSON data, line 1: "abc
 SELECT '"abc
 def"'::json;					-- ERROR, unescaped newline in string constant
 ERROR:  invalid input syntax for type json
 LINE 1: SELECT '"abc
                ^
-DETAIL:  line 1: Character "
-" must be escaped.
+DETAIL:  Character with value 0x0a must be escaped.
+CONTEXT:  JSON data, line 1: "abc
 SELECT '"\n\"\\"'::json;		-- OK, legal escapes
    json   
 ----------
 ERROR:  invalid input syntax for type json
 LINE 1: SELECT '"\v"'::json;
                ^
-DETAIL:  line 1: Invalid escape "\v".
+DETAIL:  Escape sequence "\v" is invalid.
+CONTEXT:  JSON data, line 1: "\v...
 SELECT '"\u"'::json;			-- ERROR, incomplete escape
 ERROR:  invalid input syntax for type json
 LINE 1: SELECT '"\u"'::json;
                ^
-DETAIL:  line 1: "\u" must be followed by four hexadecimal digits.
+DETAIL:  "\u" must be followed by four hexadecimal digits.
+CONTEXT:  JSON data, line 1: "\u"
 SELECT '"\u00"'::json;			-- ERROR, incomplete escape
 ERROR:  invalid input syntax for type json
 LINE 1: SELECT '"\u00"'::json;
                ^
-DETAIL:  line 1: "\u" must be followed by four hexadecimal digits.
+DETAIL:  "\u" must be followed by four hexadecimal digits.
+CONTEXT:  JSON data, line 1: "\u00"
 SELECT '"\u000g"'::json;		-- ERROR, g is not a hex digit
 ERROR:  invalid input syntax for type json
 LINE 1: SELECT '"\u000g"'::json;
                ^
-DETAIL:  line 1: "\u" must be followed by four hexadecimal digits.
+DETAIL:  "\u" must be followed by four hexadecimal digits.
+CONTEXT:  JSON data, line 1: "\u000g...
 SELECT '"\u0000"'::json;		-- OK, legal escape
    json   
 ----------
 ERROR:  invalid input syntax for type json
 LINE 1: SELECT '01'::json;
                ^
-DETAIL:  line 1: Token "01" is invalid.
+DETAIL:  Token "01" is invalid.
+CONTEXT:  JSON data, line 1: 01
 SELECT '0.1'::json;				-- OK
  json 
 ------
 ERROR:  invalid input syntax for type json
 LINE 1: SELECT '1f2'::json;
                ^
-DETAIL:  line 1: Token "1f2" is invalid.
+DETAIL:  Token "1f2" is invalid.
+CONTEXT:  JSON data, line 1: 1f2
 SELECT '0.x1'::json;            -- ERROR
 ERROR:  invalid input syntax for type json
 LINE 1: SELECT '0.x1'::json;
                ^
-DETAIL:  line 1: Token "0.x1" is invalid.
+DETAIL:  Token "0.x1" is invalid.
+CONTEXT:  JSON data, line 1: 0.x1
 SELECT '1.3ex100'::json;        -- ERROR
 ERROR:  invalid input syntax for type json
 LINE 1: SELECT '1.3ex100'::json;
                ^
-DETAIL:  line 1: Token "1.3ex100" is invalid.
+DETAIL:  Token "1.3ex100" is invalid.
+CONTEXT:  JSON data, line 1: 1.3ex100
 -- Arrays.
 SELECT '[]'::json;				-- OK
  json 
 (1 row)
 
 SELECT '[1,2,]'::json;			-- ERROR, trailing comma
-ERROR:  invalid input syntax for type json: "[1,2,]"
+ERROR:  invalid input syntax for type json
 LINE 1: SELECT '[1,2,]'::json;
                ^
-DETAIL:  line 1: Expected string, number, object, array, true, false, or null, but found "]".
+DETAIL:  Expected JSON value, but found "]".
+CONTEXT:  JSON data, line 1: [1,2,]
 SELECT '[1,2'::json;			-- ERROR, no closing bracket
-ERROR:  invalid input syntax for type json: "[1,2"
+ERROR:  invalid input syntax for type json
 LINE 1: SELECT '[1,2'::json;
                ^
 DETAIL:  The input string ended unexpectedly.
+CONTEXT:  JSON data, line 1: [1,2
 SELECT '[1,[2]'::json;			-- ERROR, no closing bracket
-ERROR:  invalid input syntax for type json: "[1,[2]"
+ERROR:  invalid input syntax for type json
 LINE 1: SELECT '[1,[2]'::json;
                ^
 DETAIL:  The input string ended unexpectedly.
+CONTEXT:  JSON data, line 1: [1,[2]
 -- Objects.
 SELECT '{}'::json;				-- OK
  json 
 (1 row)
 
 SELECT '{"abc"}'::json;			-- ERROR, no value
-ERROR:  invalid input syntax for type json: "{"abc"}"
+ERROR:  invalid input syntax for type json
 LINE 1: SELECT '{"abc"}'::json;
                ^
-DETAIL:  line 1: Expected ":", but found "}".
+DETAIL:  Expected ":", but found "}".
+CONTEXT:  JSON data, line 1: {"abc"}
 SELECT '{"abc":1}'::json;		-- OK
    json    
 -----------
 (1 row)
 
 SELECT '{1:"abc"}'::json;		-- ERROR, keys must be strings
-ERROR:  invalid input syntax for type json: "{1:"abc"}"
+ERROR:  invalid input syntax for type json
 LINE 1: SELECT '{1:"abc"}'::json;
                ^
-DETAIL:  line 1: Expected string or "}", but found "1".
+DETAIL:  Expected string or "}", but found "1".
+CONTEXT:  JSON data, line 1: {1...
 SELECT '{"abc",1}'::json;		-- ERROR, wrong separator
-ERROR:  invalid input syntax for type json: "{"abc",1}"
+ERROR:  invalid input syntax for type json
 LINE 1: SELECT '{"abc",1}'::json;
                ^
-DETAIL:  line 1: Expected ":", but found ",".
+DETAIL:  Expected ":", but found ",".
+CONTEXT:  JSON data, line 1: {"abc",...
 SELECT '{"abc"=1}'::json;		-- ERROR, totally wrong separator
 ERROR:  invalid input syntax for type json
 LINE 1: SELECT '{"abc"=1}'::json;
                ^
-DETAIL:  line 1: Token "=" is invalid.
+DETAIL:  Token "=" is invalid.
+CONTEXT:  JSON data, line 1: {"abc"=...
 SELECT '{"abc"::1}'::json;		-- ERROR, another wrong separator
-ERROR:  invalid input syntax for type json: "{"abc"::1}"
+ERROR:  invalid input syntax for type json
 LINE 1: SELECT '{"abc"::1}'::json;
                ^
-DETAIL:  line 1: Expected string, number, object, array, true, false, or null, but found ":".
+DETAIL:  Expected JSON value, but found ":".
+CONTEXT:  JSON data, line 1: {"abc"::...
 SELECT '{"abc":1,"def":2,"ghi":[3,4],"hij":{"klm":5,"nop":[6]}}'::json; -- OK
                           json                           
 ---------------------------------------------------------
 (1 row)
 
 SELECT '{"abc":1:2}'::json;		-- ERROR, colon in wrong spot
-ERROR:  invalid input syntax for type json: "{"abc":1:2}"
+ERROR:  invalid input syntax for type json
 LINE 1: SELECT '{"abc":1:2}'::json;
                ^
-DETAIL:  line 1: Expected "," or "}", but found ":".
+DETAIL:  Expected "," or "}", but found ":".
+CONTEXT:  JSON data, line 1: {"abc":1:...
 SELECT '{"abc":1,3}'::json;		-- ERROR, no value
-ERROR:  invalid input syntax for type json: "{"abc":1,3}"
+ERROR:  invalid input syntax for type json
 LINE 1: SELECT '{"abc":1,3}'::json;
                ^
-DETAIL:  line 1: Expected string, but found "3".
+DETAIL:  Expected string, but found "3".
+CONTEXT:  JSON data, line 1: {"abc":1,3...
 -- Miscellaneous stuff.
 SELECT 'true'::json;			-- OK
  json 
 (1 row)
 
 SELECT 'true false'::json;		-- ERROR, too many values
-ERROR:  invalid input syntax for type json: "true false"
+ERROR:  invalid input syntax for type json
 LINE 1: SELECT 'true false'::json;
                ^
-DETAIL:  line 1: Expected end of input, but found "false".
+DETAIL:  Expected end of input, but found "false".
+CONTEXT:  JSON data, line 1: true false
 SELECT 'true, false'::json;		-- ERROR, too many values
-ERROR:  invalid input syntax for type json: "true, false"
+ERROR:  invalid input syntax for type json
 LINE 1: SELECT 'true, false'::json;
                ^
-DETAIL:  line 1: Expected end of input, but found ",".
+DETAIL:  Expected end of input, but found ",".
+CONTEXT:  JSON data, line 1: true,...
 SELECT 'truf'::json;			-- ERROR, not a keyword
 ERROR:  invalid input syntax for type json
 LINE 1: SELECT 'truf'::json;
                ^
-DETAIL:  line 1: Token "truf" is invalid.
+DETAIL:  Token "truf" is invalid.
+CONTEXT:  JSON data, line 1: truf
 SELECT 'trues'::json;			-- ERROR, not a keyword
 ERROR:  invalid input syntax for type json
 LINE 1: SELECT 'trues'::json;
                ^
-DETAIL:  line 1: Token "trues" is invalid.
+DETAIL:  Token "trues" is invalid.
+CONTEXT:  JSON data, line 1: trues
 SELECT ''::json;				-- ERROR, no value
-ERROR:  invalid input syntax for type json: ""
+ERROR:  invalid input syntax for type json
 LINE 1: SELECT ''::json;
                ^
 DETAIL:  The input string ended unexpectedly.
+CONTEXT:  JSON data, line 1: 
 SELECT '    '::json;			-- ERROR, no value
-ERROR:  invalid input syntax for type json: "    "
+ERROR:  invalid input syntax for type json
 LINE 1: SELECT '    '::json;
                ^
 DETAIL:  The input string ended unexpectedly.
+CONTEXT:  JSON data, line 1:     
 --constructors
 -- array_to_json
 SELECT array_to_json(array(select 1 as a));