From 8e9c9bd41f92f9c7fa84ff3475dd2a0aa1343af9 Mon Sep 17 00:00:00 2001
From: Junwang Zhao <zhjwpku@gmail.com>
Date: Wed, 18 Mar 2026 19:48:30 +0800
Subject: [PATCH v2] GRAPH_TABLE: collect variables across full clause

SQL/PGQ variable scope is the whole GRAPH_TABLE, so gather element variables
up-front before transforming element expressions. Also fix whereClause property
ref replacement to use the full graph path.

Reported-by: zengman <zengman@halodbtech.com>
Author: Junwang Zhao <zhjwpku@gmail.com>
Author: Henson Choi <assam258@gmail.com>

Discussion: https://www.postgresql.org/message-id/CAAAe_zD9FP-sVqNs5-Dwc0CqNqT2B40CsYnwDGXpOLMT5KnhSg%40mail.gmail.com
---
 src/backend/parser/parse_graphtable.c     | 23 ++++++--
 src/backend/rewrite/rewriteGraphTable.c   |  2 +-
 src/test/regress/expected/graph_table.out | 64 +++++++++++++++++++++++
 src/test/regress/sql/graph_table.sql      | 54 +++++++++++++++++++
 4 files changed, 139 insertions(+), 4 deletions(-)

diff --git a/src/backend/parser/parse_graphtable.c b/src/backend/parser/parse_graphtable.c
index bf805b4beb6..d2096dd687f 100644
--- a/src/backend/parser/parse_graphtable.c
+++ b/src/backend/parser/parse_graphtable.c
@@ -235,9 +235,6 @@ transformGraphElementPattern(ParseState *pstate, GraphElementPattern *gep)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("element pattern quantifier is not supported")));
 
-	if (gep->variable)
-		gpstate->variables = lappend(gpstate->variables, makeString(pstrdup(gep->variable)));
-
 	gep->labelexpr = transformLabelExpr(gpstate, gep->labelexpr);
 
 	gep->whereClause = transformExpr(pstate, gep->whereClause, EXPR_KIND_WHERE);
@@ -268,6 +265,7 @@ static Node *
 transformPathPatternList(ParseState *pstate, List *path_pattern)
 {
 	List	   *result = NIL;
+	GraphTableParseState *gpstate = pstate->p_graph_table_pstate;
 
 	/* Grammar doesn't allow empty path pattern list */
 	Assert(list_length(path_pattern) > 0);
@@ -281,6 +279,25 @@ transformPathPatternList(ParseState *pstate, List *path_pattern)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("multiple path patterns in one GRAPH_TABLE clause not supported")));
 
+	/*
+	 * SQL/PGQ standard says variable scope is the whole GRAPH_TABLE (SR 18),
+	 * so collect variables across all path patterns/terms before transforming
+	 * any element pattern expressions.
+	 */
+	foreach_node(List, path_term, path_pattern)
+	{
+		foreach_node(GraphElementPattern, gep, path_term)
+		{
+			if (gep->variable)
+			{
+				String	   *v = makeString(pstrdup(gep->variable));
+
+				if (!list_member(gpstate->variables, v))
+					gpstate->variables = lappend(gpstate->variables, v);
+			}
+		}
+	}
+
 	foreach_node(List, path_term, path_pattern)
 		result = lappend(result, transformPathTerm(pstate, path_term));
 
diff --git a/src/backend/rewrite/rewriteGraphTable.c b/src/backend/rewrite/rewriteGraphTable.c
index 06f2f3442d8..d3f382573fd 100644
--- a/src/backend/rewrite/rewriteGraphTable.c
+++ b/src/backend/rewrite/rewriteGraphTable.c
@@ -519,7 +519,7 @@ generate_query_for_graph_path(RangeTblEntry *rte, List *graph_path)
 		{
 			Node	   *tr;
 
-			tr = replace_property_refs(rte->relid, pf->whereClause, list_make1(pe));
+			tr = replace_property_refs(rte->relid, pf->whereClause, graph_path);
 
 			qual_exprs = lappend(qual_exprs, tr);
 		}
diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out
index 27c81ec6e42..261f0b40d0e 100644
--- a/src/test/regress/expected/graph_table.out
+++ b/src/test/regress/expected/graph_table.out
@@ -233,6 +233,70 @@ SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)->(o IS orders) COLUMNS
  customer2 | 01-02-2024
 (2 rows)
 
+CREATE TABLE users (
+    user_id integer PRIMARY KEY,
+    name text,
+    birthdate date
+);
+CREATE TABLE user_follows (
+    user_follows_id integer PRIMARY KEY,
+    src_user_id integer REFERENCES users (user_id),
+    dst_user_id integer REFERENCES users (user_id),
+    followed_since date
+);
+CREATE PROPERTY GRAPH social
+    VERTEX TABLES (
+        users LABEL users PROPERTIES (name, birthdate)
+    )
+    EDGE TABLES (
+        user_follows KEY (user_follows_id)
+            SOURCE KEY (src_user_id) REFERENCES users (user_id)
+            DESTINATION KEY (dst_user_id) REFERENCES users (user_id)
+            LABEL user_follows PROPERTIES (followed_since)
+    );
+INSERT INTO users VALUES
+    (1, 'alice', date '2006-01-01'),
+    (2, 'bob', date '2000-06-15'),
+    (3, 'carol', date '2008-03-10');
+INSERT INTO user_follows (user_follows_id, src_user_id, dst_user_id, followed_since) VALUES
+    (1, 1, 3, date '2020-01-01'),   -- alice followed carol when 14, before her 18th
+    (2, 2, 3, date '2020-01-01'),   -- bob followed carol when 20, after his 18th
+    (3, 3, 1, date '2020-01-01');   -- carol followed alice when 12, before her 18th
+-- vertex-to-vertex cross-references
+SELECT *
+FROM GRAPH_TABLE (
+    social MATCH (a IS users)-[]->(x IS users)<-[]-(b IS users WHERE b.name != a.name)
+    COLUMNS (a.name AS a_name, x.name AS x_name, b.name AS b_name)
+) ORDER BY 1, 2, 3;
+ a_name | x_name | b_name 
+--------+--------+--------
+ alice  | carol  | bob
+ bob    | carol  | alice
+(2 rows)
+
+SELECT *
+FROM GRAPH_TABLE (
+    social MATCH (a IS users WHERE b.name != a.name)-[]->(x IS users)<-[]-(b IS users)
+    COLUMNS (a.name AS a_name, x.name AS x_name, b.name AS b_name)
+) ORDER BY 1, 2, 3;
+ a_name | x_name | b_name 
+--------+--------+--------
+ alice  | carol  | bob
+ bob    | carol  | alice
+(2 rows)
+
+-- edge WHERE clause referencing vertex variables: a followed b before a's 18th birthday
+SELECT *
+FROM GRAPH_TABLE (
+    social MATCH (a IS users)-[f IS user_follows WHERE f.followed_since < (a.birthdate + interval '18 years')]->(b IS users)
+    COLUMNS (a.name AS follower, b.name AS followed)
+) ORDER BY 1, 2;
+ follower | followed 
+----------+----------
+ alice    | carol
+ carol    | alice
+(2 rows)
+
 -- lateral test
 CREATE TABLE x1 (a int, b text);
 INSERT INTO x1 VALUES (1, 'one'), (2, 'two');
diff --git a/src/test/regress/sql/graph_table.sql b/src/test/regress/sql/graph_table.sql
index 6d2b08b997b..2daa5c465e5 100644
--- a/src/test/regress/sql/graph_table.sql
+++ b/src/test/regress/sql/graph_table.sql
@@ -150,6 +150,60 @@ SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[IS customer_orders | c
 -- vertex to vertex connection abbreviation
 SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)->(o IS orders) COLUMNS (c.name, o.ordered_when)) ORDER BY 1;
 
+CREATE TABLE users (
+    user_id integer PRIMARY KEY,
+    name text,
+    birthdate date
+);
+
+CREATE TABLE user_follows (
+    user_follows_id integer PRIMARY KEY,
+    src_user_id integer REFERENCES users (user_id),
+    dst_user_id integer REFERENCES users (user_id),
+    followed_since date
+);
+
+CREATE PROPERTY GRAPH social
+    VERTEX TABLES (
+        users LABEL users PROPERTIES (name, birthdate)
+    )
+    EDGE TABLES (
+        user_follows KEY (user_follows_id)
+            SOURCE KEY (src_user_id) REFERENCES users (user_id)
+            DESTINATION KEY (dst_user_id) REFERENCES users (user_id)
+            LABEL user_follows PROPERTIES (followed_since)
+    );
+
+INSERT INTO users VALUES
+    (1, 'alice', date '2006-01-01'),
+    (2, 'bob', date '2000-06-15'),
+    (3, 'carol', date '2008-03-10');
+
+INSERT INTO user_follows (user_follows_id, src_user_id, dst_user_id, followed_since) VALUES
+    (1, 1, 3, date '2020-01-01'),   -- alice followed carol when 14, before her 18th
+    (2, 2, 3, date '2020-01-01'),   -- bob followed carol when 20, after his 18th
+    (3, 3, 1, date '2020-01-01');   -- carol followed alice when 12, before her 18th
+
+-- vertex-to-vertex cross-references
+SELECT *
+FROM GRAPH_TABLE (
+    social MATCH (a IS users)-[]->(x IS users)<-[]-(b IS users WHERE b.name != a.name)
+    COLUMNS (a.name AS a_name, x.name AS x_name, b.name AS b_name)
+) ORDER BY 1, 2, 3;
+
+SELECT *
+FROM GRAPH_TABLE (
+    social MATCH (a IS users WHERE b.name != a.name)-[]->(x IS users)<-[]-(b IS users)
+    COLUMNS (a.name AS a_name, x.name AS x_name, b.name AS b_name)
+) ORDER BY 1, 2, 3;
+
+-- edge WHERE clause referencing vertex variables: a followed b before a's 18th birthday
+SELECT *
+FROM GRAPH_TABLE (
+    social MATCH (a IS users)-[f IS user_follows WHERE f.followed_since < (a.birthdate + interval '18 years')]->(b IS users)
+    COLUMNS (a.name AS follower, b.name AS followed)
+) ORDER BY 1, 2;
+
 -- lateral test
 CREATE TABLE x1 (a int, b text);
 INSERT INTO x1 VALUES (1, 'one'), (2, 'two');
-- 
2.41.0

