From 67ebe74ea3b77e2a806afeb9ca9d46b7be579edd Mon Sep 17 00:00:00 2001
From: pgaddict <jian.universality@gmail.com>
Date: Wed, 13 Sep 2023 16:31:40 +0800
Subject: [PATCH v8 7/7] refactor ReadArrayStr.

array ending errors(unnessary junk) can happen in many cases, like
inside double or not, with backslash or not.
so conslidate it, using a goto ending_errror.
add more tests, also cover doule quote cases.
expect_delim state is not enought for case like '{{},}'. You need tracking
current token and previous token to solve cases where a delimiter followed by
a closing brace.
---
 src/backend/utils/adt/arrayfuncs.c   | 66 +++++++++++-----------------
 src/test/regress/expected/arrays.out | 38 ++++++++++++++++
 src/test/regress/sql/arrays.sql      |  9 ++++
 3 files changed, 72 insertions(+), 41 deletions(-)

diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 95a3d5f6..bb514d68 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -305,14 +305,14 @@ array_in(PG_FUNCTION_ARGS)
 
 		initStringInfo(&buf);
 
-		appendStringInfo(&buf, "array_in- ndim %d (", ndim);
+		appendStringInfo(&buf, "ndim %d (", ndim);
 		for (int i = 0; i < MAXDIM; i++)
 			appendStringInfo(&buf, " %d", dim[i]);
-		appendStringInfo(&buf, "lBound info");
+		appendStringInfo(&buf, "); lBound info:");
 		for (int i = 0; i < MAXDIM; i++)
 			appendStringInfo(&buf, " %d", lBound[i]);
 		appendStringInfo(&buf, ") for %s\n", string);
-		elog(NOTICE, "%s", buf.data);
+		elog(DEBUG1, "%s", buf.data);
 		pfree(buf.data);
 	}
 #endif
@@ -596,7 +596,7 @@ ReadArrayStr(char **srcptr,
 	bool		dimensions_specified = ndim != 0;
 	bool		ndim_frozen;
 	int			nelems[MAXDIM];
-
+	ArrayToken	prev_tok;
 	/* The caller already checked this */
 	Assert(**srcptr == '{');
 
@@ -622,6 +622,7 @@ ReadArrayStr(char **srcptr,
 	expect_delim = false;
 	nest_level = 0;
 	nitems = 0;
+	prev_tok = ATOK_LEVEL_START;	/* initialize it */
 	do
 	{
 		ArrayToken	tok;
@@ -655,7 +656,8 @@ ReadArrayStr(char **srcptr,
 				}
 				break;
 			case ATOK_LEVEL_END:
-				if (nest_level == 0)
+				/* cannot precede with a delim. nest_level should larger than 0 */
+				if (prev_tok == ATOK_DELIM || nest_level == 0)
 					ereturn(escontext, false,
 							(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
 							 errmsg("malformed array literal: \"%s\"", origStr),
@@ -739,6 +741,7 @@ ReadArrayStr(char **srcptr,
 			case ATOK_ERROR:
 				return false;
 		}
+		prev_tok = tok;
 	} while (nest_level > 0);
 
 	pfree(elembuf);
@@ -788,12 +791,7 @@ ReadArrayToken(char **srcptr, char *elembuf, char typdelim,
 		switch (*p)
 		{
 			case '\0':
-				errsave(escontext,
-						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-						 errmsg("malformed array literal: \"%s\"",
-								origStr)));
-				*srcptr = p;
-				return ATOK_ERROR;
+				goto ending_error;
 			case '{':
 				*srcptr = p + 1;
 				return ATOK_LEVEL_START;
@@ -825,24 +823,12 @@ quoted_element:
 		switch (*p)
 		{
 			case '\0':
-				errsave(escontext,
-						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-						 errmsg("malformed array literal: \"%s\"",
-								origStr)));
-				*srcptr = p;
-				return ATOK_ERROR;
+				goto ending_error;
 			case '\\':
 				/* Skip backslash, copy next character as-is. */
 				p++;
 				if (*p == '\0')
-				{
-					errsave(escontext,
-							(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-							 errmsg("malformed array literal: \"%s\"",
-									origStr)));
-					*srcptr = p;
-					return ATOK_ERROR;
-				}
+					goto ending_error;
 				*dst++ = *p++;
 				break;
 			case '"':
@@ -863,31 +849,20 @@ unquoted_element:
 		{
 			case '\0':
 			case '{':
-				errsave(escontext,
-						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-						 errmsg("malformed array literal: \"%s\"",
-								origStr)));
-				*srcptr = p;
-				return ATOK_ERROR;
+				goto ending_error;
 			case '\\':
 				/* Skip backslash, copy next character as-is. */
 				p++;
 				if (*p == '\0')
-				{
-					errsave(escontext,
-							(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-							 errmsg("malformed array literal: \"%s\"",
-									origStr)));
-					*srcptr = p;
-					return ATOK_ERROR;
-				}
+					goto ending_error;
 				*dst++ = *p++;
+				dstendptr = dst;
 				break;
 			case '"':
 				errsave(escontext,
 						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-						 errmsg("malformed array literal: \"%s\"",
-								origStr)));
+						 errmsg("malformed array literal: \"%s\"",origStr),
+						 errdetail("Unexpected array element.")));
 				*srcptr = p;
 				return ATOK_ERROR;
 
@@ -907,6 +882,15 @@ unquoted_element:
 				p++;
 		}
 	}
+
+ending_error:
+	errsave(escontext,
+			(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+				errmsg("malformed array literal: \"%s\"",
+					origStr),
+				errdetail("Unexpected end of input.")));
+	*srcptr = p;
+	return ATOK_ERROR;
 }
 
 /*
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index bea0d00b..0d9fef55 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -1481,6 +1481,16 @@ ERROR:  malformed array literal: "{{1,2},\{2,3}}"
 LINE 1: select E'{{1,2},\\{2,3}}'::text[];
                ^
 DETAIL:  Multidimensional arrays must have sub-arrays with matching dimensions.
+select E'{"a"a}'::text[];
+ERROR:  malformed array literal: "{"a"a}"
+LINE 1: select E'{"a"a}'::text[];
+               ^
+DETAIL:  Unexpected array element.
+select E'{a"a"}'::text[];
+ERROR:  malformed array literal: "{a"a"}"
+LINE 1: select E'{a"a"}'::text[];
+               ^
+DETAIL:  Unexpected array element.
 select '{{"1 2" x},{3}}'::text[];
 ERROR:  malformed array literal: "{{"1 2" x},{3}}"
 LINE 1: select '{{"1 2" x},{3}}'::text[];
@@ -1544,6 +1554,34 @@ ERROR:  cannot determine type of empty array
 LINE 1: select array[];
                ^
 HINT:  Explicitly cast to the desired type, for example ARRAY[]::integer[].
+select '{{1,},{1},}'::text[];
+ERROR:  malformed array literal: "{{1,},{1},}"
+LINE 1: select '{{1,},{1},}'::text[];
+               ^
+DETAIL:  Unexpected "}" character.
+select '{{1,},{1}}'::text[];
+ERROR:  malformed array literal: "{{1,},{1}}"
+LINE 1: select '{{1,},{1}}'::text[];
+               ^
+DETAIL:  Unexpected "}" character.
+select '{{1,}}'::text[];
+ERROR:  malformed array literal: "{{1,}}"
+LINE 1: select '{{1,}}'::text[];
+               ^
+DETAIL:  Unexpected "}" character.
+select '{1,}'::text[];
+ERROR:  malformed array literal: "{1,}"
+LINE 1: select '{1,}'::text[];
+               ^
+DETAIL:  Unexpected "}" character.
+select '[21474836488:21474836489]={1,2}'::int[];
+ERROR:  array bound is out of range
+LINE 1: select '[21474836488:21474836489]={1,2}'::int[];
+               ^
+select '[-2147483649:-2147483648]={1,2}'::int[];
+ERROR:  array bound is out of range
+LINE 1: select '[-2147483649:-2147483648]={1,2}'::int[];
+               ^
 -- none of the above should be accepted
 -- all of the following should be accepted
 select '{}'::text[];
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 5eacb351..47667ea3 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -455,6 +455,8 @@ select 'foo' ilike all (array['F%', '%O']); -- t
 -- none of the following should be accepted
 select '{{1,{2}},{2,3}}'::text[];
 select E'{{1,2},\\{2,3}}'::text[];
+select E'{"a"a}'::text[];
+select E'{a"a"}'::text[];
 select '{{"1 2" x},{3}}'::text[];
 select '{}}'::text[];
 select '{ }}'::text[];
@@ -468,6 +470,13 @@ select '[2147483646:2147483647]={1,2}'::int[];
 select '[1:-1]={}'::int[];
 select '[1:0]={1}'::int[];
 select array[];
+select '{{1,},{1},}'::text[];
+select '{{1,},{1}}'::text[];
+select '{{1,}}'::text[];
+select '{1,}'::text[];
+select '[21474836488:21474836489]={1,2}'::int[];
+select '[-2147483649:-2147483648]={1,2}'::int[];
+
 -- none of the above should be accepted
 
 -- all of the following should be accepted
-- 
2.34.1

