Skip to content

Commit ffd3944

Browse files
committed
Improve reporting for syntax errors in multi-line JSON data.
Point to the specific line where the error was detected; the previous code tended to include several preceding lines as well. Avoid re-scanning the entire input to recompute which line that was. Simplify the logic a bit. Add test cases. Simon Riggs and Hamid Akhtar, reviewed by Daniel Gustafsson and myself Discussion: https://postgr.es/m/CANbhV-EPBnXm3MF_TTWBwwqgn1a1Ghmep9VHfqmNBQ8BT0f+_g@mail.gmail.com
1 parent bd69ddf commit ffd3944

File tree

7 files changed

+113
-22
lines changed

7 files changed

+113
-22
lines changed

src/backend/utils/adt/jsonfuncs.c

+6-17
Original file line numberDiff line numberDiff line change
@@ -641,30 +641,19 @@ report_json_context(JsonLexContext *lex)
641641
const char *context_start;
642642
const char *context_end;
643643
const char *line_start;
644-
int line_number;
645644
char *ctxt;
646645
int ctxtlen;
647646
const char *prefix;
648647
const char *suffix;
649648

650649
/* Choose boundaries for the part of the input we will display */
651-
context_start = lex->input;
650+
line_start = lex->line_start;
651+
context_start = line_start;
652652
context_end = lex->token_terminator;
653-
line_start = context_start;
654-
line_number = 1;
655-
for (;;)
653+
654+
/* Advance until we are close enough to context_end */
655+
while (context_end - context_start >= 50 && context_start < context_end)
656656
{
657-
/* Always advance over newlines */
658-
if (context_start < context_end && *context_start == '\n')
659-
{
660-
context_start++;
661-
line_start = context_start;
662-
line_number++;
663-
continue;
664-
}
665-
/* Otherwise, done as soon as we are close enough to context_end */
666-
if (context_end - context_start < 50)
667-
break;
668657
/* Advance to next multibyte character */
669658
if (IS_HIGHBIT_SET(*context_start))
670659
context_start += pg_mblen(context_start);
@@ -694,7 +683,7 @@ report_json_context(JsonLexContext *lex)
694683
suffix = (lex->token_type != JSON_TOKEN_END && context_end - lex->input < lex->input_length && *context_end != '\n' && *context_end != '\r') ? "..." : "";
695684

696685
return errcontext("JSON data, line %d: %s%s%s",
697-
line_number, prefix, ctxt, suffix);
686+
lex->line_number, prefix, ctxt, suffix);
698687
}
699688

700689

src/common/jsonapi.c

+5-3
Original file line numberDiff line numberDiff line change
@@ -535,10 +535,12 @@ json_lex(JsonLexContext *lex)
535535
while (len < lex->input_length &&
536536
(*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r'))
537537
{
538-
if (*s == '\n')
538+
if (*s++ == '\n')
539+
{
539540
++lex->line_number;
540-
++s;
541-
++len;
541+
lex->line_start = s;
542+
}
543+
len++;
542544
}
543545
lex->token_start = s;
544546

src/include/common/jsonapi.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ typedef struct JsonLexContext
7979
char *prev_token_terminator;
8080
JsonTokenType token_type;
8181
int lex_level;
82-
int line_number;
83-
char *line_start;
82+
int line_number; /* line number, starting from 1 */
83+
char *line_start; /* where that line starts within input */
8484
StringInfo strval;
8585
} JsonLexContext;
8686

src/test/regress/expected/json.out

+35
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,41 @@ LINE 1: SELECT ' '::json;
272272
^
273273
DETAIL: The input string ended unexpectedly.
274274
CONTEXT: JSON data, line 1:
275+
-- Multi-line JSON input to check ERROR reporting
276+
SELECT '{
277+
"one": 1,
278+
"two":"two",
279+
"three":
280+
true}'::json; -- OK
281+
json
282+
------------------------------
283+
{ +
284+
"one": 1, +
285+
"two":"two",+
286+
"three": +
287+
true}
288+
(1 row)
289+
290+
SELECT '{
291+
"one": 1,
292+
"two":,"two", -- ERROR extraneous comma before field "two"
293+
"three":
294+
true}'::json;
295+
ERROR: invalid input syntax for type json
296+
LINE 1: SELECT '{
297+
^
298+
DETAIL: Expected JSON value, but found ",".
299+
CONTEXT: JSON data, line 3: "two":,...
300+
SELECT '{
301+
"one": 1,
302+
"two":"two",
303+
"averyveryveryveryveryveryveryveryveryverylongfieldname":}'::json;
304+
ERROR: invalid input syntax for type json
305+
LINE 1: SELECT '{
306+
^
307+
DETAIL: Expected JSON value, but found "}".
308+
CONTEXT: JSON data, line 4: ...yveryveryveryveryveryveryveryverylongfieldname":}
309+
-- ERROR missing value for last field
275310
--constructors
276311
-- array_to_json
277312
SELECT array_to_json(array(select 1 as a));

src/test/regress/expected/jsonb.out

+31
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,37 @@ LINE 1: SELECT ' '::jsonb;
272272
^
273273
DETAIL: The input string ended unexpectedly.
274274
CONTEXT: JSON data, line 1:
275+
-- Multi-line JSON input to check ERROR reporting
276+
SELECT '{
277+
"one": 1,
278+
"two":"two",
279+
"three":
280+
true}'::jsonb; -- OK
281+
jsonb
282+
-----------------------------------------
283+
{"one": 1, "two": "two", "three": true}
284+
(1 row)
285+
286+
SELECT '{
287+
"one": 1,
288+
"two":,"two", -- ERROR extraneous comma before field "two"
289+
"three":
290+
true}'::jsonb;
291+
ERROR: invalid input syntax for type json
292+
LINE 1: SELECT '{
293+
^
294+
DETAIL: Expected JSON value, but found ",".
295+
CONTEXT: JSON data, line 3: "two":,...
296+
SELECT '{
297+
"one": 1,
298+
"two":"two",
299+
"averyveryveryveryveryveryveryveryveryverylongfieldname":}'::jsonb;
300+
ERROR: invalid input syntax for type json
301+
LINE 1: SELECT '{
302+
^
303+
DETAIL: Expected JSON value, but found "}".
304+
CONTEXT: JSON data, line 4: ...yveryveryveryveryveryveryveryverylongfieldname":}
305+
-- ERROR missing value for last field
275306
-- make sure jsonb is passed through json generators without being escaped
276307
SELECT array_to_json(ARRAY [jsonb '{"a":1}', jsonb '{"b":[2,3]}']);
277308
array_to_json

src/test/regress/sql/json.sql

+17
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,23 @@ SELECT 'trues'::json; -- ERROR, not a keyword
5959
SELECT ''::json; -- ERROR, no value
6060
SELECT ' '::json; -- ERROR, no value
6161

62+
-- Multi-line JSON input to check ERROR reporting
63+
SELECT '{
64+
"one": 1,
65+
"two":"two",
66+
"three":
67+
true}'::json; -- OK
68+
SELECT '{
69+
"one": 1,
70+
"two":,"two", -- ERROR extraneous comma before field "two"
71+
"three":
72+
true}'::json;
73+
SELECT '{
74+
"one": 1,
75+
"two":"two",
76+
"averyveryveryveryveryveryveryveryveryverylongfieldname":}'::json;
77+
-- ERROR missing value for last field
78+
6279
--constructors
6380
-- array_to_json
6481

src/test/regress/sql/jsonb.sql

+17
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,23 @@ SELECT 'trues'::jsonb; -- ERROR, not a keyword
5959
SELECT ''::jsonb; -- ERROR, no value
6060
SELECT ' '::jsonb; -- ERROR, no value
6161

62+
-- Multi-line JSON input to check ERROR reporting
63+
SELECT '{
64+
"one": 1,
65+
"two":"two",
66+
"three":
67+
true}'::jsonb; -- OK
68+
SELECT '{
69+
"one": 1,
70+
"two":,"two", -- ERROR extraneous comma before field "two"
71+
"three":
72+
true}'::jsonb;
73+
SELECT '{
74+
"one": 1,
75+
"two":"two",
76+
"averyveryveryveryveryveryveryveryveryverylongfieldname":}'::jsonb;
77+
-- ERROR missing value for last field
78+
6279
-- make sure jsonb is passed through json generators without being escaped
6380
SELECT array_to_json(ARRAY [jsonb '{"a":1}', jsonb '{"b":[2,3]}']);
6481

0 commit comments

Comments
 (0)