Hi Ayush,

On Tue, May 19, 2026 at 8:46 PM Ayush Tiwari
<[email protected]> wrote:
>
> Hi,
>
> On Tue, 12 May 2026 at 23:12, SATYANARAYANA NARLAPURAM 
> <[email protected]> wrote:
>>
>> HI,
>>
>> On Tue, May 12, 2026 at 4:15 AM Ashutosh Bapat 
>> <[email protected]> wrote:
>>>
>>> On Thu, May 7, 2026 at 9:43 AM SATYANARAYANA NARLAPURAM
>>> <[email protected]> wrote:
>>> >
>>> > Hi hackers,
>>> >
>>> > A LATERAL GRAPH_TABLE whose pattern matches more than one path query
>>> > fails with the assert
>>> >
>>> > TRAP: failed Assert("!bms_is_member(rti, lateral_relids)"), File: 
>>> > "initsplan.c", Line: 1428, PID: 3586144
>>> > postgres: postgres postgres [local] 
>>> > SELECT(ExceptionalCondition+0x70)[0x63488e3cc070]
>>> > postgres: postgres postgres [local] 
>>> > SELECT(create_lateral_join_info+0x468)[0x63488e14ac28]
>>> > postgres: postgres postgres [local] 
>>> > SELECT(query_planner+0x13a)[0x63488e14dfca]
>>> >
>>> > Repro:
>>> > SELECT v1.vname, gt.aname
>>> >   FROM v1, LATERAL (SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1 | vl2 
>>> > WHERE a.vprop1 = v1.vprop1) COLUMNS (a.vname AS aname)) g) gt
>>> >   ORDER BY 1, 2;
>>> >
>>> > Single-label GRAPH_TABLE with the same outer reference works fine.
>>> > rewriteGraphTable() turns the GRAPH_TABLE RTE into a subquery RTE and
>>> > bumps outer Vars by one sublevel.  When the pattern produces multiple
>>> > path queries, generate_setop_from_pathqueries() wraps each one in
>>> > another subquery RTE for the UNION ALL but does not bump again, so the
>>> > lateral reference collapses onto GRAPH_TABLE's own RTE.
>>>
>>> + /* Wrapping lquery in a subquery RTE adds one query level, so bump
>>> + * outer-level Vars accordingly. */
>>> + IncrementVarSublevelsUp((Node *) lquery, 1, 1);
>>> +
>>>
>>> Multiline comments start with /* and end with */ on separate lines 
>>> respectively.
>>>
>>> From the comment it feels like we will add as many varlevels as the
>>> depth of setop tree, since we will add a subquery RTE for each setop
>>> level. In reality all the legs of the setop tree will be at the same
>>> varlevel. A better comment would be "Vars in each path query are one
>>> level away from the setop query combining them.".
>>>
>>> The connection between varlevelsup, addition of subquery RTE and the
>>> assertion failure isn't clear. Assertion failure indicates that the
>>> relation being examined has lateral reference to itself which boils
>>> down to range table entry index but the varlevelsup is about depth of
>>> a given query. The connection between these two seemingly different
>>> things needs to be explained in the commit message. Though the code
>>> changes fix the problem, there may be a better place to increment
>>> varlevels up. That will be clear once the connection is clear.
>>>
>>> >
>>> > Tried fixing this by bumping each lquery's sublevels by 1 before the 
>>> > addRangeTableEntry
>>> > ForSubquery() wrap.  Single-pathquery queries skip this path entirely.
>>> >
>>> > Attached a patch with the tests.
>>> >
>>>
>>> Do we need both the tests? The second one has no label expression
>>> which means it will try all the labels. So the test will reproduce the
>>> bug only when there are multiple labels. The first test explicitly
>>> adds the multi-label pattern. I think we should just leave the first
>>> one and remove the second one. Also this test should be placed in the
>>> section which tests other lateral references. myshop property graph
>>> has two sets of elements that share a label - order and wishlist,
>>> order_items and wishlist_items.
>>
>>
>> Thanks for reviewing, will address the comments shortly.
>
> Attached is v2 of Satya's patch, addressing the review comments.
>

Thanks for the patch addressing comments.

> Changes in v2:
>
> - Reworded the inline comment in PostgreSQL multi-line style. It now explains
>   that each path query is inserted as a subquery RTE of the setop query, so
>   Vars that already refer outside the path query must be adjusted for the
>   additional query level. Local Vars belonging to the path query are
>   unchanged (the call uses min_sublevels_up = 1).
>
> - Expanded the commit message to spell out the connection between the missing
>   varlevelsup adjustment and the lateral self-reference assertion.
>
> - Reduced the regression coverage to a single explicit multi-label case, and
>   moved it next to the existing lateral-reference tests rather than at the
>   end of the file.
>
> The fix is kept at the same place (generate_setop_from_pathqueries), since
> that is where the extra subquery RTE wrapping happens. Doing it any earlier
> would over-adjust the single-pathquery path.

Yes, fix is at the right place. We can not set the varlevelsup to the
correct value in replace_properties_references itself because when
that function is called, we do not know whether UNION is required or
not. The next place to adjust varlevelsup is when creating a UNION
statement.

Depending upon the varno of outer reference we will see different
symptoms. a crash or an error "plan should not reference subplan's
variable" or even data type mismatch. Also the levelsup is maintained
in nodes other than Var. I have modified the commit message to focus
on the problem instead of the symptom. Also some small edits to the
comments.

Attached patch has the number  0010 (vs 0001) since I am creating
patches for all the SQL/PGQ fixes from the same branch. It can be
applied independent of other patches.
-- 
Best Wishes,
Ashutosh Bapat
From e182cc2bea150680f5664cac3cec70c5e9842418 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email protected]>
Date: Tue, 2 Jun 2026 15:51:41 +0530
Subject: [PATCH v20260602 10/10] Fix LATERAL references in GRAPH_TABLE with
 multi-label pattern

When rewriting a GRAPH_TABLE into a subquery
replace_property_refs_mutator() bumps levelsup of lateral references by
one so that reference outside the subquery. This works for path patterns
which result in only one path query.  Patterns that produce multiple
path queries are rewritten as UNION of path queries. Since every path
query becomes a subquery of UNION statement, the levelsup of lateral
references in those path queries need additional bump. This adjustment
was missing resulting in the lateral references being interpreted as
references in the UNION query itself. This caused different symptoms
like a crash in create_lateral_join_info() or error "plan should not
reference subplan's variable". The symptoms varied depending upon the
number of RangeTblEntries in the UNION statement.

This commit adds the missing adjustment.

Author: Ayush Tiwari <[email protected]>
Author: Satya Narlapuram <[email protected]>
Reviewed-by: Ashutosh Bapat <[email protected]>
Discussion: https://www.postgresql.org/message-id/cahg+qdfnlzsgjaq_cikspp4jh3mkoiwoawecczxa9uyr45y...@mail.gmail.com
---
 src/backend/rewrite/rewriteGraphTable.c   |  7 +++++++
 src/test/regress/expected/graph_table.out | 14 ++++++++++++++
 src/test/regress/sql/graph_table.sql      |  6 ++++++
 3 files changed, 27 insertions(+)

diff --git a/src/backend/rewrite/rewriteGraphTable.c b/src/backend/rewrite/rewriteGraphTable.c
index 2292ca6dcee..7df7f9fd70a 100644
--- a/src/backend/rewrite/rewriteGraphTable.c
+++ b/src/backend/rewrite/rewriteGraphTable.c
@@ -720,6 +720,13 @@ generate_setop_from_pathqueries(List *pathqueries, List **rtable, List **targetl
 
 	lquery = linitial_node(Query, pathqueries);
 
+	/*
+	 * Each path query will become a subquery of the UNION statement. So any
+	 * Vars that already refer outside the path query must be adjusted for
+	 * additional query level.
+	 */
+	IncrementVarSublevelsUp((Node *) lquery, 1, 1);
+
 	pni = addRangeTableEntryForSubquery(make_parsestate(NULL), lquery, NULL,
 										false, false);
 	*rtable = lappend(*rtable, pni->p_rte);
diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out
index 475ac20d376..c229212cb51 100644
--- a/src/test/regress/expected/graph_table.out
+++ b/src/test/regress/expected/graph_table.out
@@ -263,6 +263,20 @@ SELECT x1.a, g.* FROM x1, GRAPH_TABLE (myshop MATCH (x1 IS customers WHERE x1.ad
  2 | customer1     |   1 |        1
 (2 rows)
 
+-- lateral reference with multi-label pattern, which is rewritten as UNION of
+-- path queries
+SELECT x1.a, g.* FROM x1,
+    GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.customer_id = x1.a)-[IS customer_orders | customer_wishlists]->(l IS lists)-[IS list_items]->(p IS products)
+        COLUMNS (x1.a AS outer_id, c.name AS customer_name, p.name AS product_name, l.list_type)) g
+    ORDER BY 1, 3, 4, 5;
+ a | outer_id | customer_name | product_name | list_type 
+---+----------+---------------+--------------+-----------
+ 1 |        1 | customer1     | product1     | order
+ 1 |        1 | customer1     | product2     | order
+ 2 |        2 | customer2     | product1     | order
+ 2 |        2 | customer2     | product1     | wishlist
+(4 rows)
+
 -- non-local property references are not allowed, even if a lateral column
 -- reference is available
 SELECT x1.a, g.* FROM x1, GRAPH_TABLE (myshop MATCH (x1 IS customers)-[IS customer_orders]->(o IS orders WHERE o.order_id = x1.a) COLUMNS (x1.name AS customer_name, x1.customer_id AS cid, o.order_id)) g; -- error
diff --git a/src/test/regress/sql/graph_table.sql b/src/test/regress/sql/graph_table.sql
index 5412f10ac3c..e30c908dccc 100644
--- a/src/test/regress/sql/graph_table.sql
+++ b/src/test/regress/sql/graph_table.sql
@@ -160,6 +160,12 @@ CREATE TABLE x1 (a int, address text);
 INSERT INTO x1 VALUES (1, 'one'), (2, 'two');
 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;
+-- lateral reference with multi-label pattern, which is rewritten as UNION of
+-- path queries
+SELECT x1.a, g.* FROM x1,
+    GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.customer_id = x1.a)-[IS customer_orders | customer_wishlists]->(l IS lists)-[IS list_items]->(p IS products)
+        COLUMNS (x1.a AS outer_id, c.name AS customer_name, p.name AS product_name, l.list_type)) g
+    ORDER BY 1, 3, 4, 5;
 -- non-local property references are not allowed, even if a lateral column
 -- reference is available
 SELECT x1.a, g.* FROM x1, GRAPH_TABLE (myshop MATCH (x1 IS customers)-[IS customer_orders]->(o IS orders WHERE o.order_id = x1.a) COLUMNS (x1.name AS customer_name, x1.customer_id AS cid, o.order_id)) g; -- error
-- 
2.34.1

Reply via email to