Fixes for ChangeVarNodes_walker()
authorAlexander Korotkov <akorotkov@postgresql.org>
Tue, 29 Apr 2025 11:34:44 +0000 (14:34 +0300)
committerAlexander Korotkov <akorotkov@postgresql.org>
Tue, 29 Apr 2025 11:34:44 +0000 (14:34 +0300)
This commit fixes two bug in ChangeVarNodes_walker() function.

 * When considering RestrictInfo, walk down to its clauses based on the
   presense of relid to be deleted not just in clause_relids but also in
   required_relids.

 * Incrementally adjust num_base_rels based on the change of clause_relids
   instead of recalculating it using clause_relids, which could contain
   outer-join relids.

Reported-by: Richard Guo <guofenglinux@gmail.com>
Discussion: https://postgr.es/m/CAMbWs49PE3CvnV8vrQ0Dr%3DHqgZZmX0tdNbzVNJxqc8yg-8kDQQ%40mail.gmail.com
Author: Andrei Lepikhov <lepihov@gmail.com>
Reviewed-by: Alexander Korotkov <aekorotkov@gmail.com>
src/backend/rewrite/rewriteManip.c
src/test/regress/expected/join.out
src/test/regress/sql/join.sql

index e190f169fb3e08ce628524a3c8e27687dafe4aaf..3f8b8b6eed9a4e91dc58ce5f4a6f81a8a30873ec 100644 (file)
@@ -644,14 +644,34 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context)
        bool        clause_relids_is_multiple =
            (bms_membership(rinfo->clause_relids) == BMS_MULTIPLE);
 
-       if (bms_is_member(context->rt_index, rinfo->clause_relids))
+       /*
+        * Recurse down into clauses if the target relation is present in
+        * clause_relids or required_relids.  We must check required_relids
+        * because the relation not present in clause_relids might still be
+        * present somewhere in orclause.
+        */
+       if (bms_is_member(context->rt_index, rinfo->clause_relids) ||
+           bms_is_member(context->rt_index, rinfo->required_relids))
        {
+           Relids      new_clause_relids;
+
            expression_tree_walker((Node *) rinfo->clause, ChangeVarNodes_walker, (void *) context);
            expression_tree_walker((Node *) rinfo->orclause, ChangeVarNodes_walker, (void *) context);
 
-           rinfo->clause_relids =
-               adjust_relid_set(rinfo->clause_relids, context->rt_index, context->new_index);
-           rinfo->num_base_rels = bms_num_members(rinfo->clause_relids);
+           new_clause_relids = adjust_relid_set(rinfo->clause_relids,
+                                                context->rt_index,
+                                                context->new_index);
+
+           /*
+            * Incrementally adjust num_base_rels based on the change of
+            * clause_relids, which could contain both base relids and
+            * outer-join relids.  This operation is legal until we remove
+            * only baserels.
+            */
+           rinfo->num_base_rels -= bms_num_members(rinfo->clause_relids) -
+               bms_num_members(new_clause_relids);
+
+           rinfo->clause_relids = new_clause_relids;
            rinfo->left_relids =
                adjust_relid_set(rinfo->left_relids, context->rt_index, context->new_index);
            rinfo->right_relids =
index fa2c740551908e64fbf45f9e6febfbf20d068014..f35a0b18c37cbbcaad246647cb34e8ebd3bf875f 100644 (file)
@@ -7260,7 +7260,21 @@ WHERE t1.id = emp1.id RETURNING emp1.id, emp1.code, t1.code;
                Index Cond: (id = emp1.id)
 (5 rows)
 
-INSERT INTO emp1 VALUES (1, 1), (2, 1);
+-- Check that SJE correctly replaces relations in OR-clauses
+EXPLAIN (COSTS OFF)
+SELECT * FROM emp1 t1
+   INNER JOIN emp1 t2 ON t1.id = t2.id
+    LEFT JOIN emp1 t3 ON t1.code = 1 AND (t2.code = t3.code OR t2.code = 1);
+                                QUERY PLAN                                 
+---------------------------------------------------------------------------
+ Nested Loop Left Join
+   Join Filter: ((t2.code = 1) AND ((t2.code = t3.code) OR (t2.code = 1)))
+   ->  Seq Scan on emp1 t2
+   ->  Materialize
+         ->  Seq Scan on emp1 t3
+(5 rows)
+
+    INSERT INTO emp1 VALUES (1, 1), (2, 1);
 WITH t1 AS (SELECT * FROM emp1)
 UPDATE emp1 SET code = t1.code + 1 FROM t1
 WHERE t1.id = emp1.id RETURNING emp1.id, emp1.code, t1.code;
index d01d1da4ef829b139f9166c89e060a308c73d671..cc5128add4df0271213b0fa75a276eb0850526df 100644 (file)
@@ -2807,7 +2807,13 @@ WITH t1 AS (SELECT * FROM emp1)
 UPDATE emp1 SET code = t1.code + 1 FROM t1
 WHERE t1.id = emp1.id RETURNING emp1.id, emp1.code, t1.code;
 
-INSERT INTO emp1 VALUES (1, 1), (2, 1);
+-- Check that SJE correctly replaces relations in OR-clauses
+EXPLAIN (COSTS OFF)
+SELECT * FROM emp1 t1
+   INNER JOIN emp1 t2 ON t1.id = t2.id
+    LEFT JOIN emp1 t3 ON t1.code = 1 AND (t2.code = t3.code OR t2.code = 1);
+
+    INSERT INTO emp1 VALUES (1, 1), (2, 1);
 
 WITH t1 AS (SELECT * FROM emp1)
 UPDATE emp1 SET code = t1.code + 1 FROM t1