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

Lines changed: 6 additions & 17 deletions
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

Lines changed: 5 additions & 3 deletions
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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 35 additions & 0 deletions
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

Lines changed: 31 additions & 0 deletions
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

Lines changed: 17 additions & 0 deletions
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

Lines changed: 17 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy