Commit ffd3944a authored by Tom Lane's avatar Tom Lane

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
parent bd69ddfc
...@@ -641,30 +641,19 @@ report_json_context(JsonLexContext *lex) ...@@ -641,30 +641,19 @@ report_json_context(JsonLexContext *lex)
const char *context_start; const char *context_start;
const char *context_end; const char *context_end;
const char *line_start; const char *line_start;
int line_number;
char *ctxt; char *ctxt;
int ctxtlen; int ctxtlen;
const char *prefix; const char *prefix;
const char *suffix; const char *suffix;
/* Choose boundaries for the part of the input we will display */ /* 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; context_end = lex->token_terminator;
line_start = context_start;
line_number = 1; /* Advance until we are close enough to context_end */
for (;;) 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 */ /* Advance to next multibyte character */
if (IS_HIGHBIT_SET(*context_start)) if (IS_HIGHBIT_SET(*context_start))
context_start += pg_mblen(context_start); context_start += pg_mblen(context_start);
...@@ -694,7 +683,7 @@ report_json_context(JsonLexContext *lex) ...@@ -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') ? "..." : ""; 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", return errcontext("JSON data, line %d: %s%s%s",
line_number, prefix, ctxt, suffix); lex->line_number, prefix, ctxt, suffix);
} }
......
...@@ -535,10 +535,12 @@ json_lex(JsonLexContext *lex) ...@@ -535,10 +535,12 @@ json_lex(JsonLexContext *lex)
while (len < lex->input_length && while (len < lex->input_length &&
(*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r')) (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r'))
{ {
if (*s == '\n') if (*s++ == '\n')
{
++lex->line_number; ++lex->line_number;
++s; lex->line_start = s;
++len; }
len++;
} }
lex->token_start = s; lex->token_start = s;
......
...@@ -79,8 +79,8 @@ typedef struct JsonLexContext ...@@ -79,8 +79,8 @@ typedef struct JsonLexContext
char *prev_token_terminator; char *prev_token_terminator;
JsonTokenType token_type; JsonTokenType token_type;
int lex_level; int lex_level;
int line_number; int line_number; /* line number, starting from 1 */
char *line_start; char *line_start; /* where that line starts within input */
StringInfo strval; StringInfo strval;
} JsonLexContext; } JsonLexContext;
......
...@@ -272,6 +272,41 @@ LINE 1: SELECT ' '::json; ...@@ -272,6 +272,41 @@ LINE 1: SELECT ' '::json;
^ ^
DETAIL: The input string ended unexpectedly. DETAIL: The input string ended unexpectedly.
CONTEXT: JSON data, line 1: 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 --constructors
-- array_to_json -- array_to_json
SELECT array_to_json(array(select 1 as a)); SELECT array_to_json(array(select 1 as a));
......
...@@ -272,6 +272,37 @@ LINE 1: SELECT ' '::jsonb; ...@@ -272,6 +272,37 @@ LINE 1: SELECT ' '::jsonb;
^ ^
DETAIL: The input string ended unexpectedly. DETAIL: The input string ended unexpectedly.
CONTEXT: JSON data, line 1: 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 -- make sure jsonb is passed through json generators without being escaped
SELECT array_to_json(ARRAY [jsonb '{"a":1}', jsonb '{"b":[2,3]}']); SELECT array_to_json(ARRAY [jsonb '{"a":1}', jsonb '{"b":[2,3]}']);
array_to_json array_to_json
......
...@@ -59,6 +59,23 @@ SELECT 'trues'::json; -- ERROR, not a keyword ...@@ -59,6 +59,23 @@ SELECT 'trues'::json; -- ERROR, not a keyword
SELECT ''::json; -- ERROR, no value SELECT ''::json; -- ERROR, no value
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 --constructors
-- array_to_json -- array_to_json
......
...@@ -59,6 +59,23 @@ SELECT 'trues'::jsonb; -- ERROR, not a keyword ...@@ -59,6 +59,23 @@ SELECT 'trues'::jsonb; -- ERROR, not a keyword
SELECT ''::jsonb; -- ERROR, no value SELECT ''::jsonb; -- ERROR, no value
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 -- make sure jsonb is passed through json generators without being escaped
SELECT array_to_json(ARRAY [jsonb '{"a":1}', jsonb '{"b":[2,3]}']); SELECT array_to_json(ARRAY [jsonb '{"a":1}', jsonb '{"b":[2,3]}']);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment