Improve reporting for syntax errors in multi-line JSON data.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 1 Mar 2021 21:44:17 +0000 (16:44 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 1 Mar 2021 21:44:17 +0000 (16:44 -0500)
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

src/backend/utils/adt/jsonfuncs.c
src/common/jsonapi.c
src/include/common/jsonapi.h
src/test/regress/expected/json.out
src/test/regress/expected/jsonb.out
src/test/regress/sql/json.sql
src/test/regress/sql/jsonb.sql

index f194ff911b0bf17fe3b70c8b48bfc6d629601de6..511467280f26243d6d45fa2a69333fbbdcd6dd5d 100644 (file)
@@ -641,30 +641,19 @@ 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;
+       line_start = lex->line_start;
+       context_start = line_start;
        context_end = lex->token_terminator;
-       line_start = context_start;
-       line_number = 1;
-       for (;;)
+
+       /* Advance until we are close enough to context_end */
+       while (context_end - context_start >= 50 && context_start < context_end)
        {
-               /* Always advance over newlines */
-               if (context_start < context_end && *context_start == '\n')
-               {
-                       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);
@@ -694,7 +683,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);
 }
 
 
index 831a44a2da632f4349463f537e1cd024237208ad..1bf38d7b4295e40355d5efe1fb24ae83f34ee10f 100644 (file)
@@ -535,10 +535,12 @@ json_lex(JsonLexContext *lex)
        while (len < lex->input_length &&
                   (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r'))
        {
-               if (*s == '\n')
+               if (*s++ == '\n')
+               {
                        ++lex->line_number;
-               ++s;
-               ++len;
+                       lex->line_start = s;
+               }
+               len++;
        }
        lex->token_start = s;
 
index 03331f6d13fadef4ad342b3167a3d247e1a58920..ec3dfce9c32b3fb5caa204bdfd91fe1078394bec 100644 (file)
@@ -79,8 +79,8 @@ typedef struct JsonLexContext
        char       *prev_token_terminator;
        JsonTokenType token_type;
        int                     lex_level;
-       int                     line_number;
-       char       *line_start;
+       int                     line_number;    /* line number, starting from 1 */
+       char       *line_start;         /* where that line starts within input */
        StringInfo      strval;
 } JsonLexContext;
 
index c4156cf2a66f96cb495b347d5fa37818ad02f188..e9d6e9faf290584e6813cd2ceea02afe5b05212a 100644 (file)
@@ -272,6 +272,41 @@ LINE 1: SELECT '    '::json;
                ^
 DETAIL:  The input string ended unexpectedly.
 CONTEXT:  JSON data, line 1:     
+-- Multi-line JSON input to check ERROR reporting
+SELECT '{
+               "one": 1,
+               "two":"two",
+               "three":
+               true}'::json; -- OK
+             json             
+------------------------------
+ {                           +
+                 "one": 1,   +
+                 "two":"two",+
+                 "three":    +
+                 true}
+(1 row)
+
+SELECT '{
+               "one": 1,
+               "two":,"two",  -- ERROR extraneous comma before field "two"
+               "three":
+               true}'::json;
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '{
+               ^
+DETAIL:  Expected JSON value, but found ",".
+CONTEXT:  JSON data, line 3:           "two":,...
+SELECT '{
+               "one": 1,
+               "two":"two",
+               "averyveryveryveryveryveryveryveryveryverylongfieldname":}'::json;
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '{
+               ^
+DETAIL:  Expected JSON value, but found "}".
+CONTEXT:  JSON data, line 4: ...yveryveryveryveryveryveryveryverylongfieldname":}
+-- ERROR missing value for last field
 --constructors
 -- array_to_json
 SELECT array_to_json(array(select 1 as a));
index cf0ce0e44cce05072ae53d0ee4a1869c88fe1aeb..1add673968bfdad6b05c83746371c815e53197f2 100644 (file)
@@ -272,6 +272,37 @@ LINE 1: SELECT '    '::jsonb;
                ^
 DETAIL:  The input string ended unexpectedly.
 CONTEXT:  JSON data, line 1:     
+-- Multi-line JSON input to check ERROR reporting
+SELECT '{
+               "one": 1,
+               "two":"two",
+               "three":
+               true}'::jsonb; -- OK
+                  jsonb                  
+-----------------------------------------
+ {"one": 1, "two": "two", "three": true}
+(1 row)
+
+SELECT '{
+               "one": 1,
+               "two":,"two",  -- ERROR extraneous comma before field "two"
+               "three":
+               true}'::jsonb;
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '{
+               ^
+DETAIL:  Expected JSON value, but found ",".
+CONTEXT:  JSON data, line 3:           "two":,...
+SELECT '{
+               "one": 1,
+               "two":"two",
+               "averyveryveryveryveryveryveryveryveryverylongfieldname":}'::jsonb;
+ERROR:  invalid input syntax for type json
+LINE 1: SELECT '{
+               ^
+DETAIL:  Expected JSON value, but found "}".
+CONTEXT:  JSON data, line 4: ...yveryveryveryveryveryveryveryverylongfieldname":}
+-- ERROR missing value for last field
 -- make sure jsonb is passed through json generators without being escaped
 SELECT array_to_json(ARRAY [jsonb '{"a":1}', jsonb '{"b":[2,3]}']);
       array_to_json       
index 20354f04e3745d80be8b1317797df191b55c5528..e366c6f51b65bdf128034090cc1a9192107d8092 100644 (file)
@@ -59,6 +59,23 @@ 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 '{
+               "one": 1,
+               "two":"two",
+               "three":
+               true}'::json; -- OK
+SELECT '{
+               "one": 1,
+               "two":,"two",  -- ERROR extraneous comma before field "two"
+               "three":
+               true}'::json;
+SELECT '{
+               "one": 1,
+               "two":"two",
+               "averyveryveryveryveryveryveryveryveryverylongfieldname":}'::json;
+-- ERROR missing value for last field
+
 --constructors
 -- array_to_json
 
index 1a9d21741fa5e010f2809c0e734ce762366f66c0..5016f29c15ad73c59f1e340a3dddde3cb73be01b 100644 (file)
@@ -59,6 +59,23 @@ SELECT 'trues'::jsonb;                       -- ERROR, not a keyword
 SELECT ''::jsonb;                              -- ERROR, no value
 SELECT '    '::jsonb;                  -- ERROR, no value
 
+-- Multi-line JSON input to check ERROR reporting
+SELECT '{
+               "one": 1,
+               "two":"two",
+               "three":
+               true}'::jsonb; -- OK
+SELECT '{
+               "one": 1,
+               "two":,"two",  -- ERROR extraneous comma before field "two"
+               "three":
+               true}'::jsonb;
+SELECT '{
+               "one": 1,
+               "two":"two",
+               "averyveryveryveryveryveryveryveryveryverylongfieldname":}'::jsonb;
+-- ERROR missing value for last field
+
 -- make sure jsonb is passed through json generators without being escaped
 SELECT array_to_json(ARRAY [jsonb '{"a":1}', jsonb '{"b":[2,3]}']);