From cc7bb2562a02cee163a308f4707f14f0ca4bfde3 Mon Sep 17 00:00:00 2001
From: Alexandra Wang <alexandra.wang.oss@gmail.com>
Date: Fri, 5 Sep 2025 14:17:37 -0700
Subject: [PATCH v16 2/7] Allow Generic Type Subscripting to Accept Dot
 Notation (.) as Input
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This change extends generic type subscripting to recognize dot
notation (.) in addition to bracket notation ([]). While this does not
yet provide full support for dot notation, it enables subscripting
containers to process it in the future.

For now, container-specific transform functions only handle
subscripting indices and stop processing when encountering dot
notation. It is up to individual containers to decide how to transform
dot notation in subsequent updates.

This change also updates the SubscriptTransform() API to remove the
"bool isSlice" argument. Each container’s transform function now
determines slice-ness itself, since it may only process a prefix of
the indirection list. For example, array_subscript_transform() stops
at dot notation, so "isSlice" should not depend on slice specifiers
that appear afterward.

Authored-by: Nikita Glukhov <glukhov.n.a@gmail.com>
Authored-by: Alexandra Wang <alexandra.wang.oss@gmail.com>
Reviewed-by: Andrew Dunstan <andrew@dunslane.net>
Reviewed-by: Matheus Alcantara <matheusssilv97@gmail.com>
Reviewed-by: Jian He <jian.universality@gmail.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
---
 contrib/hstore/hstore_subs.c         |  9 ++--
 src/backend/parser/parse_expr.c      | 68 ++++++++++++++++++----------
 src/backend/parser/parse_node.c      | 57 ++++++++++++++---------
 src/backend/parser/parse_target.c    |  3 +-
 src/backend/utils/adt/arraysubs.c    | 40 ++++++++++++++--
 src/backend/utils/adt/jsonbsubs.c    | 16 +++++--
 src/include/nodes/subscripting.h     |  3 +-
 src/include/parser/parse_node.h      |  3 +-
 src/test/regress/expected/arrays.out | 26 +++++++++--
 src/test/regress/sql/arrays.sql      |  7 ++-
 10 files changed, 162 insertions(+), 70 deletions(-)

diff --git a/contrib/hstore/hstore_subs.c b/contrib/hstore/hstore_subs.c
index 1b29543ab67..5492049af3b 100644
--- a/contrib/hstore/hstore_subs.c
+++ b/contrib/hstore/hstore_subs.c
@@ -42,14 +42,16 @@ static void
 hstore_subscript_transform(SubscriptingRef *sbsref,
 						   List **indirection,
 						   ParseState *pstate,
-						   bool isSlice,
 						   bool isAssignment)
 {
 	A_Indices  *ai;
 	Node	   *subexpr;
 
+	Assert(*indirection != NIL);
+	ai = linitial_node(A_Indices, *indirection);
+
 	/* We support only single-subscript, non-slice cases */
-	if (isSlice || list_length(*indirection) != 1)
+	if (ai->is_slice || list_length(*indirection) != 1)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("hstore allows only one subscript"),
@@ -57,8 +59,7 @@ hstore_subscript_transform(SubscriptingRef *sbsref,
 									exprLocation((Node *) *indirection))));
 
 	/* Transform the subscript expression to type text */
-	ai = linitial_node(A_Indices, *indirection);
-	Assert(ai->uidx != NULL && ai->lidx == NULL && !ai->is_slice);
+	Assert(ai->uidx != NULL && ai->lidx == NULL);
 
 	subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
 	/* If it's not text already, try to coerce */
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 6e8fd42c612..f8a0617f823 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -441,38 +441,59 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 	ListCell   *i;
 
 	/*
-	 * We have to split any field-selection operations apart from
-	 * subscripting.  Adjacent A_Indices nodes have to be treated as a single
+	 * Combine field names and subscripts into a single indirection list, as
+	 * some subscripting containers, such as jsonb, support field access using
+	 * dot notation. Adjacent A_Indices nodes have to be treated as a single
 	 * multidimensional subscript operation.
 	 */
 	foreach(i, ind->indirection)
 	{
 		Node	   *n = lfirst(i);
 
-		if (IsA(n, A_Indices))
+		if (IsA(n, A_Indices) || IsA(n, String))
 			subscripts = lappend(subscripts, n);
-		else if (IsA(n, A_Star))
+		else
 		{
+			Assert(IsA(n, A_Star));
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("row expansion via \"*\" is not supported here"),
 					 parser_errposition(pstate, location)));
 		}
-		else
+	}
+
+	while (subscripts)
+	{
+		/* try processing container subscripts first */
+		Node	   *newresult = (Node *)
+			transformContainerSubscripts(pstate,
+										 result,
+										 exprType(result),
+										 exprTypmod(result),
+										 &subscripts,
+										 false,
+										 true);
+
+		if (!newresult)
 		{
-			Node	   *newresult;
+			/*
+			 * generic subscripting failed; falling back to field selection
+			 * for a composite type.
+			 */
+			Node	   *n;
+
+			Assert(subscripts);
 
-			Assert(IsA(n, String));
+			n = linitial(subscripts);
 
-			/* process subscripts before this field selection */
-			while (subscripts)
-				result = (Node *) transformContainerSubscripts(pstate,
-															   result,
-															   exprType(result),
-															   exprTypmod(result),
-															   &subscripts,
-															   false);
+			if (!IsA(n, String))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("cannot subscript type %s because it does not support subscripting",
+								format_type_be(exprType(result))),
+						 parser_errposition(pstate, exprLocation(result))));
 
+			/* try to find function for field selection */
 			newresult = ParseFuncOrColumn(pstate,
 										  list_make1(n),
 										  list_make1(result),
@@ -480,19 +501,16 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 										  NULL,
 										  false,
 										  location);
-			if (newresult == NULL)
+
+			if (!newresult)
 				unknown_attribute(pstate, result, strVal(n), location);
-			result = newresult;
+
+			/* consume field select */
+			subscripts = list_delete_first(subscripts);
 		}
+
+		result = newresult;
 	}
-	/* process trailing subscripts, if any */
-	while (subscripts)
-		result = (Node *) transformContainerSubscripts(pstate,
-													   result,
-													   exprType(result),
-													   exprTypmod(result),
-													   &subscripts,
-													   false);
 
 	return result;
 }
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index f05baa50a15..d7b23688b9b 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -238,6 +238,8 @@ transformContainerType(Oid *containerType, int32 *containerTypmod)
  * containerTypMod	typmod for the container
  * indirection		Untransformed list of subscripts (must not be NIL)
  * isAssignment		True if this will become a container assignment.
+ * noError			True for return NULL with no error, if the container type
+ * 					is not subscriptable.
  */
 SubscriptingRef *
 transformContainerSubscripts(ParseState *pstate,
@@ -245,13 +247,13 @@ transformContainerSubscripts(ParseState *pstate,
 							 Oid containerType,
 							 int32 containerTypMod,
 							 List **indirection,
-							 bool isAssignment)
+							 bool isAssignment,
+							 bool noError)
 {
 	SubscriptingRef *sbsref;
 	const SubscriptRoutines *sbsroutines;
 	Oid			elementType;
-	bool		isSlice = false;
-	ListCell   *idx;
+	int			indirection_length = list_length(*indirection);
 
 	/*
 	 * Determine the actual container type, smashing any domain.  In the
@@ -267,28 +269,15 @@ transformContainerSubscripts(ParseState *pstate,
 	 */
 	sbsroutines = getSubscriptingRoutines(containerType, &elementType);
 	if (!sbsroutines)
+	{
+		if (noError)
+			return NULL;
+
 		ereport(ERROR,
 				(errcode(ERRCODE_DATATYPE_MISMATCH),
 				 errmsg("cannot subscript type %s because it does not support subscripting",
 						format_type_be(containerType)),
 				 parser_errposition(pstate, exprLocation(containerBase))));
-
-	/*
-	 * Detect whether any of the indirection items are slice specifiers.
-	 *
-	 * A list containing only simple subscripts refers to a single container
-	 * element.  If any of the items are slice specifiers (lower:upper), then
-	 * the subscript expression means a container slice operation.
-	 */
-	foreach(idx, *indirection)
-	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
-
-		if (ai->is_slice)
-		{
-			isSlice = true;
-			break;
-		}
 	}
 
 	/*
@@ -310,7 +299,33 @@ transformContainerSubscripts(ParseState *pstate,
 	 * determine the subscripting result type.
 	 */
 	sbsroutines->transform(sbsref, indirection, pstate,
-						   isSlice, isAssignment);
+						   isAssignment);
+
+	/*
+	 * Error out, if datatype failed to consume any indirection elements.
+	 */
+	if (list_length(*indirection) == indirection_length)
+	{
+		Node	   *ind = linitial(*indirection);
+
+		if (noError)
+			return NULL;
+
+		if (IsA(ind, String))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support dot notation",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else if (IsA(ind, A_Indices))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("type %s does not support array subscripting",
+							format_type_be(containerType)),
+					 parser_errposition(pstate, exprLocation(containerBase))));
+		else
+			elog(ERROR, "invalid indirection operation: %d", nodeTag(ind));
+	}
 
 	/*
 	 * Verify we got a valid type (this defends, for example, against someone
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index a097736229a..b89736ff1ea 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -936,7 +936,8 @@ transformAssignmentSubscripts(ParseState *pstate,
 										  containerType,
 										  containerTypMod,
 										  &subscripts,
-										  true);
+										  true,
+										  false);
 
 	typeNeeded = sbsref->refrestype;
 	typmodNeeded = sbsref->reftypmod;
diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c
index 234c2c278c1..378b6bf16cb 100644
--- a/src/backend/utils/adt/arraysubs.c
+++ b/src/backend/utils/adt/arraysubs.c
@@ -56,12 +56,38 @@ static void
 array_subscript_transform(SubscriptingRef *sbsref,
 						  List **indirection,
 						  ParseState *pstate,
-						  bool isSlice,
 						  bool isAssignment)
 {
 	List	   *upperIndexpr = NIL;
 	List	   *lowerIndexpr = NIL;
 	ListCell   *idx;
+	int			ndim;
+	bool		isSlice = false;
+
+	/*
+	 * Detect whether any of the indirection items are slice specifiers.
+	 *
+	 * A list containing only simple subscripts refers to a single container
+	 * element.  If any of the items are slice specifiers (lower:upper), then
+	 * the subscript expression means a container slice operation.
+	 */
+	foreach(idx, *indirection)
+	{
+		Node	   *ai = lfirst(idx);
+
+		/*
+		 * We should not inspect slice specifiers beyond an indirection node
+		 * type that we don't support.
+		 */
+		if (!IsA(ai, A_Indices))
+			break;
+
+		if (castNode(A_Indices, ai)->is_slice)
+		{
+			isSlice = true;
+			break;
+		}
+	}
 
 	/*
 	 * Transform the subscript expressions, and separate upper and lower
@@ -73,9 +99,14 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subexpr;
 
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
 		if (isSlice)
 		{
 			if (ai->lidx)
@@ -145,14 +176,15 @@ array_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->reflowerindexpr = lowerIndexpr;
 
 	/* Verify subscript list lengths are within implementation limit */
-	if (list_length(upperIndexpr) > MAXDIM)
+	ndim = list_length(upperIndexpr);
+	if (ndim > MAXDIM)
 		ereport(ERROR,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 				 errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
 						list_length(upperIndexpr), MAXDIM)));
 	/* We need not check lowerIndexpr separately */
 
-	*indirection = NIL;
+	*indirection = list_delete_first_n(*indirection, ndim);
 
 	/*
 	 * Determine the result type of the subscripting operation.  It's the same
diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c
index 8ad6aa1ad4f..ec8e82dc629 100644
--- a/src/backend/utils/adt/jsonbsubs.c
+++ b/src/backend/utils/adt/jsonbsubs.c
@@ -43,7 +43,6 @@ static void
 jsonb_subscript_transform(SubscriptingRef *sbsref,
 						  List **indirection,
 						  ParseState *pstate,
-						  bool isSlice,
 						  bool isAssignment)
 {
 	List	   *upperIndexpr = NIL;
@@ -55,10 +54,15 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	 */
 	foreach(idx, *indirection)
 	{
-		A_Indices  *ai = lfirst_node(A_Indices, idx);
+		A_Indices  *ai;
 		Node	   *subExpr;
 
-		if (isSlice)
+		if (!IsA(lfirst(idx), A_Indices))
+			break;
+
+		ai = lfirst_node(A_Indices, idx);
+
+		if (ai->is_slice)
 		{
 			Node	   *expr = ai->uidx ? ai->uidx : ai->lidx;
 
@@ -142,7 +146,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 			 * Slice with omitted upper bound. Should not happen as we already
 			 * errored out on slice earlier, but handle this just in case.
 			 */
-			Assert(isSlice && ai->is_slice);
+			Assert(ai->is_slice);
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("jsonb subscript does not support slices"),
@@ -160,7 +164,9 @@ jsonb_subscript_transform(SubscriptingRef *sbsref,
 	sbsref->refrestype = JSONBOID;
 	sbsref->reftypmod = -1;
 
-	*indirection = NIL;
+	/* Remove processed elements */
+	if (upperIndexpr)
+		*indirection = list_delete_first_n(*indirection, list_length(upperIndexpr));
 }
 
 /*
diff --git a/src/include/nodes/subscripting.h b/src/include/nodes/subscripting.h
index df988a85fad..14ef414a180 100644
--- a/src/include/nodes/subscripting.h
+++ b/src/include/nodes/subscripting.h
@@ -68,7 +68,7 @@ struct SubscriptExecSteps;
  * same length as refupperindexpr for a slice operation.  Insert NULLs
  * (that is, an empty parse tree, not a null Const node) for any omitted
  * subscripts in a slice operation.  (Of course, if the transform method
- * does not care to support slicing, it can just throw an error if isSlice.)
+ * does not care to support slicing, it can just throw an error.)
  * See array_subscript_transform() for sample code.
  *
  * The transform method receives a pointer to a list of raw indirections.
@@ -100,7 +100,6 @@ struct SubscriptExecSteps;
 typedef void (*SubscriptTransform) (SubscriptingRef *sbsref,
 									List **indirection,
 									struct ParseState *pstate,
-									bool isSlice,
 									bool isAssignment);
 
 /*
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 58a4b9df157..5cc3ce58c30 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -362,7 +362,8 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate,
 													 Oid containerType,
 													 int32 containerTypMod,
 													 List **indirection,
-													 bool isAssignment);
+													 bool isAssignment,
+													 bool noError);
 extern Const *make_const(ParseState *pstate, A_Const *aconst);
 
 #endif							/* PARSE_NODE_H */
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index b815473f414..78dd9e71bf3 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -1782,17 +1782,17 @@ SELECT max(f1), min(f1), max(f2), min(f2), max(f3), min(f3) FROM arraggtest;
 (1 row)
 
 -- A few simple tests for arrays of composite types
-create type comptype as (f1 int, f2 text);
+create type comptype as (f1 int, f2 text, f3 int[]);
 create table comptable (c1 comptype, c2 comptype[]);
 -- XXX would like to not have to specify row() construct types here ...
 insert into comptable
-  values (row(1,'foo'), array[row(2,'bar')::comptype, row(3,'baz')::comptype]);
+  values (row(1,'foo',array[10,20]), array[row(2,'bar',array[30,40])::comptype, row(3,'baz',array[50,60])::comptype]);
 -- check that implicitly named array type _comptype isn't a problem
 create type _comptype as enum('fooey');
 select * from comptable;
-   c1    |          c2           
----------+-----------------------
- (1,foo) | {"(2,bar)","(3,baz)"}
+        c1         |                      c2                       
+-------------------+-----------------------------------------------
+ (1,foo,"{10,20}") | {"(2,bar,\"{30,40}\")","(3,baz,\"{50,60}\")"}
 (1 row)
 
 select c2[2].f2 from comptable;
@@ -1801,6 +1801,22 @@ select c2[2].f2 from comptable;
  baz
 (1 row)
 
+select c2[2].f3 from comptable;
+   f3    
+---------
+ {50,60}
+(1 row)
+
+select c2[2].f3[1:2] from comptable;
+   f3    
+---------
+ {50,60}
+(1 row)
+
+select c2[1:2].f3[1:2] from comptable;
+ERROR:  column notation .f3 applied to type comptype[], which is not a composite type
+LINE 1: select c2[1:2].f3[1:2] from comptable;
+               ^
 drop type _comptype;
 drop table comptable;
 drop type comptype;
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 47d62c1d38d..450389831a0 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -555,19 +555,22 @@ SELECT max(f1), min(f1), max(f2), min(f2), max(f3), min(f3) FROM arraggtest;
 
 -- A few simple tests for arrays of composite types
 
-create type comptype as (f1 int, f2 text);
+create type comptype as (f1 int, f2 text, f3 int[]);
 
 create table comptable (c1 comptype, c2 comptype[]);
 
 -- XXX would like to not have to specify row() construct types here ...
 insert into comptable
-  values (row(1,'foo'), array[row(2,'bar')::comptype, row(3,'baz')::comptype]);
+  values (row(1,'foo',array[10,20]), array[row(2,'bar',array[30,40])::comptype, row(3,'baz',array[50,60])::comptype]);
 
 -- check that implicitly named array type _comptype isn't a problem
 create type _comptype as enum('fooey');
 
 select * from comptable;
 select c2[2].f2 from comptable;
+select c2[2].f3 from comptable;
+select c2[2].f3[1:2] from comptable;
+select c2[1:2].f3[1:2] from comptable;
 
 drop type _comptype;
 drop table comptable;
-- 
2.39.5 (Apple Git-154)

