From 0af27d235ca6fd1db12d81c657d8b349f3b29316 Mon Sep 17 00:00:00 2001
From: Ubuntu <ubuntu@ip-172-31-38-230.ec2.internal>
Date: Wed, 21 May 2025 17:25:02 +0000
Subject: [PATCH v4 1/3] Add tracking for expression boundaries

This adds the ability to track the locations of the start and
end of a list of elements such as those in an 'IN' list of an
Array expression to support squashing of values for query
normalization purposes. This corrects various normalization
issues that are a result of 62d712ec.

Discussion: https://www.postgresql.org/message-id/flat/202505021256.4yaa24s3sytm%40alvherre.pgsql#1195a340edca50cc3b7389a2ba8b0467
---
 src/backend/parser/gram.y       | 94 +++++++++++++++++++++++----------
 src/backend/parser/parse_expr.c |  4 ++
 src/include/nodes/parsenodes.h  |  4 ++
 src/include/nodes/primnodes.h   |  4 ++
 4 files changed, 79 insertions(+), 27 deletions(-)

diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0b5652071d1..0cd5f794db3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -136,6 +136,17 @@ typedef struct KeyActions
 	KeyAction *deleteAction;
 } KeyActions;
 
+/*
+ * Track the start and end of a list in an expression, such as an 'IN' list
+ * or Array Expression
+ */
+typedef struct ListWithBoundary
+{
+	Node	   *expr;
+	ParseLoc	start;
+	ParseLoc	end;
+} ListWithBoundary;
+
 /* ConstraintAttributeSpec yields an integer bitmask of these flags: */
 #define CAS_NOT_DEFERRABLE			0x01
 #define CAS_DEFERRABLE				0x02
@@ -184,7 +195,7 @@ static void doNegateFloat(Float *v);
 static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeOrExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeNotExpr(Node *expr, int location);
-static Node *makeAArrayExpr(List *elements, int location);
+static Node *makeAArrayExpr(List *elements, int location, int end_location);
 static Node *makeSQLValueFunction(SQLValueFunctionOp op, int32 typmod,
 								  int location);
 static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args,
@@ -269,6 +280,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct KeyAction *keyaction;
 	ReturningClause *retclause;
 	ReturningOptionKind retoptionkind;
+	struct ListWithBoundary *listwithboundary;
 }
 
 %type <node>	stmt toplevel_stmt schema_stmt routine_body_stmt
@@ -523,8 +535,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <defelt>	def_elem reloption_elem old_aggr_elem operator_def_elem
 %type <node>	def_arg columnElem where_clause where_or_current_clause
 				a_expr b_expr c_expr AexprConst indirection_el opt_slice_bound
-				columnref in_expr having_clause func_table xmltable array_expr
+				columnref having_clause func_table xmltable array_expr
 				OptWhereClause operator_def_arg
+%type <listwithboundary> in_expr
 %type <list>	opt_column_and_period_list
 %type <list>	rowsfrom_item rowsfrom_list opt_col_def_list
 %type <boolean> opt_ordinality opt_without_overlaps
@@ -15289,46 +15302,58 @@ a_expr:		c_expr									{ $$ = $1; }
 				}
 			| a_expr IN_P in_expr
 				{
+					ListWithBoundary *n = $3;
+
 					/* in_expr returns a SubLink or a list of a_exprs */
-					if (IsA($3, SubLink))
+					if (IsA(n->expr, SubLink))
 					{
 						/* generate foo = ANY (subquery) */
-						SubLink	   *n = (SubLink *) $3;
-
-						n->subLinkType = ANY_SUBLINK;
-						n->subLinkId = 0;
-						n->testexpr = $1;
-						n->operName = NIL;		/* show it's IN not = ANY */
-						n->location = @2;
-						$$ = (Node *) n;
+						SubLink	   *n2 = (SubLink *) n->expr;
+
+						n2->subLinkType = ANY_SUBLINK;
+						n2->subLinkId = 0;
+						n2->testexpr = $1;
+						n2->operName = NIL;		/* show it's IN not = ANY */
+						n2->location = @2;
+						$$ = (Node *) n2;
 					}
 					else
 					{
 						/* generate scalar IN expression */
-						$$ = (Node *) makeSimpleA_Expr(AEXPR_IN, "=", $1, $3, @2);
+						A_Expr *n2 = makeSimpleA_Expr(AEXPR_IN, "=", $1, n->expr, @2);
+
+						n2->rexpr_list_start = $3->start;
+						n2->rexpr_list_end = $3->end;
+						$$ = (Node *) n2;
 					}
 				}
 			| a_expr NOT_LA IN_P in_expr						%prec NOT_LA
 				{
+					ListWithBoundary *n = $4;
+
 					/* in_expr returns a SubLink or a list of a_exprs */
-					if (IsA($4, SubLink))
+					if (IsA(n->expr, SubLink))
 					{
 						/* generate NOT (foo = ANY (subquery)) */
 						/* Make an = ANY node */
-						SubLink	   *n = (SubLink *) $4;
+						SubLink	   *n2 = (SubLink *) n->expr;
 
-						n->subLinkType = ANY_SUBLINK;
-						n->subLinkId = 0;
-						n->testexpr = $1;
-						n->operName = NIL;		/* show it's IN not = ANY */
-						n->location = @2;
+						n2->subLinkType = ANY_SUBLINK;
+						n2->subLinkId = 0;
+						n2->testexpr = $1;
+						n2->operName = NIL;		/* show it's IN not = ANY */
+						n2->location = @2;
 						/* Stick a NOT on top; must have same parse location */
-						$$ = makeNotExpr((Node *) n, @2);
+						$$ = makeNotExpr((Node *) n2, @2);
 					}
 					else
 					{
 						/* generate scalar NOT IN expression */
-						$$ = (Node *) makeSimpleA_Expr(AEXPR_IN, "<>", $1, $4, @2);
+						A_Expr *n2 = makeSimpleA_Expr(AEXPR_IN, "<>", $1, n->expr, @2);
+
+						n2->rexpr_list_start = $4->start;
+						n2->rexpr_list_end = $4->end;
+						$$ = (Node *) n2;
 					}
 				}
 			| a_expr subquery_Op sub_type select_with_parens	%prec Op
@@ -16764,15 +16789,15 @@ type_list:	Typename								{ $$ = list_make1($1); }
 
 array_expr: '[' expr_list ']'
 				{
-					$$ = makeAArrayExpr($2, @1);
+					$$ = makeAArrayExpr($2, @1, @3);
 				}
 			| '[' array_expr_list ']'
 				{
-					$$ = makeAArrayExpr($2, @1);
+					$$ = makeAArrayExpr($2, @1, @3);
 				}
 			| '[' ']'
 				{
-					$$ = makeAArrayExpr(NIL, @1);
+					$$ = makeAArrayExpr(NIL, @1, @2);
 				}
 		;
 
@@ -16897,12 +16922,25 @@ trim_list:	a_expr FROM expr_list					{ $$ = lappend($3, $1); }
 in_expr:	select_with_parens
 				{
 					SubLink	   *n = makeNode(SubLink);
+					ListWithBoundary *n2 = palloc(sizeof(ListWithBoundary));
 
 					n->subselect = $1;
 					/* other fields will be filled later */
-					$$ = (Node *) n;
+
+					n2->expr = (Node *) n;
+					n2->start = -1;
+					n2->end = -1;
+					$$ = n2;
+				}
+			| '(' expr_list ')'
+				{
+					ListWithBoundary *n = palloc(sizeof(ListWithBoundary));
+
+					n->expr = (Node *) $2;
+					n->start = @1;
+					n->end = @3;
+					$$ = n;
 				}
-			| '(' expr_list ')'						{ $$ = (Node *) $2; }
 		;
 
 /*
@@ -19300,12 +19338,14 @@ makeNotExpr(Node *expr, int location)
 }
 
 static Node *
-makeAArrayExpr(List *elements, int location)
+makeAArrayExpr(List *elements, int location, int location_end)
 {
 	A_ArrayExpr *n = makeNode(A_ArrayExpr);
 
 	n->elements = elements;
 	n->location = location;
+	n->list_start = location;
+	n->list_end = location_end;
 	return (Node *) n;
 }
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 1f8e2d54673..7347c989e11 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1224,6 +1224,8 @@ transformAExprIn(ParseState *pstate, A_Expr *a)
 			newa->elements = aexprs;
 			newa->multidims = false;
 			newa->location = -1;
+			newa->list_start = a->rexpr_list_start;
+			newa->list_end = a->rexpr_list_end;
 
 			result = (Node *) make_scalar_array_op(pstate,
 												   a->name,
@@ -2166,6 +2168,8 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a,
 	newa->element_typeid = element_type;
 	newa->elements = newcoercedelems;
 	newa->location = a->location;
+	newa->list_start = a->list_start;
+	newa->list_end = a->list_end;
 
 	return (Node *) newa;
 }
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4610fc61293..2f078887d06 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -347,6 +347,8 @@ typedef struct A_Expr
 	Node	   *lexpr;			/* left argument, or NULL if none */
 	Node	   *rexpr;			/* right argument, or NULL if none */
 	ParseLoc	location;		/* token location, or -1 if unknown */
+	ParseLoc	rexpr_list_start;	/* location of the start of a rexpr list */
+	ParseLoc	rexpr_list_end; /* location of the end of a rexpr list */
 } A_Expr;
 
 /*
@@ -502,6 +504,8 @@ typedef struct A_ArrayExpr
 	NodeTag		type;
 	List	   *elements;		/* array element expressions */
 	ParseLoc	location;		/* token location, or -1 if unknown */
+	ParseLoc	list_start;		/* location of the start of the elements list */
+	ParseLoc	list_end;		/* location of the end of the elements list */
 } A_ArrayExpr;
 
 /*
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 7d3b4198f26..773cdd880aa 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1399,6 +1399,10 @@ typedef struct ArrayExpr
 	bool		multidims pg_node_attr(query_jumble_ignore);
 	/* token location, or -1 if unknown */
 	ParseLoc	location;
+	/* location of the start of the elements list */
+	ParseLoc	list_start;
+	/* location of the end of the elements list */
+	ParseLoc	list_end;
 } ArrayExpr;
 
 /*
-- 
2.39.5 (Apple Git-154)

