From 7d6b309246a188f1cefb84c11d85865a47729100 Mon Sep 17 00:00:00 2001
From: Sami Imseih <samimseih@gmail.com>
Date: Wed, 12 Aug 2026 14:19:42 +0000
Subject: [PATCH v1 1/1] Disallow outer-reference and WHERE-clause aggregates
 in GRAPH_TABLE

Commit f585671055d1 disallowed aggregates, window functions, and SRFs
in a GRAPH_TABLE COLUMNS list. Aggregates were detected with
pstate->p_hasAggs after transforming the columns, while window functions
and SRFs used the separate p_hasWindowFuncs and p_hasTargetSRFs flags.
The aggregate check has two gaps.

1. An aggregate that references an outer query belongs to a parent query
level, so check_agglevels_and_constraints sets p_hasAggs on the parent
ParseState, not the GRAPH_TABLE's own. The local flag stays false and
the aggregate is not identified.

2. The graph pattern WHERE clause was not checked at all. A local
aggregate there is already rejected with "aggregate functions are not
allowed in WHERE", but an outer-referencing one is attributed to a
parent level and escapes that check as well.

In either case the query reaches the executor and fails with the
internal error "Aggref found in non-Agg plan node". These shapes do not
trip the assertion seen in f585671055d1, but reaching an internal
"planner messed up" error from user SQL is itself the bug. An
unsupported feature should be rejected cleanly at parse time.

Close both gaps by walking the transformed COLUMNS list and the graph
pattern for Aggref and GroupingFunc nodes. Unlike p_hasAggs, the walk
detects an aggregate by its presence in those trees rather than by the
query level it belongs to. Window functions and SRFs always mark the
local ParseState, so their existing flag checks are unchanged.

Add tests covering these cases.
---
 src/backend/parser/parse_clause.c         | 34 +++++++++++++++++++----
 src/test/regress/expected/graph_table.out |  8 ++++++
 src/test/regress/sql/graph_table.sql      |  6 ++++
 3 files changed, 42 insertions(+), 6 deletions(-)

diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 68b525ffdcc..33977767280 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -931,6 +931,24 @@ parserOpenPropGraph(ParseState *pstate, const RangeVar *relation, LOCKMODE lockm
 	return rel;
 }
 
+/*
+ * Walk a GRAPH_TABLE expression tree looking for an Aggref or GroupingFunc.
+ * We can't test pstate->p_hasAggs, since an outer-referencing aggregate sets
+ * that flag on a parent ParseState, not ours.
+ */
+static bool
+graph_table_has_aggs_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Aggref) || IsA(node, GroupingFunc))
+		return true;
+
+	return expression_tree_walker(node, graph_table_has_aggs_walker,
+								  context);
+}
+
 /*
  * transformRangeGraphTable -- transform a GRAPH_TABLE clause
  */
@@ -946,7 +964,6 @@ transformRangeGraphTable(ParseState *pstate, RangeGraphTable *rgt)
 	ListCell   *lc;
 	int			resno = 0;
 	bool		saved_hasSublinks;
-	bool		saved_hasAggs;
 	bool		saved_hasWindowFuncs;
 	bool		saved_hasTargetSRFs;
 
@@ -970,8 +987,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;
@@ -1043,12 +1058,20 @@ transformRangeGraphTable(ParseState *pstate, RangeGraphTable *rgt)
 
 	/*
 	 * GRAPH_TABLE cannot yet evaluate aggregate, window, or set-returning
-	 * functions in its COLUMNS list, so prohibit them for now.
+	 * functions, so prohibit them for now.  Aggregates are found by walking
+	 * the COLUMNS list and the graph pattern, because an outer-referencing
+	 * aggregate does not set pstate->p_hasAggs on our ParseState.  Window
+	 * functions and SRFs always set their flag on our own ParseState, so a
+	 * flag test finds them.
 	 */
-	if (pstate->p_hasAggs)
+	if (graph_table_has_aggs_walker((Node *) columns, NULL))
 		ereport(ERROR,
 				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				errmsg("aggregate functions in GRAPH_TABLE COLUMNS are not supported"));
+	if (graph_table_has_aggs_walker((Node *) gp, NULL))
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("aggregate functions in GRAPH_TABLE WHERE clause are not supported"));
 	if (pstate->p_hasWindowFuncs)
 		ereport(ERROR,
 				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -1057,7 +1080,6 @@ transformRangeGraphTable(ParseState *pstate, RangeGraphTable *rgt)
 		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;
 
diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out
index cde3114ebf4..0b9a0856d65 100644
--- a/src/test/regress/expected/graph_table.out
+++ b/src/test/regress/expected/graph_table.out
@@ -478,6 +478,14 @@ SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (row_number() O
 ERROR:  window functions in GRAPH_TABLE COLUMNS are not supported
 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
+-- also rejected when the aggregate references an outer query
+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 in GRAPH_TABLE COLUMNS are not supported
+-- likewise for an outer-referencing aggregate in a pattern WHERE clause
+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 in GRAPH_TABLE WHERE clause are not supported
 -- 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..4369921fafb 100644
--- a/src/test/regress/sql/graph_table.sql
+++ b/src/test/regress/sql/graph_table.sql
@@ -310,6 +310,12 @@ SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.* IS NOT NULL)-[
 SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (count(*) AS num));
 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));
+-- also rejected when the aggregate references an outer query
+SELECT (SELECT num FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (count(o.customer_id) AS num)) t)
+FROM customers o;
+-- likewise for an outer-referencing aggregate in a pattern WHERE clause
+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

