From e313772a0fbfcdf3df1f8b77eaf477ea102f15f2 Mon Sep 17 00:00:00 2001
From: Junwang Zhao <zhjwpku@gmail.com>
Date: Wed, 18 Mar 2026 19:48:30 +0800
Subject: [PATCH] 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     | 22 ++++++++--
 src/backend/rewrite/rewriteGraphTable.c   |  2 +-
 src/test/regress/expected/graph_table.out | 50 +++++++++++++++++++++++
 src/test/regress/sql/graph_table.sql      | 43 +++++++++++++++++++
 4 files changed, 113 insertions(+), 4 deletions(-)

diff --git a/src/backend/parser/parse_graphtable.c b/src/backend/parser/parse_graphtable.c
index bf805b4beb6..16db46f78ff 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,24 @@ 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..9c99af4d6a5 100644
--- a/src/test/regress/expected/graph_table.out
+++ b/src/test/regress/expected/graph_table.out
@@ -40,6 +40,15 @@ CREATE TABLE customer_wishlists (
     customer_id integer REFERENCES customers (customer_id),
     wishlist_id integer REFERENCES wishlists (wishlist_id)
 );
+CREATE TABLE users (
+    user_id integer PRIMARY KEY,
+    name text
+);
+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)
+);
 CREATE PROPERTY GRAPH myshop
     VERTEX TABLES (
         products,
@@ -73,6 +82,16 @@ CREATE PROPERTY GRAPH myshop
             DEFAULT LABEL
             LABEL cust_lists PROPERTIES (customer_id, wishlist_id AS link_id)
     );
+CREATE PROPERTY GRAPH social
+    VERTEX TABLES (
+        users LABEL users
+    )
+    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)
+            DEFAULT LABEL
+    );
 SELECT customer_name FROM GRAPH_TABLE (xxx MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name));  -- error
 ERROR:  relation "xxx" does not exist
 LINE 1: SELECT customer_name FROM GRAPH_TABLE (xxx MATCH (c IS custo...
@@ -125,6 +144,10 @@ INSERT INTO customers VALUES
     (1, 'customer1', 'US'),
     (2, 'customer2', 'CA'),
     (3, 'customer3', 'GL');
+INSERT INTO users VALUES
+    (1, 'alice'),
+    (2, 'bob'),
+    (3, 'carol');
 INSERT INTO orders VALUES
     (1, date '2024-01-01'),
     (2, date '2024-01-02'),
@@ -149,6 +172,10 @@ INSERT INTO wishlist_items (wishlist_items_id, wishlist_id, product_no) VALUES
     (2, 1, 3),
     (3, 2, 1),
     (4, 3, 1);
+INSERT INTO user_follows (user_follows_id, src_user_id, dst_user_id) VALUES
+    (1, 1, 3),
+    (2, 2, 3),
+    (3, 3, 1);
 -- single element path pattern
 SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (c.name));
    name    
@@ -233,6 +260,29 @@ SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)->(o IS orders) COLUMNS
  customer2 | 01-02-2024
 (2 rows)
 
+-- cross-element comparison in WHERE
+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)
+
 -- 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..b8c593acf60 100644
--- a/src/test/regress/sql/graph_table.sql
+++ b/src/test/regress/sql/graph_table.sql
@@ -49,6 +49,17 @@ CREATE TABLE customer_wishlists (
     wishlist_id integer REFERENCES wishlists (wishlist_id)
 );
 
+CREATE TABLE users (
+    user_id integer PRIMARY KEY,
+    name text
+);
+
+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)
+);
+
 CREATE PROPERTY GRAPH myshop
     VERTEX TABLES (
         products,
@@ -83,6 +94,17 @@ CREATE PROPERTY GRAPH myshop
             LABEL cust_lists PROPERTIES (customer_id, wishlist_id AS link_id)
     );
 
+CREATE PROPERTY GRAPH social
+    VERTEX TABLES (
+        users LABEL users
+    )
+    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)
+            DEFAULT LABEL
+    );
+
 SELECT customer_name FROM GRAPH_TABLE (xxx MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name));  -- error
 SELECT customer_name FROM GRAPH_TABLE (pg_class MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name));  -- error
 SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (cx.name AS customer_name));  -- error
@@ -107,6 +129,10 @@ INSERT INTO customers VALUES
     (1, 'customer1', 'US'),
     (2, 'customer2', 'CA'),
     (3, 'customer3', 'GL');
+INSERT INTO users VALUES
+    (1, 'alice'),
+    (2, 'bob'),
+    (3, 'carol');
 INSERT INTO orders VALUES
     (1, date '2024-01-01'),
     (2, date '2024-01-02'),
@@ -131,6 +157,10 @@ INSERT INTO wishlist_items (wishlist_items_id, wishlist_id, product_no) VALUES
     (2, 1, 3),
     (3, 2, 1),
     (4, 3, 1);
+INSERT INTO user_follows (user_follows_id, src_user_id, dst_user_id) VALUES
+    (1, 1, 3),
+    (2, 2, 3),
+    (3, 3, 1);
 
 -- single element path pattern
 SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (c.name));
@@ -150,6 +180,19 @@ 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;
 
+-- cross-element comparison in WHERE
+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;
+
 -- lateral test
 CREATE TABLE x1 (a int, b text);
 INSERT INTO x1 VALUES (1, 'one'), (2, 'two');
-- 
2.41.0

