From a51efd4583f30a1366a992c7d5632b14f77f4e61 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Date: Thu, 2 Jul 2026 10:33:13 +0530
Subject: [PATCH v20260703 11/14] replace_property_refs() ignores the root of
 expression tree

replace_property_refs() called expression_tree_mutator() with the root of the
expression tree as the input node. expression_tree_mutator() does not call the
mutator function on the root node, so the root node remains unchanged. If the
root node is a property reference or a lateral reference -- the two node kinds
that replace_property_refs_mutator() rewrites -- it is returned unchanged.
Modules after the rewriter do not know about property reference nodes resulting
in "ERROR: unrecognized node type: 63". Since varlevelsup of lateral references
is not incremented, they are not resolved correctly in the planner, leading to
many different symptoms. Fix this by calling replace_property_refs_mutator()
directly from replace_property_refs(), similar to how other mutator functions
do.

The only case when a property reference or a lateral reference can be
the root of a GRAPH_TABLE expression tree is when it is a bare property
reference or a bare lateral reference in the WHERE clause. The COLUMNS
clause is passed to replace_property_refs() as a targetlist. Every other
expression has at least one expression node covering the property
reference or a lateral reference in the expression tree. That explains
why this bug was not seen so far.

Author: Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Reported by: Noah Misch <noah@leadboat.com>
Fix suggested by: Noah Misch <noah@leadboat.com>
Discussion: https://www.postgresql.org/message-id/20260630173053.51.noahmisch@microsoft.com
---
 src/backend/rewrite/rewriteGraphTable.c   |  2 +-
 src/test/regress/expected/graph_table.out | 38 +++++++++++++++++------
 src/test/regress/sql/graph_table.sql      | 17 ++++++----
 3 files changed, 41 insertions(+), 16 deletions(-)

diff --git a/src/backend/rewrite/rewriteGraphTable.c b/src/backend/rewrite/rewriteGraphTable.c
index 7df7f9fd70a..a5b1be77a0a 100644
--- a/src/backend/rewrite/rewriteGraphTable.c
+++ b/src/backend/rewrite/rewriteGraphTable.c
@@ -1129,7 +1129,7 @@ replace_property_refs(Oid propgraphid, Node *node, const List *mappings)
 	context.mappings = mappings;
 	context.propgraphid = propgraphid;
 
-	return expression_tree_mutator(node, replace_property_refs_mutator, &context);
+	return replace_property_refs_mutator(node, &context);
 }
 
 /*
diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out
index c0622ab7234..c33712283c1 100644
--- a/src/test/regress/expected/graph_table.out
+++ b/src/test/regress/expected/graph_table.out
@@ -248,12 +248,12 @@ SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)->(o IS orders) COLUMNS
 -- Use table with a column name same as a property in the property graph so as
 -- to test resolution preferences. Property references are preferred over
 -- lateral table references.
-CREATE TABLE x1 (a int, address text);
-INSERT INTO x1 VALUES (1, 'one'), (2, 'two');
+CREATE TABLE x1 (a int, address text, flag boolean);
+INSERT INTO x1 VALUES (1, 'one', true), (2, 'two', false);
 SELECT * FROM x1, GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US' AND c.customer_id = x1.a)-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name, c.customer_id AS cid));
- a | address | customer_name | cid 
----+---------+---------------+-----
- 1 | one     | customer1     |   1
+ a | address | flag | customer_name | cid 
+---+---------+------+---------------+-----
+ 1 | one     | t    | customer1     |   1
 (1 row)
 
 SELECT x1.a, g.* FROM x1, GRAPH_TABLE (myshop MATCH (x1 IS customers WHERE x1.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (x1.name AS customer_name, x1.customer_id AS cid, o.order_id)) g;
@@ -263,6 +263,13 @@ SELECT x1.a, g.* FROM x1, GRAPH_TABLE (myshop MATCH (x1 IS customers WHERE x1.ad
  2 | customer1     |   1 |        1
 (2 rows)
 
+-- bare lateral reference in WHERE clause
+SELECT * FROM x1, GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.customer_id = x1.a) WHERE x1.flag COLUMNS (c.name AS customer_name));
+ a | address | flag | customer_name 
+---+---------+------+---------------
+ 1 | one     | t    | customer1
+(1 row)
+
 -- lateral reference with multi-label pattern, which is rewritten as UNION of
 -- path queries
 SELECT x1.a, g.* FROM x1,
@@ -866,12 +873,12 @@ CREATE TABLE cv2 () INHERITS (pv);
 INSERT INTO pv VALUES (1, 10);
 INSERT INTO cv1 VALUES (2, 20);
 INSERT INTO cv2 VALUES (3, 30);
-CREATE TABLE pe (id int, src int, dest int, val int);
+CREATE TABLE pe (id int, src int, dest int, val int, flag boolean);
 CREATE TABLE ce1 () INHERITS (pe);
 CREATE TABLE ce2 () INHERITS (pe);
-INSERT INTO pe VALUES (1, 1, 2, 100);
-INSERT INTO ce1 VALUES (2, 2, 3, 200);
-INSERT INTO ce2 VALUES (3, 3, 1, 300);
+INSERT INTO pe VALUES (1, 1, 2, 100, false);
+INSERT INTO ce1 VALUES (2, 2, 3, 200, false);
+INSERT INTO ce2 VALUES (3, 3, 1, 300, true);
 CREATE PROPERTY GRAPH g3
     NODE TABLES (
         pv KEY (id)
@@ -889,6 +896,19 @@ SELECT * FROM GRAPH_TABLE (g3 MATCH (s IS pv)-[e IS pe]->(d IS pv) COLUMNS (s.va
   30 | 300 |  10
 (3 rows)
 
+-- bare property reference in WHERE clause
+SELECT * FROM GRAPH_TABLE (g3 MATCH (s IS pv)-[e IS pe WHERE e.flag]->(d IS pv) COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3;
+ val | val | val 
+-----+-----+-----
+  30 | 300 |  10
+(1 row)
+
+SELECT * FROM GRAPH_TABLE (g3 MATCH (s IS pv)-[e IS pe]->(d IS pv) WHERE e.flag COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3;
+ val | val | val 
+-----+-----+-----
+  30 | 300 |  10
+(1 row)
+
 CREATE TEMPORARY PROPERTY GRAPH gtmp
     VERTEX TABLES (
         pv KEY (id)
diff --git a/src/test/regress/sql/graph_table.sql b/src/test/regress/sql/graph_table.sql
index 437fb3b01f2..0bfa8f3ac85 100644
--- a/src/test/regress/sql/graph_table.sql
+++ b/src/test/regress/sql/graph_table.sql
@@ -156,10 +156,12 @@ SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)->(o IS orders) COLUMNS
 -- Use table with a column name same as a property in the property graph so as
 -- to test resolution preferences. Property references are preferred over
 -- lateral table references.
-CREATE TABLE x1 (a int, address text);
-INSERT INTO x1 VALUES (1, 'one'), (2, 'two');
+CREATE TABLE x1 (a int, address text, flag boolean);
+INSERT INTO x1 VALUES (1, 'one', true), (2, 'two', false);
 SELECT * FROM x1, GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US' AND c.customer_id = x1.a)-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name, c.customer_id AS cid));
 SELECT x1.a, g.* FROM x1, GRAPH_TABLE (myshop MATCH (x1 IS customers WHERE x1.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (x1.name AS customer_name, x1.customer_id AS cid, o.order_id)) g;
+-- bare lateral reference in WHERE clause
+SELECT * FROM x1, GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.customer_id = x1.a) WHERE x1.flag COLUMNS (c.name AS customer_name));
 -- lateral reference with multi-label pattern, which is rewritten as UNION of
 -- path queries
 SELECT x1.a, g.* FROM x1,
@@ -485,12 +487,12 @@ CREATE TABLE cv2 () INHERITS (pv);
 INSERT INTO pv VALUES (1, 10);
 INSERT INTO cv1 VALUES (2, 20);
 INSERT INTO cv2 VALUES (3, 30);
-CREATE TABLE pe (id int, src int, dest int, val int);
+CREATE TABLE pe (id int, src int, dest int, val int, flag boolean);
 CREATE TABLE ce1 () INHERITS (pe);
 CREATE TABLE ce2 () INHERITS (pe);
-INSERT INTO pe VALUES (1, 1, 2, 100);
-INSERT INTO ce1 VALUES (2, 2, 3, 200);
-INSERT INTO ce2 VALUES (3, 3, 1, 300);
+INSERT INTO pe VALUES (1, 1, 2, 100, false);
+INSERT INTO ce1 VALUES (2, 2, 3, 200, false);
+INSERT INTO ce2 VALUES (3, 3, 1, 300, true);
 CREATE PROPERTY GRAPH g3
     NODE TABLES (
         pv KEY (id)
@@ -501,6 +503,9 @@ CREATE PROPERTY GRAPH g3
             DESTINATION KEY(dest) REFERENCES pv(id)
     );
 SELECT * FROM GRAPH_TABLE (g3 MATCH (s IS pv)-[e IS pe]->(d IS pv) COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3;
+-- bare property reference in WHERE clause
+SELECT * FROM GRAPH_TABLE (g3 MATCH (s IS pv)-[e IS pe WHERE e.flag]->(d IS pv) COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3;
+SELECT * FROM GRAPH_TABLE (g3 MATCH (s IS pv)-[e IS pe]->(d IS pv) WHERE e.flag COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3;
 CREATE TEMPORARY PROPERTY GRAPH gtmp
     VERTEX TABLES (
         pv KEY (id)
-- 
2.34.1

