From 8e9c9bd41f92f9c7fa84ff3475dd2a0aa1343af9 Mon Sep 17 00:00:00 2001 From: Junwang Zhao 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 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 | 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