Fix inconsistent RestrictInfo serial numbers
authorRichard Guo <[email protected]>
Fri, 8 Nov 2024 02:21:11 +0000 (11:21 +0900)
committerRichard Guo <[email protected]>
Fri, 8 Nov 2024 02:21:11 +0000 (11:21 +0900)
When we generate multiple clones of the same qual condition to cope
with outer join identity 3, we need to ensure that all the clones get
the same serial number.  To achieve this, we reset the
root->last_rinfo_serial counter each time we produce RestrictInfo(s)
from the qual list (see deconstruct_distribute_oj_quals).  This
approach works only if we ensure that we are not changing the qual
list in any way that'd affect the number of RestrictInfos built from
it.

However, with b262ad440, an IS NULL qual on a NOT NULL column might
result in an additional constant-FALSE RestrictInfo.  And different
versions of the same qual clause can lead to different conclusions
about whether it can be reduced to constant-FALSE.  This would affect
the number of RestrictInfos built from the qual list for different
versions, causing inconsistent RestrictInfo serial numbers across
multiple clones of the same qual.  This inconsistency can confuse
users of these serial numbers, such as rebuild_joinclause_attr_needed,
and lead to planner errors such as "ERROR:  variable not found in
subplan target lists".

To fix, reset the root->last_rinfo_serial counter after generating the
additional constant-FALSE RestrictInfo.

Back- to v17 where the issue crept in.  In v17, I failed to make
a test case that would expose this bug, so no test case for v17.

Author: Richard Guo
Discussion: https://postgr.es/m/CAMbWs4-B6kafn+LmPuh-TYFwFyEm-vVj3Qqv7Yo-69CEv14rRg@mail.gmail.com

src/backend/optimizer/plan/initsplan.c
src/backend/optimizer/util/joininfo.c
src/test/regress/expected/predicate.out
src/test/regress/sql/predicate.sql

index c5bc0f51e996e5c4acaac2c9ae2758ee38ec905d..903c397d40932580cd3d858c772d3d10786f2ea7 100644 (file)
@@ -2767,11 +2767,18 @@ add_base_clause_to_rel(PlannerInfo *root, Index relid,
 
        /*
         * Substitute the origin qual with constant-FALSE if it is provably
-        * always false.  Note that we keep the same rinfo_serial.
+        * always false.
+        *
+        * Note that we need to keep the same rinfo_serial, since it is in
+        * practice the same condition.  We also need to reset the
+        * last_rinfo_serial counter, which is essential to ensure that the
+        * RestrictInfos for the "same" qual condition get identical serial
+        * numbers (see deconstruct_distribute_oj_quals).
         */
        if (restriction_is_always_false(root, restrictinfo))
        {
            int         save_rinfo_serial = restrictinfo->rinfo_serial;
+           int         save_last_rinfo_serial = root->last_rinfo_serial;
 
            restrictinfo = make_restrictinfo(root,
                                             (Expr *) makeBoolConst(false, false),
@@ -2784,6 +2791,7 @@ add_base_clause_to_rel(PlannerInfo *root, Index relid,
                                             restrictinfo->incompatible_relids,
                                             restrictinfo->outer_relids);
            restrictinfo->rinfo_serial = save_rinfo_serial;
+           root->last_rinfo_serial = save_last_rinfo_serial;
        }
    }
 
index 5fb0c17630a196ac4cad7deda9dde253f7f670b8..65993bd6599d3fe3c4e25f13671667e8bf8f7d07 100644 (file)
@@ -106,12 +106,19 @@ add_join_clause_to_rels(PlannerInfo *root,
        return;
 
    /*
-    * Substitute constant-FALSE for the origin qual if it is always false.
-    * Note that we keep the same rinfo_serial.
+    * Substitute the origin qual with constant-FALSE if it is provably always
+    * false.
+    *
+    * Note that we need to keep the same rinfo_serial, since it is in
+    * practice the same condition.  We also need to reset the
+    * last_rinfo_serial counter, which is essential to ensure that the
+    * RestrictInfos for the "same" qual condition get identical serial
+    * numbers (see deconstruct_distribute_oj_quals).
     */
    if (restriction_is_always_false(root, restrictinfo))
    {
        int         save_rinfo_serial = restrictinfo->rinfo_serial;
+       int         save_last_rinfo_serial = root->last_rinfo_serial;
 
        restrictinfo = make_restrictinfo(root,
                                         (Expr *) makeBoolConst(false, false),
@@ -124,6 +131,7 @@ add_join_clause_to_rels(PlannerInfo *root,
                                         restrictinfo->incompatible_relids,
                                         restrictinfo->outer_relids);
        restrictinfo->rinfo_serial = save_rinfo_serial;
+       root->last_rinfo_serial = save_last_rinfo_serial;
    }
 
    cur_relid = -1;
index 6f1cc0d54cd3f12bae821f99714173b291e09e88..965a3a76161d8c7d150f35c9d7d8334e459d3463 100644 (file)
@@ -290,3 +290,31 @@ SELECT * FROM pred_parent WHERE a IS NULL;
 (2 rows)
 
 DROP TABLE pred_parent, pred_child;
+-- Validate the additional constant-FALSE qual does not cause inconsistent
+-- RestrictInfo serial numbers
+CREATE TABLE pred_tab (a int PRIMARY KEY, b int);
+INSERT INTO pred_tab SELECT i, i FROM generate_series(1, 10)i;
+ANALYZE pred_tab;
+EXPLAIN (COSTS OFF)
+SELECT 1 FROM pred_tab t1
+    LEFT JOIN
+        (pred_tab t2 LEFT JOIN pred_tab t3 ON t2.a = t3.a) ON TRUE
+    LEFT JOIN pred_tab t4 ON t1.a IS NULL AND t1.b = 1
+    RIGHT JOIN pred_tab t5 ON t1.b = t5.b;
+                    QUERY PLAN                     
+---------------------------------------------------
+ Hash Right Join
+   Hash Cond: (t1.b = t5.b)
+   ->  Nested Loop Left Join
+         ->  Nested Loop Left Join
+               Join Filter: (false AND (t1.b = 1))
+               ->  Seq Scan on pred_tab t1
+               ->  Result
+                     One-Time Filter: false
+         ->  Materialize
+               ->  Seq Scan on pred_tab t2
+   ->  Hash
+         ->  Seq Scan on pred_tab t5
+(12 rows)
+
+DROP TABLE pred_tab;
index 63f6a7786f369a65d338f6fdb4a889dbf433a72f..661013ff7e4f92a8e71b79e1e79265a2c9c58650 100644 (file)
@@ -147,3 +147,18 @@ EXPLAIN (COSTS OFF)
 SELECT * FROM pred_parent WHERE a IS NULL;
 
 DROP TABLE pred_parent, pred_child;
+
+-- Validate the additional constant-FALSE qual does not cause inconsistent
+-- RestrictInfo serial numbers
+CREATE TABLE pred_tab (a int PRIMARY KEY, b int);
+INSERT INTO pred_tab SELECT i, i FROM generate_series(1, 10)i;
+ANALYZE pred_tab;
+
+EXPLAIN (COSTS OFF)
+SELECT 1 FROM pred_tab t1
+    LEFT JOIN
+        (pred_tab t2 LEFT JOIN pred_tab t3 ON t2.a = t3.a) ON TRUE
+    LEFT JOIN pred_tab t4 ON t1.a IS NULL AND t1.b = 1
+    RIGHT JOIN pred_tab t5 ON t1.b = t5.b;
+
+DROP TABLE pred_tab;