From 475aedd1ef0c0f9fc9d675dd2286380d14804975 Mon Sep 17 00:00:00 2001 From: Tom Lane <tgl@sss.pgh.pa.us> Date: Tue, 2 Dec 2014 18:23:16 -0500 Subject: [PATCH] Improve error messages for malformed array input strings. Make the error messages issued by array_in() uniformly follow the style ERROR: malformed array literal: "actual input string" DETAIL: specific complaint here and rewrite many of the specific complaints to be clearer. The immediate motivation for doing this is a complaint from Josh Berkus that json_to_record() produced an unintelligible error message when dealing with an array item, because it tries to feed the JSON-format array value to array_in(). Really it ought to be smart enough to perform JSON-to-Postgres array conversion, but that's a future feature not a bug fix. In the meantime, this change is something we agreed we could back-patch into 9.4, and it should help de-confuse things a bit. --- src/backend/utils/adt/arrayfuncs.c | 76 +++++++++++++++++-------- src/pl/plperl/expected/plperl_array.out | 3 +- src/test/regress/expected/arrays.out | 6 ++ 3 files changed, 60 insertions(+), 25 deletions(-) diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 743351b95e0..933c6b035c6 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -247,11 +247,13 @@ array_in(PG_FUNCTION_ARGS) errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)", ndim + 1, MAXDIM))); - for (q = p; isdigit((unsigned char) *q) || (*q == '-') || (*q == '+'); q++); + for (q = p; isdigit((unsigned char) *q) || (*q == '-') || (*q == '+'); q++) + /* skip */ ; if (q == p) /* no digits? */ ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("missing dimension value"))); + errmsg("malformed array literal: \"%s\"", string), + errdetail("\"[\" must introduce explicitly-specified array dimensions."))); if (*q == ':') { @@ -259,11 +261,13 @@ array_in(PG_FUNCTION_ARGS) *q = '\0'; lBound[ndim] = atoi(p); p = q + 1; - for (q = p; isdigit((unsigned char) *q) || (*q == '-') || (*q == '+'); q++); + for (q = p; isdigit((unsigned char) *q) || (*q == '-') || (*q == '+'); q++) + /* skip */ ; if (q == p) /* no digits? */ ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("missing dimension value"))); + errmsg("malformed array literal: \"%s\"", string), + errdetail("Missing array dimension value."))); } else { @@ -273,7 +277,9 @@ array_in(PG_FUNCTION_ARGS) if (*q != ']') ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("missing \"]\" in array dimensions"))); + errmsg("malformed array literal: \"%s\"", string), + errdetail("Missing \"%s\" after array dimensions.", + "]"))); *q = '\0'; ub = atoi(p); @@ -293,7 +299,8 @@ array_in(PG_FUNCTION_ARGS) if (*p != '{') ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("array value must start with \"{\" or dimension information"))); + errmsg("malformed array literal: \"%s\"", string), + errdetail("Array value must start with \"{\" or dimension information."))); ndim = ArrayCount(p, dim, typdelim); for (i = 0; i < ndim; i++) lBound[i] = 1; @@ -307,7 +314,9 @@ array_in(PG_FUNCTION_ARGS) if (strncmp(p, ASSGN, strlen(ASSGN)) != 0) ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("missing assignment operator"))); + errmsg("malformed array literal: \"%s\"", string), + errdetail("Missing \"%s\" after array dimensions.", + ASSGN))); p += strlen(ASSGN); while (array_isspace(*p)) p++; @@ -319,18 +328,21 @@ array_in(PG_FUNCTION_ARGS) if (*p != '{') ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("array value must start with \"{\" or dimension information"))); + errmsg("malformed array literal: \"%s\"", string), + errdetail("Array contents must start with \"{\"."))); ndim_braces = ArrayCount(p, dim_braces, typdelim); if (ndim_braces != ndim) ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("array dimensions incompatible with array literal"))); + errmsg("malformed array literal: \"%s\"", string), + errdetail("Specified array dimensions do not match array contents."))); for (i = 0; i < ndim; ++i) { if (dim[i] != dim_braces[i]) ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("array dimensions incompatible with array literal"))); + errmsg("malformed array literal: \"%s\"", string), + errdetail("Specified array dimensions do not match array contents."))); } } @@ -460,7 +472,8 @@ ArrayCount(const char *str, int *dim, char typdelim) /* Signal a premature end of the string */ ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("malformed array literal: \"%s\"", str))); + errmsg("malformed array literal: \"%s\"", str), + errdetail("Unexpected end of input."))); break; case '\\': @@ -475,7 +488,9 @@ ArrayCount(const char *str, int *dim, char typdelim) parse_state != ARRAY_ELEM_DELIMITED) ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("malformed array literal: \"%s\"", str))); + errmsg("malformed array literal: \"%s\"", str), + errdetail("Unexpected \"%c\" character.", + '\\'))); if (parse_state != ARRAY_QUOTED_ELEM_STARTED) parse_state = ARRAY_ELEM_STARTED; /* skip the escaped character */ @@ -484,7 +499,8 @@ ArrayCount(const char *str, int *dim, char typdelim) else ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("malformed array literal: \"%s\"", str))); + errmsg("malformed array literal: \"%s\"", str), + errdetail("Unexpected end of input."))); break; case '\"': @@ -498,7 +514,8 @@ ArrayCount(const char *str, int *dim, char typdelim) parse_state != ARRAY_ELEM_DELIMITED) ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("malformed array literal: \"%s\"", str))); + errmsg("malformed array literal: \"%s\"", str), + errdetail("Unexpected array element."))); in_quotes = !in_quotes; if (in_quotes) parse_state = ARRAY_QUOTED_ELEM_STARTED; @@ -518,7 +535,9 @@ ArrayCount(const char *str, int *dim, char typdelim) parse_state != ARRAY_LEVEL_DELIMITED) ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("malformed array literal: \"%s\"", str))); + errmsg("malformed array literal: \"%s\"", str), + errdetail("Unexpected \"%c\" character.", + '{'))); parse_state = ARRAY_LEVEL_STARTED; if (nest_level >= MAXDIM) ereport(ERROR, @@ -546,21 +565,25 @@ ArrayCount(const char *str, int *dim, char typdelim) !(nest_level == 1 && parse_state == ARRAY_LEVEL_STARTED)) ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("malformed array literal: \"%s\"", str))); + errmsg("malformed array literal: \"%s\"", str), + errdetail("Unexpected \"%c\" character.", + '}'))); parse_state = ARRAY_LEVEL_COMPLETED; if (nest_level == 0) ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("malformed array literal: \"%s\"", str))); + errmsg("malformed array literal: \"%s\"", str), + errdetail("Unmatched \"%c\" character.", '}'))); nest_level--; if (nelems_last[nest_level] != 0 && nelems[nest_level] != nelems_last[nest_level]) ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("multidimensional arrays must have " - "array expressions with matching " - "dimensions"))); + errmsg("malformed array literal: \"%s\"", str), + errdetail("Multidimensional arrays must have " + "sub-arrays with matching " + "dimensions."))); nelems_last[nest_level] = nelems[nest_level]; nelems[nest_level] = 1; if (nest_level == 0) @@ -591,7 +614,9 @@ ArrayCount(const char *str, int *dim, char typdelim) parse_state != ARRAY_LEVEL_COMPLETED) ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("malformed array literal: \"%s\"", str))); + errmsg("malformed array literal: \"%s\"", str), + errdetail("Unexpected \"%c\" character.", + typdelim))); if (parse_state == ARRAY_LEVEL_COMPLETED) parse_state = ARRAY_LEVEL_DELIMITED; else @@ -612,7 +637,8 @@ ArrayCount(const char *str, int *dim, char typdelim) parse_state != ARRAY_ELEM_DELIMITED) ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("malformed array literal: \"%s\"", str))); + errmsg("malformed array literal: \"%s\"", str), + errdetail("Unexpected array element."))); parse_state = ARRAY_ELEM_STARTED; } } @@ -631,7 +657,8 @@ ArrayCount(const char *str, int *dim, char typdelim) if (!array_isspace(*ptr++)) ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("malformed array literal: \"%s\"", str))); + errmsg("malformed array literal: \"%s\"", str), + errdetail("Junk after closing right brace."))); } /* special case for an empty array */ @@ -718,7 +745,8 @@ ReadArrayStr(char *arrayStr, * character. * * The error checking in this routine is mostly pro-forma, since we expect - * that ArrayCount() already validated the string. + * that ArrayCount() already validated the string. So we don't bother + * with errdetail messages. */ srcptr = arrayStr; while (!eoArray) diff --git a/src/pl/plperl/expected/plperl_array.out b/src/pl/plperl/expected/plperl_array.out index d21c84bd98a..6347b5211d2 100644 --- a/src/pl/plperl/expected/plperl_array.out +++ b/src/pl/plperl/expected/plperl_array.out @@ -65,9 +65,10 @@ ERROR: number of array dimensions (7) exceeds the maximum allowed (6) LINE 1: select plperl_sum_array('{{{{{{{1,2},{3,4}},{{5,6},{7,8}}},{... ^ select plperl_sum_array('{{{1,2,3}, {4,5,6,7}}, {{7,8,9}, {10, 11, 12}}}'); -ERROR: multidimensional arrays must have array expressions with matching dimensions +ERROR: malformed array literal: "{{{1,2,3}, {4,5,6,7}}, {{7,8,9}, {10, 11, 12}}}" LINE 1: select plperl_sum_array('{{{1,2,3}, {4,5,6,7}}, {{7,8,9}, {1... ^ +DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions. CREATE OR REPLACE FUNCTION plperl_concat(TEXT[]) RETURNS TEXT AS $$ my $array_arg = shift; my $result = ""; diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index cb606afd9cf..d33c9b90fff 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -1065,26 +1065,32 @@ select '{{1,{2}},{2,3}}'::text[]; ERROR: malformed array literal: "{{1,{2}},{2,3}}" LINE 1: select '{{1,{2}},{2,3}}'::text[]; ^ +DETAIL: Unexpected "{" character. select '{{},{}}'::text[]; ERROR: malformed array literal: "{{},{}}" LINE 1: select '{{},{}}'::text[]; ^ +DETAIL: Unexpected "}" character. select E'{{1,2},\\{2,3}}'::text[]; ERROR: malformed array literal: "{{1,2},\{2,3}}" LINE 1: select E'{{1,2},\\{2,3}}'::text[]; ^ +DETAIL: Unexpected "\" character. select '{{"1 2" x},{3}}'::text[]; ERROR: malformed array literal: "{{"1 2" x},{3}}" LINE 1: select '{{"1 2" x},{3}}'::text[]; ^ +DETAIL: Unexpected array element. select '{}}'::text[]; ERROR: malformed array literal: "{}}" LINE 1: select '{}}'::text[]; ^ +DETAIL: Junk after closing right brace. select '{ }}'::text[]; ERROR: malformed array literal: "{ }}" LINE 1: select '{ }}'::text[]; ^ +DETAIL: Junk after closing right brace. select array[]; ERROR: cannot determine type of empty array LINE 1: select array[]; -- GitLab