From e313772a0fbfcdf3df1f8b77eaf477ea102f15f2 Mon Sep 17 00:00:00 2001 From: Junwang Zhao 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 Author: Junwang Zhao Author: Henson Choi 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