From 469b2c0f42895ef21557ae13b097496332fdafef Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat Date: Mon, 15 Dec 2025 18:24:47 +0530 Subject: [PATCH v20251215 2/2] Edge in any direction bug When an edge table connects vertices from the same table, an edge in any direction pattern, e.g. ()-[]-(), was matching vertices only in one direction. The qual for such a path pattern need to have two sets of quals, one for each direction, disjuncted. The disjunction makes sure that an edge in either direction is selected. Author: Ashutosh Bapat Reported-by: Peter Eisentraut --- src/backend/rewrite/rewriteGraphTable.c | 77 ++++++++++++----------- src/test/regress/expected/graph_table.out | 30 ++++++++- src/test/regress/sql/graph_table.sql | 8 ++- 3 files changed, 78 insertions(+), 37 deletions(-) diff --git a/src/backend/rewrite/rewriteGraphTable.c b/src/backend/rewrite/rewriteGraphTable.c index 06bcb982774..3b319639b81 100644 --- a/src/backend/rewrite/rewriteGraphTable.c +++ b/src/backend/rewrite/rewriteGraphTable.c @@ -276,9 +276,8 @@ generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern) * * Edge pointing in any direction is treated similar to that pointing * in right direction here. When constructing a query in - * generate_query_for_graph_path(), we will swap source and - * destination elements if the edge element turns out to be an edge - * pointing in the left direction. + * generate_query_for_graph_path(), we will try links in both the + * directions. * * If multiple edge patterns share the same variable name, they * constrain the adjacent vertex patterns since an edge can connect @@ -426,8 +425,7 @@ generate_query_for_graph_path(RangeTblEntry *rte, List *graph_path) { struct path_element *src_pe; struct path_element *dest_pe; - List *src_quals; - List *dest_quals; + Expr *edge_qual = NULL; Assert(pf->src_pf && pf->dest_pf); src_pe = list_nth(graph_path, pf->src_pf->factorpos); @@ -437,41 +435,50 @@ generate_query_for_graph_path(RangeTblEntry *rte, List *graph_path) Assert(pf->src_pf == src_pe->path_factor && pf->dest_pf == dest_pe->path_factor); + if (src_pe->elemoid == pe->srcvertexid && + dest_pe->elemoid == pe->destvertexid) + edge_qual = makeBoolExpr(AND_EXPR, + list_concat(copyObject(pe->src_quals), + copyObject(pe->dest_quals)), + -1); + /* - * If the given edge element does not connect the adjacent vertex - * elements in this path, the path is broken. Abandon this path as - * it won't return any rows. - * - * For an edge element pattern pointing in any direction, try - * swapping the source and destination vertex elements. + * An edge pattern in any direction matches edges in both + * directions, try swapping source and destination. When the + * source and destination is the same vertex table, quals + * corresponding to either direction may get satisfied. Hence OR + * the quals corresponding to both the directions. */ - if (src_pe->elemoid != pe->srcvertexid || - dest_pe->elemoid != pe->destvertexid) + if (pf->kind == EDGE_PATTERN_ANY && + dest_pe->elemoid == pe->srcvertexid && + src_pe->elemoid == pe->destvertexid) { - if (pf->kind == EDGE_PATTERN_ANY && - dest_pe->elemoid == pe->srcvertexid && - src_pe->elemoid == pe->destvertexid) - { - dest_quals = copyObject(pe->src_quals); - src_quals = copyObject(pe->dest_quals); - - /* Swap the source and destination varnos in the quals. */ - ChangeVarNodes((Node *) dest_quals, pe->path_factor->src_pf->factorpos + 1, - pe->path_factor->dest_pf->factorpos + 1, 0); - ChangeVarNodes((Node *) src_quals, pe->path_factor->dest_pf->factorpos + 1, - pe->path_factor->src_pf->factorpos + 1, 0); - } + List *src_quals = copyObject(pe->dest_quals); + List *dest_quals = copyObject(pe->src_quals); + Expr *rev_edge_qual; + + /* Swap the source and destination varnos in the quals. */ + ChangeVarNodes((Node *) dest_quals, pe->path_factor->src_pf->factorpos + 1, + pe->path_factor->dest_pf->factorpos + 1, 0); + ChangeVarNodes((Node *) src_quals, pe->path_factor->dest_pf->factorpos + 1, + pe->path_factor->src_pf->factorpos + 1, 0); + + rev_edge_qual = makeBoolExpr(AND_EXPR, list_concat(src_quals, dest_quals), -1); + if (edge_qual) + edge_qual = makeBoolExpr(OR_EXPR, list_make2(edge_qual, rev_edge_qual), -1); else - return NULL; - } - else - { - src_quals = copyObject(pe->src_quals); - dest_quals = copyObject(pe->dest_quals); + edge_qual = rev_edge_qual; } - qual_exprs = list_concat(qual_exprs, src_quals); - qual_exprs = list_concat(qual_exprs, dest_quals); + /* + * If the given edge element does not connect the adjacent vertex + * elements in this path, the path is broken. Abandon this path as + * it won't return any rows. + */ + if (edge_qual == NULL) + return NULL; + + qual_exprs = lappend(qual_exprs, edge_qual); } else Assert(!pe->src_quals && !pe->dest_quals); @@ -525,7 +532,7 @@ generate_query_for_graph_path(RangeTblEntry *rte, List *graph_path) } path_query->jointree = makeFromExpr(fromlist, - (Node *) makeBoolExpr(AND_EXPR, qual_exprs, -1)); + qual_exprs ? (Node *) makeBoolExpr(AND_EXPR, qual_exprs, -1) : NULL); /* Construct query targetlist from COLUMNS specification of GRAPH_TABLE. */ path_query->targetList = castNode(List, diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out index ee1424127ea..ecac6096720 100644 --- a/src/test/regress/expected/graph_table.out +++ b/src/test/regress/expected/graph_table.out @@ -553,7 +553,7 @@ ALTER PROPERTY GRAPH g1 ADD EDGE TABLES ( e3_3 KEY (src_id, dest_id) SOURCE KEY (src_id) REFERENCES v3 (id) - DESTINATION KEY (src_id) REFERENCES v3 (id) + DESTINATION KEY (dest_id) REFERENCES v3 (id) LABEL el2 PROPERTIES (ename, eprop1 * 10 AS lprop2) LABEL l1 PROPERTIES (ename AS elname) ); @@ -566,6 +566,19 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (a.vname AS se SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(c)-[b]->(d) COLUMNS (a.vname AS aname, b.ename AS bname, c.vname AS cname, d.vname AS dname)); --error ERROR: An edge can not connect more than two vertexes even in a cyclic pattern. +-- the looping edge should be reported only once even when edge pattern with any direction is used +SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[c]-(a) COLUMNS (a.vname AS self, c.ename AS loop_name)); + self | loop_name +------+----------- + v33 | e331 +(1 row) + +SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-(a) COLUMNS (a.vname AS self)); + self +------ + v33 +(1 row) + -- test collation specified in the expression INSERT INTO e3_3 VALUES (2003, 2003, 'E331', 10011); SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (a.vname AS self, b.ename AS loop_name)) ORDER BY loop_name COLLATE "C" ASC; @@ -802,6 +815,21 @@ SELECT * FROM GRAPH_TABLE (g4 MATCH (s IS ptnv)-[e IS ptne]->(d IS ptnv) COLUMNS 30 | 300 | 10 (3 rows) +-- edges from the same vertex in both directions connecting to other vertexes in the same table +SELECT * FROM GRAPH_TABLE (g4 MATCH (s)-[e]-(d) WHERE s.id = 3 COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3; + val | val | val +-----+-----+----- + 30 | 200 | 20 + 30 | 300 | 10 +(2 rows) + +SELECT * FROM GRAPH_TABLE (g4 MATCH (s WHERE s.id = 3)-[e]-(d) COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3; + val | val | val +-----+-----+----- + 30 | 200 | 20 + 30 | 300 | 10 +(2 rows) + CREATE VIEW customers_us AS SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name)); SELECT pg_get_viewdef('customers_us'::regclass); pg_get_viewdef diff --git a/src/test/regress/sql/graph_table.sql b/src/test/regress/sql/graph_table.sql index afafeb2efa9..412da394aab 100644 --- a/src/test/regress/sql/graph_table.sql +++ b/src/test/regress/sql/graph_table.sql @@ -356,7 +356,7 @@ ALTER PROPERTY GRAPH g1 ADD EDGE TABLES ( e3_3 KEY (src_id, dest_id) SOURCE KEY (src_id) REFERENCES v3 (id) - DESTINATION KEY (src_id) REFERENCES v3 (id) + DESTINATION KEY (dest_id) REFERENCES v3 (id) LABEL el2 PROPERTIES (ename, eprop1 * 10 AS lprop2) LABEL l1 PROPERTIES (ename AS elname) ); @@ -364,6 +364,9 @@ ALTER PROPERTY GRAPH g1 INSERT INTO e3_3 VALUES (2003, 2003, 'e331', 10010); SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (a.vname AS self, b.ename AS loop_name)); SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(c)-[b]->(d) COLUMNS (a.vname AS aname, b.ename AS bname, c.vname AS cname, d.vname AS dname)); --error +-- the looping edge should be reported only once even when edge pattern with any direction is used +SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[c]-(a) COLUMNS (a.vname AS self, c.ename AS loop_name)); +SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-(a) COLUMNS (a.vname AS self)); -- test collation specified in the expression INSERT INTO e3_3 VALUES (2003, 2003, 'E331', 10011); @@ -480,6 +483,9 @@ CREATE PROPERTY GRAPH g4 DESTINATION KEY (dest) REFERENCES ptnv(id) ); SELECT * FROM GRAPH_TABLE (g4 MATCH (s IS ptnv)-[e IS ptne]->(d IS ptnv) COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3; +-- edges from the same vertex in both directions connecting to other vertexes in the same table +SELECT * FROM GRAPH_TABLE (g4 MATCH (s)-[e]-(d) WHERE s.id = 3 COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3; +SELECT * FROM GRAPH_TABLE (g4 MATCH (s WHERE s.id = 3)-[e]-(d) COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3; CREATE VIEW customers_us AS SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name)); -- 2.34.1