From b376b023aed5b4769cb29daa7f55e6eb22b90afd Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Sat, 1 Apr 2023 23:15:26 +0300
Subject: [PATCH v7 5/5] Allow processing of .* by generic subscripting

---
 src/backend/parser/gram.y           |   2 +
 src/backend/parser/parse_expr.c     |  34 +++--
 src/backend/parser/parse_target.c   |  60 ++++++---
 src/backend/utils/adt/ruleutils.c   |   6 +-
 src/include/parser/parse_expr.h     |   3 +
 src/test/regress/expected/jsonb.out | 195 +++++++++++++++++++---------
 6 files changed, 206 insertions(+), 94 deletions(-)

diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d7f9c00c409..5f69ac661bd 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -18959,6 +18959,7 @@ check_func_name(List *names, core_yyscan_t yyscanner)
 static List *
 check_indirection(List *indirection, core_yyscan_t yyscanner)
 {
+#if 0
 	ListCell   *l;
 
 	foreach(l, indirection)
@@ -18969,6 +18970,7 @@ check_indirection(List *indirection, core_yyscan_t yyscanner)
 				parser_yyerror("improper use of \"*\"");
 		}
 	}
+#endif
 	return indirection;
 }
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index afe953fdbea..b0c38529872 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -74,7 +74,6 @@ static Node *transformColumnRef(ParseState *pstate, ColumnRef *cref);
 static Node *transformWholeRowRef(ParseState *pstate,
 								  ParseNamespaceItem *nsitem,
 								  int sublevels_up, int location);
-static Node *transformIndirection(ParseState *pstate, A_Indirection *ind);
 static Node *transformTypeCast(ParseState *pstate, TypeCast *tc);
 static Node *transformCollateClause(ParseState *pstate, CollateClause *c);
 static Node *transformJsonObjectConstructor(ParseState *pstate,
@@ -158,7 +157,7 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 			break;
 
 		case T_A_Indirection:
-			result = transformIndirection(pstate, (A_Indirection *) expr);
+			result = transformIndirection(pstate, (A_Indirection *) expr, NULL);
 			break;
 
 		case T_A_ArrayExpr:
@@ -432,8 +431,9 @@ unknown_attribute(ParseState *pstate, Node *relref, const char *attname,
 	}
 }
 
-static Node *
-transformIndirection(ParseState *pstate, A_Indirection *ind)
+Node *
+transformIndirection(ParseState *pstate, A_Indirection *ind,
+					 bool *trailing_star_expansion)
 {
 	Node	   *last_srf = pstate->p_last_srf;
 	Node	   *result = transformExprRecurse(pstate, ind->arg);
@@ -453,12 +453,7 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		if (IsA(n, A_Indices))
 			subscripts = lappend(subscripts, n);
 		else if (IsA(n, A_Star))
-		{
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("row expansion via \"*\" is not supported here"),
-					 parser_errposition(pstate, location)));
-		}
+			subscripts = lappend(subscripts, n);
 		else
 		{
 			Assert(IsA(n, String));
@@ -487,7 +482,21 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 
 			n = linitial(subscripts);
 
-			if (!IsA(n, String))
+			if (IsA(n, A_Star))
+			{
+				/* Success, if trailing star expansion is allowed */
+				if (trailing_star_expansion && list_length(subscripts) == 1)
+				{
+					*trailing_star_expansion = true;
+					return result;
+				}
+
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("row expansion via \"*\" is not supported here"),
+						 parser_errposition(pstate, location)));
+			}
+			else if (!IsA(n, String))
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
 						 errmsg("cannot subscript type %s because it does not support subscripting",
@@ -513,6 +522,9 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
 		result = newresult;
 	}
 
+	if (trailing_star_expansion)
+		*trailing_star_expansion = false;
+
 	return result;
 }
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 5e126145ea5..965e2f06e7b 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -48,7 +48,7 @@ static Node *transformAssignmentSubscripts(ParseState *pstate,
 static List *ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 								 bool make_target_entry);
 static List *ExpandAllTables(ParseState *pstate, int location);
-static List *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
+static Node *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 								   bool make_target_entry, ParseExprKind exprKind);
 static List *ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 							   int sublevels_up, int location,
@@ -134,6 +134,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 	foreach(o_target, targetlist)
 	{
 		ResTarget  *res = (ResTarget *) lfirst(o_target);
+		Node	   *transformed = NULL;
 
 		/*
 		 * Check for "something.*".  Depending on the complexity of the
@@ -162,13 +163,19 @@ transformTargetList(ParseState *pstate, List *targetlist,
 
 				if (IsA(llast(ind->indirection), A_Star))
 				{
-					/* It is something.*, expand into multiple items */
-					p_target = list_concat(p_target,
-										   ExpandIndirectionStar(pstate,
-																 ind,
-																 true,
-																 exprKind));
-					continue;
+					Node	   *columns = ExpandIndirectionStar(pstate,
+																ind,
+																true,
+																exprKind);
+
+					if (IsA(columns, List))
+					{
+						/* It is something.*, expand into multiple items */
+						p_target = list_concat(p_target, (List *) columns);
+						continue;
+					}
+
+					transformed = (Node *) columns;
 				}
 			}
 		}
@@ -180,7 +187,7 @@ transformTargetList(ParseState *pstate, List *targetlist,
 		p_target = lappend(p_target,
 						   transformTargetEntry(pstate,
 												res->val,
-												NULL,
+												transformed,
 												exprKind,
 												res->name,
 												false));
@@ -251,10 +258,15 @@ transformExpressionList(ParseState *pstate, List *exprlist,
 
 			if (IsA(llast(ind->indirection), A_Star))
 			{
-				/* It is something.*, expand into multiple items */
-				result = list_concat(result,
-									 ExpandIndirectionStar(pstate, ind,
-														   false, exprKind));
+				Node	   *cols = ExpandIndirectionStar(pstate, ind,
+														 false, exprKind);
+
+				if (!cols || IsA(cols, List))
+					/* It is something.*, expand into multiple items */
+					result = list_concat(result, (List *) cols);
+				else
+					result = lappend(result, cols);
+
 				continue;
 			}
 		}
@@ -1348,22 +1360,30 @@ ExpandAllTables(ParseState *pstate, int location)
  * For robustness, we use a separate "make_target_entry" flag to control
  * this rather than relying on exprKind.
  */
-static List *
+static Node *
 ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind,
 					  bool make_target_entry, ParseExprKind exprKind)
 {
 	Node	   *expr;
+	ParseExprKind sv_expr_kind;
+	bool		trailing_star_expansion = false;
+
+	/* Save and restore identity of expression type we're parsing */
+	Assert(exprKind != EXPR_KIND_NONE);
+	sv_expr_kind = pstate->p_expr_kind;
+	pstate->p_expr_kind = exprKind;
 
 	/* Strip off the '*' to create a reference to the rowtype object */
-	ind = copyObject(ind);
-	ind->indirection = list_truncate(ind->indirection,
-									 list_length(ind->indirection) - 1);
+	expr = transformIndirection(pstate, ind, &trailing_star_expansion);
+
+	pstate->p_expr_kind = sv_expr_kind;
 
-	/* And transform that */
-	expr = transformExpr(pstate, (Node *) ind, exprKind);
+	/* '*' was consumed by generic type subscripting */
+	if (!trailing_star_expansion)
+		return expr;
 
 	/* Expand the rowtype expression into individual fields */
-	return ExpandRowReference(pstate, expr, make_target_entry);
+	return (Node *) ExpandRowReference(pstate, expr, make_target_entry);
 }
 
 /*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index af5417d0859..10006b749dc 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -12919,7 +12919,11 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	{
 		Node	   *up = (Node *) lfirst(uplist_item);
 
-		if (IsA(up, String))
+		if (!up)
+		{
+			appendStringInfoString(buf, ".*");
+		}
+		else if (IsA(up, String))
 		{
 			appendStringInfoChar(buf, '.');
 			appendStringInfoString(buf, quote_identifier(strVal(up)));
diff --git a/src/include/parser/parse_expr.h b/src/include/parser/parse_expr.h
index efbaff8e710..c9f6a7724c0 100644
--- a/src/include/parser/parse_expr.h
+++ b/src/include/parser/parse_expr.h
@@ -22,4 +22,7 @@ extern Node *transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKin
 
 extern const char *ParseExprKindName(ParseExprKind exprKind);
 
+extern Node *transformIndirection(ParseState *pstate, A_Indirection *ind,
+								  bool *trailing_star_expansion);
+
 #endif							/* PARSE_EXPR_H */
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 14123929475..9057186bdf3 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -5891,19 +5891,39 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
 CREATE TABLE test_jsonb_dot_notation AS
 SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb;
 SELECT (jb).* FROM test_jsonb_dot_notation;
-ERROR:  type jsonb is not composite
+                                                              jb                                                              
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
 SELECT (jb).* FROM test_jsonb_dot_notation t;
-ERROR:  type jsonb is not composite
+                                                              jb                                                              
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
 SELECT (t.jb).* FROM test_jsonb_dot_notation t;
-ERROR:  type jsonb is not composite
+                                                              jb                                                              
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
 SELECT (jb).* FROM test_jsonb_dot_notation;
-ERROR:  type jsonb is not composite
+                                                              jb                                                              
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
 SELECT (t.jb).* FROM test_jsonb_dot_notation;
 ERROR:  missing FROM-clause entry for table "t"
 LINE 1: SELECT (t.jb).* FROM test_jsonb_dot_notation;
                 ^
 SELECT (t.jb).* FROM test_jsonb_dot_notation t;
-ERROR:  type jsonb is not composite
+                                                              jb                                                              
+------------------------------------------------------------------------------------------------------------------------------
+ [[1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]]
+(1 row)
+
 SELECT (jb).a FROM test_jsonb_dot_notation;
                                     a                                    
 -------------------------------------------------------------------------
@@ -5935,75 +5955,120 @@ SELECT (jb).a.b FROM test_jsonb_dot_notation;
 (1 row)
 
 SELECT (jb).a.* FROM test_jsonb_dot_notation;
-ERROR:  type jsonb is not composite
+                     a                     
+-------------------------------------------
+ ["c", "d", "f", {"y": "yyy", "z": "zzz"}]
+(1 row)
+
 SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).a.*.b FROM test_jsonb_dot_notation;
-                          ^
+ b 
+---
+ 
+(1 row)
+
 SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).a.*.x FROM test_jsonb_dot_notation;
-                          ^
+ x 
+---
+ 
+(1 row)
+
 SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).a.*.y FROM test_jsonb_dot_notation;
-                          ^
+   y   
+-------
+ "yyy"
+(1 row)
+
 SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).a.*.* FROM test_jsonb_dot_notation;
-                          ^
+       a        
+----------------
+ ["yyy", "zzz"]
+(1 row)
+
 SELECT (jb).*.x FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).*.x FROM test_jsonb_dot_notation;
-                        ^
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
 SELECT (jb).*.x FROM test_jsonb_dot_notation t;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).*.x FROM test_jsonb_dot_notation t;
-                        ^
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
 SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
-ERROR:  row expansion via "*" is not supported here
-LINE 1: SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
-                 ^
+ x 
+---
+ 
+(1 row)
+
 SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
-ERROR:  row expansion via "*" is not supported here
-LINE 1: SELECT ((jb).*).x FROM test_jsonb_dot_notation t;
-                 ^
+ x 
+---
+ 
+(1 row)
+
 SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
-ERROR:  row expansion via "*" is not supported here
-LINE 1: SELECT ((jb).*)[:].x FROM test_jsonb_dot_notation t;
-                 ^
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
 SELECT (jb).*.x FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).*.x FROM test_jsonb_dot_notation;
-                        ^
+                          x                           
+------------------------------------------------------
+ [{"y": "yyy", "z": "zzz"}, {"y": "YYY", "z": "ZZZ"}]
+(1 row)
+
 SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).*.x.* FROM test_jsonb_dot_notation;
-                          ^
+              x               
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
 SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).*.x.y FROM test_jsonb_dot_notation;
-                          ^
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
 SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).*.x.z FROM test_jsonb_dot_notation;
-                          ^
+       z        
+----------------
+ ["zzz", "ZZZ"]
+(1 row)
+
 SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).*.*.y FROM test_jsonb_dot_notation;
-                          ^
+       y        
+----------------
+ ["yyy", "YYY"]
+(1 row)
+
 SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).*.*.* FROM test_jsonb_dot_notation;
-                          ^
+              jb              
+------------------------------
+ ["yyy", "zzz", "YYY", "ZZZ"]
+(1 row)
+
 SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: SELECT (jb).*.*.*.* FROM test_jsonb_dot_notation;
-                            ^
+ jb 
+----
+ 
+(1 row)
+
 SELECT (jb).a.b.c.* FROM test_jsonb_dot_notation;
-ERROR:  type jsonb is not composite
+ c 
+---
+ 
+(1 row)
+
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).* FROM test_jsonb_dot_notation;
-ERROR:  type jsonb is not composite
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: jb.*
+(2 rows)
+
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a FROM test_jsonb_dot_notation;
                  QUERY PLAN                 
 --------------------------------------------
@@ -6019,10 +6084,16 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation;
 (2 rows)
 
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*['b'] FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*['b'] FROM test_...
-                                                          ^
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: jb.a.*['b'::text]
+(2 rows)
+
 EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2]['b'].b FROM test_jsonb_dot_notation;
-ERROR:  improper use of "*" at or near "FROM"
-LINE 1: ... (VERBOSE, COSTS OFF) SELECT (jb).a.*[1:2]['b'].b FROM test_...
-                                                             ^
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on public.test_jsonb_dot_notation
+   Output: jb.a.*[1:2][:'b'::text].b
+(2 rows)
+
-- 
2.39.5 (Apple Git-154)

