From bfc5d794a12996db1d0ebe0b67503635fb11f6ad Mon Sep 17 00:00:00 2001
From: Sami Imseih <samimseih@gmail.com>
Date: Wed, 12 Aug 2026 19:24:07 +0000
Subject: [PATCH v3 1/1] Rework GRAPH_TABLE aggregate/window/SRF rejection
 using ParseExprKind

Commit f585671055d1 disallowed aggregates, window functions, and
set-returning functions in a GRAPH_TABLE COLUMNS list by inspecting the
parse state after transforming the list.  It reports no location, misses
the graph pattern WHERE clause, and lets through an aggregate that
references an outer query.  Such an aggregate belongs to the outer query
level, so the check on the GRAPH_TABLE's own level does not catch it and
it reaches the executor as "Aggref found in non-Agg plan node".

Instead give the COLUMNS list and the graph pattern WHERE clause their
own ParseExprKind values and enforce the restriction through the
parser's transformation functions check_agglevels_and_constraints(),
transformWindowFuncCall(), and check_srf_call_placement().  It
effectively reverts f585671055d1.

Because the checks now happen in those transformation functions, the
errors carry the location of the aggregate, window function, or SRF via
parser_errposition() instead of having no location at all.

Note that this changes the SQLSTATE for a rejected aggregate from
ERRCODE_FEATURE_NOT_SUPPORTED to ERRCODE_GROUPING_ERROR, consistent with
how aggregates are rejected in other clauses.

Tests added for the outer-reference and WHERE clause.
---
 src/backend/parser/parse_agg.c            | 26 ++++++++++++++++++
 src/backend/parser/parse_clause.c         | 32 +----------------------
 src/backend/parser/parse_expr.c           |  8 ++++++
 src/backend/parser/parse_func.c           |  4 +++
 src/backend/parser/parse_graphtable.c     |  6 ++---
 src/include/parser/parse_node.h           |  2 ++
 src/test/regress/expected/graph_table.out | 32 ++++++++++++++++++++---
 src/test/regress/sql/graph_table.sql      |  8 +++++-
 8 files changed, 79 insertions(+), 39 deletions(-)

diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 754a20507d0..213f517563b 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -336,6 +336,23 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 		p_levelsup = &grp->agglevelsup;
 	}
 
+	/*
+	 * Reject aggregates and grouping operations written in a GRAPH_TABLE
+	 * clause.  pstate has not been walked up yet, so p_expr_kind here still
+	 * reflects the clause the aggregate was written in.  We must check now,
+	 * before we walk up to the query level the aggregate belongs to, since
+	 * for an aggregate referencing an outer query that is a different level
+	 * and p_expr_kind would no longer reflect the GRAPH_TABLE clause.
+	 */
+	if (pstate->p_expr_kind == EXPR_KIND_GRAPH_TABLE_COLUMNS ||
+		pstate->p_expr_kind == EXPR_KIND_GRAPH_TABLE_WHERE)
+		ereport(ERROR,
+				(errcode(ERRCODE_GROUPING_ERROR),
+				 isAgg ?
+				 errmsg("aggregate functions are not allowed in GRAPH_TABLE") :
+				 errmsg("grouping operations are not allowed in GRAPH_TABLE"),
+				 parser_errposition(pstate, location)));
+
 	/*
 	 * Check the arguments to compute the aggregate's level and detect
 	 * improper nesting.
@@ -600,6 +617,11 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 
 			break;
 
+		case EXPR_KIND_GRAPH_TABLE_COLUMNS:
+		case EXPR_KIND_GRAPH_TABLE_WHERE:
+			/* rejected above */
+			break;
+
 			/*
 			 * There is intentionally no default: case here, so that the
 			 * compiler will warn if we add a new ParseExprKind without
@@ -1045,6 +1067,10 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_FOR_PORTION:
 			err = _("window functions are not allowed in FOR PORTION OF expressions");
 			break;
+		case EXPR_KIND_GRAPH_TABLE_COLUMNS:
+		case EXPR_KIND_GRAPH_TABLE_WHERE:
+			err = _("window functions are not allowed in GRAPH_TABLE");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 68b525ffdcc..b1aa13eeb1d 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -946,9 +946,6 @@ transformRangeGraphTable(ParseState *pstate, RangeGraphTable *rgt)
 	ListCell   *lc;
 	int			resno = 0;
 	bool		saved_hasSublinks;
-	bool		saved_hasAggs;
-	bool		saved_hasWindowFuncs;
-	bool		saved_hasTargetSRFs;
 
 	rel = parserOpenPropGraph(pstate, rgt->graph_name, AccessShareLock);
 
@@ -970,13 +967,6 @@ transformRangeGraphTable(ParseState *pstate, RangeGraphTable *rgt)
 	saved_hasSublinks = pstate->p_hasSubLinks;
 	pstate->p_hasSubLinks = false;
 
-	saved_hasAggs = pstate->p_hasAggs;
-	pstate->p_hasAggs = false;
-	saved_hasWindowFuncs = pstate->p_hasWindowFuncs;
-	pstate->p_hasWindowFuncs = false;
-	saved_hasTargetSRFs = pstate->p_hasTargetSRFs;
-	pstate->p_hasTargetSRFs = false;
-
 	gp = transformGraphPattern(pstate, rgt->graph_pattern);
 
 	/*
@@ -991,7 +981,7 @@ transformRangeGraphTable(ParseState *pstate, RangeGraphTable *rgt)
 		TargetEntry *te;
 		char	   *colname;
 
-		colexpr = transformExpr(pstate, rt->val, EXPR_KIND_SELECT_TARGET);
+		colexpr = transformExpr(pstate, rt->val, EXPR_KIND_GRAPH_TABLE_COLUMNS);
 
 		if (rt->name)
 			colname = rt->name;
@@ -1041,26 +1031,6 @@ transformRangeGraphTable(ParseState *pstate, RangeGraphTable *rgt)
 				 errmsg("subqueries within GRAPH_TABLE reference are not supported")));
 	pstate->p_hasSubLinks = saved_hasSublinks;
 
-	/*
-	 * GRAPH_TABLE cannot yet evaluate aggregate, window, or set-returning
-	 * functions in its COLUMNS list, so prohibit them for now.
-	 */
-	if (pstate->p_hasAggs)
-		ereport(ERROR,
-				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				errmsg("aggregate functions in GRAPH_TABLE COLUMNS are not supported"));
-	if (pstate->p_hasWindowFuncs)
-		ereport(ERROR,
-				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				errmsg("window functions in GRAPH_TABLE COLUMNS are not supported"));
-	if (pstate->p_hasTargetSRFs)
-		ereport(ERROR,
-				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				errmsg("set-returning functions in GRAPH_TABLE COLUMNS are not supported"));
-	pstate->p_hasAggs = saved_hasAggs;
-	pstate->p_hasWindowFuncs = saved_hasWindowFuncs;
-	pstate->p_hasTargetSRFs = saved_hasTargetSRFs;
-
 	return addRangeTableEntryForGraphTable(pstate, graphid, castNode(GraphPattern, gp), columns, colnames, rgt->alias, false, true);
 }
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 30c889f505f..f1c6aa236cf 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -579,6 +579,8 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 		case EXPR_KIND_GENERATED_COLUMN:
 		case EXPR_KIND_CYCLE_MARK:
 		case EXPR_KIND_PROPGRAPH_PROPERTY:
+		case EXPR_KIND_GRAPH_TABLE_COLUMNS:
+		case EXPR_KIND_GRAPH_TABLE_WHERE:
 			/* okay */
 			break;
 
@@ -1843,6 +1845,8 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_VALUES:
 		case EXPR_KIND_VALUES_SINGLE:
 		case EXPR_KIND_CYCLE_MARK:
+		case EXPR_KIND_GRAPH_TABLE_COLUMNS:
+		case EXPR_KIND_GRAPH_TABLE_WHERE:
 			/* okay */
 			break;
 		case EXPR_KIND_CHECK_CONSTRAINT:
@@ -3255,6 +3259,10 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "property definition expression";
 		case EXPR_KIND_FOR_PORTION:
 			return "FOR PORTION OF";
+		case EXPR_KIND_GRAPH_TABLE_COLUMNS:
+			return "GRAPH_TABLE COLUMNS";
+		case EXPR_KIND_GRAPH_TABLE_WHERE:
+			return "GRAPH_TABLE WHERE";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index c87804f5d41..0bb3398ce25 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2848,6 +2848,10 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
 		case EXPR_KIND_FOR_PORTION:
 			err = _("set-returning functions are not allowed in FOR PORTION OF expressions");
 			break;
+		case EXPR_KIND_GRAPH_TABLE_COLUMNS:
+		case EXPR_KIND_GRAPH_TABLE_WHERE:
+			err = _("set-returning functions are not allowed in GRAPH_TABLE");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_graphtable.c b/src/backend/parser/parse_graphtable.c
index 73fbfb541f7..e323376f0ea 100644
--- a/src/backend/parser/parse_graphtable.c
+++ b/src/backend/parser/parse_graphtable.c
@@ -92,7 +92,7 @@ transformGraphTablePropertyRef(ParseState *pstate, ColumnRef *cref)
 
 		if (IsA(field1, A_Star) || IsA(field2, A_Star))
 		{
-			if (pstate->p_expr_kind == EXPR_KIND_SELECT_TARGET)
+			if (pstate->p_expr_kind == EXPR_KIND_GRAPH_TABLE_COLUMNS)
 				ereport(ERROR,
 						errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						errmsg("\"*\" is not supported here"),
@@ -251,7 +251,7 @@ transformGraphElementPattern(ParseState *pstate, GraphElementPattern *gep)
 
 	gep->labelexpr = transformLabelExpr(gpstate, gep->labelexpr);
 
-	gep->whereClause = transformExpr(pstate, gep->whereClause, EXPR_KIND_WHERE);
+	gep->whereClause = transformExpr(pstate, gep->whereClause, EXPR_KIND_GRAPH_TABLE_WHERE);
 
 	/*
 	 * Assign collations here for the reason mentioned in the prologue of
@@ -387,7 +387,7 @@ transformGraphPattern(ParseState *pstate, GraphPattern *graph_pattern)
 											 transformPathPatternList(pstate, graph_pattern->path_pattern_list));
 
 	graph_pattern->path_pattern_list = path_pattern_list;
-	graph_pattern->whereClause = transformExpr(pstate, graph_pattern->whereClause, EXPR_KIND_WHERE);
+	graph_pattern->whereClause = transformExpr(pstate, graph_pattern->whereClause, EXPR_KIND_GRAPH_TABLE_WHERE);
 	assign_expr_collations(pstate, graph_pattern->whereClause);
 
 	return (Node *) graph_pattern;
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f7f4ba6c2a8..2b8ac813554 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -84,6 +84,8 @@ typedef enum ParseExprKind
 	EXPR_KIND_GENERATED_COLUMN, /* generation expression for a column */
 	EXPR_KIND_CYCLE_MARK,		/* cycle mark value */
 	EXPR_KIND_PROPGRAPH_PROPERTY,	/* derived property expression */
+	EXPR_KIND_GRAPH_TABLE_COLUMNS,	/* GRAPH_TABLE COLUMNS list item */
+	EXPR_KIND_GRAPH_TABLE_WHERE,	/* WHERE in a GRAPH_TABLE pattern */
 } ParseExprKind;
 
 
diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out
index cde3114ebf4..3894f0f8aed 100644
--- a/src/test/regress/expected/graph_table.out
+++ b/src/test/regress/expected/graph_table.out
@@ -471,13 +471,37 @@ SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.* IS NOT NULL)-[
 ERROR:  "*" not allowed here
 LINE 1: ...M GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.* IS NOT...
                                                              ^
--- aggregate, window, and set-returning functions are not supported in COLUMNS
+-- aggregate, grouping, window, and set-returning functions are not allowed
+-- in the COLUMNS list or the graph pattern WHERE, including an aggregate that
+-- references an outer query
 SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (count(*) AS num));
-ERROR:  aggregate functions in GRAPH_TABLE COLUMNS are not supported
+ERROR:  aggregate functions are not allowed in GRAPH_TABLE
+LINE 1: ...APH_TABLE (myshop MATCH (c IS customers) COLUMNS (count(*) A...
+                                                             ^
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (GROUPING(c.customer_id) AS g));
+ERROR:  grouping operations are not allowed in GRAPH_TABLE
+LINE 1: ...APH_TABLE (myshop MATCH (c IS customers) COLUMNS (GROUPING(c...
+                                                             ^
 SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (row_number() OVER () AS rn));
-ERROR:  window functions in GRAPH_TABLE COLUMNS are not supported
+ERROR:  window functions are not allowed in GRAPH_TABLE
+LINE 1: ...APH_TABLE (myshop MATCH (c IS customers) COLUMNS (row_number...
+                                                             ^
 SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (generate_series(1, 2) AS gs));
-ERROR:  set-returning functions in GRAPH_TABLE COLUMNS are not supported
+ERROR:  set-returning functions are not allowed in GRAPH_TABLE
+LINE 1: ...APH_TABLE (myshop MATCH (c IS customers) COLUMNS (generate_s...
+                                                             ^
+SELECT (SELECT num FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (count(o.customer_id) AS num)) t) FROM customers o;
+ERROR:  aggregate functions are not allowed in GRAPH_TABLE
+LINE 1: ...APH_TABLE (myshop MATCH (c IS customers) COLUMNS (count(o.cu...
+                                                             ^
+SELECT (SELECT nm FROM GRAPH_TABLE (myshop MATCH (c IS customers) WHERE count(o.customer_id) > 0 COLUMNS (c.name AS nm)) t) FROM customers o;
+ERROR:  aggregate functions are not allowed in GRAPH_TABLE
+LINE 1: ... GRAPH_TABLE (myshop MATCH (c IS customers) WHERE count(o.cu...
+                                                             ^
+SELECT (SELECT nm FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE count(o.customer_id) > 0) COLUMNS (c.name AS nm)) t) FROM customers o;
+ERROR:  aggregate functions are not allowed in GRAPH_TABLE
+LINE 1: ...M GRAPH_TABLE (myshop MATCH (c IS customers WHERE count(o.cu...
+                                                             ^
 -- consecutive element patterns with same kind
 SELECT * FROM GRAPH_TABLE (g1 MATCH ()() COLUMNS (1 as one));
 ERROR:  adjacent vertex patterns are not supported
diff --git a/src/test/regress/sql/graph_table.sql b/src/test/regress/sql/graph_table.sql
index 7a4189833d8..05ea2b5f02f 100644
--- a/src/test/regress/sql/graph_table.sql
+++ b/src/test/regress/sql/graph_table.sql
@@ -306,10 +306,16 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (src IS el1 | vl1)-[conn]->(dest) COLUMNS (c
 SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.*));
 -- star anywhere else is not allowed as a property reference
 SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.* IS NOT NULL)-[IS customer_orders]->(o IS orders) COLUMNS (c.name));
--- aggregate, window, and set-returning functions are not supported in COLUMNS
+-- aggregate, grouping, window, and set-returning functions are not allowed
+-- in the COLUMNS list or the graph pattern WHERE, including an aggregate that
+-- references an outer query
 SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (count(*) AS num));
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (GROUPING(c.customer_id) AS g));
 SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (row_number() OVER () AS rn));
 SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (generate_series(1, 2) AS gs));
+SELECT (SELECT num FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (count(o.customer_id) AS num)) t) FROM customers o;
+SELECT (SELECT nm FROM GRAPH_TABLE (myshop MATCH (c IS customers) WHERE count(o.customer_id) > 0 COLUMNS (c.name AS nm)) t) FROM customers o;
+SELECT (SELECT nm FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE count(o.customer_id) > 0) COLUMNS (c.name AS nm)) t) FROM customers o;
 -- consecutive element patterns with same kind
 SELECT * FROM GRAPH_TABLE (g1 MATCH ()() COLUMNS (1 as one));
 SELECT * FROM GRAPH_TABLE (g1 MATCH -> COLUMNS (1 AS one));
-- 
2.47.3

