Fix replace_property_refs() ignoring the root of expression tree

replace_property_refs() called expression_tree_mutator() with the root
of the expression tree as the input node.  But
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>
Discussion: https://www.postgresql.org/message-id/flat/20260630173053.51.noahmisch%40microsoft.com
This commit is contained in:
Peter Eisentraut 2026-07-08 09:40:46 +02:00
parent 4b587f666a
commit 16a4b3ef8e
3 changed files with 41 additions and 16 deletions

View file

@ -1159,7 +1159,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);
}
/*

View file

@ -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,
@ -864,12 +871,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)
@ -887,6 +894,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)
-- temporary property graph
CREATE TEMPORARY PROPERTY GRAPH gtmp
VERTEX TABLES (

View file

@ -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,
@ -483,12 +485,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)
@ -499,6 +501,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;
-- temporary property graph
CREATE TEMPORARY PROPERTY GRAPH gtmp
VERTEX TABLES (