v20251215-0002-Edge-in-any-direction-bug.patch
text/x-patch
Filename: v20251215-0002-Edge-in-any-direction-bug.patch
Type: text/x-patch
Part: 0
From 469b2c0f42895ef21557ae13b097496332fdafef Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
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