commit 156d4fea7a379ad72bcc4ad84faba14c70c21355
Author: Simon Riggs <simon.riggs@enterprisedb.com>
Date:   Tue Jan 19 20:25:55 2021 +0000

Fix and improve context reporting for JSON parsing errors.

Use the original input line number to report JSON parsing errors
and reduce the amount of extraneous context for mult-line issues.

Add tests for invalid multi-line JSON to illustrate problem.

These changes mean a JSON error such as this

{
 "one": 1,
 "two":,"two",		<-- extra comma
 "three": true 
}

which was previously reported as

CONTEXT:  JSON data, line 1: {
"one": 1,
"two":,...

but is now reported as

CONTEXT:  JSON data, line 3: "two":,...

Backpatch to all supported versions, with suitable modifications.

diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index a794a7df84..2ff0159ad3 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -638,17 +638,15 @@ 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_start = lex->input + lex->pos_last_linefeed;
 	context_end = lex->token_terminator;
 	line_start = context_start;
-	line_number = 1;
 	for (;;)
 	{
 		/* Always advance over newlines */
@@ -656,7 +654,6 @@ report_json_context(JsonLexContext *lex)
 		{
 			context_start++;
 			line_start = context_start;
-			line_number++;
 			continue;
 		}
 		/* Otherwise, done as soon as we are close enough to context_end */
@@ -691,7 +688,7 @@ report_json_context(JsonLexContext *lex)
 	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);
+					  lex->line_number, prefix, ctxt, suffix);
 }
 
 
diff --git a/src/common/jsonapi.c b/src/common/jsonapi.c
index 831a44a2da..648e85cbe2 100644
--- a/src/common/jsonapi.c
+++ b/src/common/jsonapi.c
@@ -158,6 +158,7 @@ makeJsonLexContextCstringLen(char *json, int len, int encoding, bool need_escape
 
 	lex->input = lex->token_terminator = lex->line_start = json;
 	lex->line_number = 1;
+	lex->pos_last_linefeed = 0;
 	lex->input_length = len;
 	lex->input_encoding = encoding;
 	if (need_escapes)
@@ -536,7 +537,10 @@ json_lex(JsonLexContext *lex)
 		   (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r'))
 	{
 		if (*s == '\n')
+		{
 			++lex->line_number;
+			lex->pos_last_linefeed = len;
+		}
 		++s;
 		++len;
 	}
diff --git a/src/include/common/jsonapi.h b/src/include/common/jsonapi.h
index 03331f6d13..3baef0411c 100644
--- a/src/include/common/jsonapi.h
+++ b/src/include/common/jsonapi.h
@@ -79,6 +79,7 @@ typedef struct JsonLexContext
 	char	   *prev_token_terminator;
 	JsonTokenType token_type;
 	int			lex_level;
+	int			pos_last_linefeed;
 	int			line_number;
 	char	   *line_start;
 	StringInfo	strval;
diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out
index c4156cf2a6..ff15acf3c0 100644
--- a/src/test/regress/expected/json.out
+++ b/src/test/regress/expected/json.out
@@ -272,6 +272,42 @@ LINE 1: SELECT '    '::json;
                ^
 DETAIL:  The input string ended unexpectedly.
 CONTEXT:  JSON data, line 1:     
+-- Multi-line JSON input to check ERROR reporting
+SELECT jsonb_pretty('{
+		"one": 1,
+		"two":"two",
+		"three":
+			true}'::jsonb); -- OK
+   jsonb_pretty    
+-------------------
+ {                +
+     "one": 1,    +
+     "two": "two",+
+     "three": true+
+ }
+(1 row)
+
+SELECT jsonb_pretty('{
+		"one": 1,
+		"two":,"two", -- ERROR extraneous comma before field "two" value, line 3
+		"three":
+			true}'::jsonb);
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT jsonb_pretty('{
+                            ^
+DETAIL:  Expected JSON value, but found ",".
+CONTEXT:  JSON data, line 3: 		"two":,...
+SELECT jsonb_pretty('{
+		"one": 1,
+		"two":"two",
+		"three":
+			}'::jsonb); -- ERROR missing value for field "three", line 5
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT jsonb_pretty('{
+                            ^
+DETAIL:  Expected JSON value, but found "}".
+CONTEXT:  JSON data, line 5: 			}
+-- ERROR is on line 5 because a LF \n is legal whitespace at end of line 4, so the missing syntax is on line 5
 --constructors
 -- array_to_json
 SELECT array_to_json(array(select 1 as a));
diff --git a/src/test/regress/sql/json.sql b/src/test/regress/sql/json.sql
index 20354f04e3..e15697c95a 100644
--- a/src/test/regress/sql/json.sql
+++ b/src/test/regress/sql/json.sql
@@ -59,6 +59,24 @@ SELECT 'trues'::json;			-- ERROR, not a keyword
 SELECT ''::json;				-- ERROR, no value
 SELECT '    '::json;			-- ERROR, no value
 
+-- Multi-line JSON input to check ERROR reporting
+SELECT jsonb_pretty('{
+		"one": 1,
+		"two":"two",
+		"three":
+			true}'::jsonb); -- OK
+SELECT jsonb_pretty('{
+		"one": 1,
+		"two":,"two", -- ERROR extraneous comma before field "two" value, line 3
+		"three":
+			true}'::jsonb);
+SELECT jsonb_pretty('{
+		"one": 1,
+		"two":"two",
+		"three":
+			}'::jsonb); -- ERROR missing value for field "three", line 5
+-- ERROR is on line 5 because a LF \n is legal whitespace at end of line 4, so the missing syntax is on line 5
+
 --constructors
 -- array_to_json
 
