]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Don't use partial unique indexes for unique proofs in the planner
authorDavid Rowley <drowley@postgresql.org>
Mon, 19 Jun 2023 01:02:52 +0000 (13:02 +1200)
committerDavid Rowley <drowley@postgresql.org>
Mon, 19 Jun 2023 01:02:52 +0000 (13:02 +1200)
Here we adjust relation_has_unique_index_for() so that it no longer makes
use of partial unique indexes as uniqueness proofs.  It is incorrect to
use these as the predicates used by check_index_predicates() to set
predOK makes use of not only baserestrictinfo quals as proofs, but also
qual from join conditions.  For relation_has_unique_index_for()'s case, we
need to know the relation is unique for a given set of columns before any
joins are evaluated, so if predOK was only set to true due to some join
qual, then it's unsafe to use such indexes in
relation_has_unique_index_for().  The final plan may not even make use
of that index, which could result in reading tuples that are not as
unique as the planner previously expected them to be.

Bug: #17975
Reported-by: Tor Erik Linnerud
Backpatch-through: 11, all supported versions
Discussion: https://postgr.es/m/17975-98a90c156f25c952%40postgresql.org

src/backend/optimizer/path/indxpath.c
src/backend/optimizer/plan/analyzejoins.c
src/test/regress/expected/join.out
src/test/regress/sql/join.sql

index 44dff2a8025b9ed195530d5d4c26c4e18f0724e6..aa1009bf1fcd43b28d89c1991756b568338b06b0 100644 (file)
@@ -3593,10 +3593,13 @@ relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel,
 
                /*
                 * If the index is not unique, or not immediately enforced, or if it's
-                * a partial index that doesn't match the query, it's useless here.
+                * a partial index, it's useless here.  We're unable to make use of
+                * predOK partial unique indexes due to the fact that
+                * check_index_predicates() also makes use of join predicates to
+                * determine if the partial index is usable. Here we need proofs that
+                * hold true before any joins are evaluated.
                 */
-               if (!ind->unique || !ind->immediate ||
-                       (ind->indpred != NIL && !ind->predOK))
+               if (!ind->unique || !ind->immediate || ind->indpred != NIL)
                        continue;
 
                /*
index bb377bd240fe4bc60ecf1e93909a5d6d9eff90bc..e4bc2ed27212f7687327ba8267725548442d41fb 100644 (file)
@@ -602,9 +602,9 @@ rel_supports_distinctness(PlannerInfo *root, RelOptInfo *rel)
                /*
                 * For a plain relation, we only know how to prove uniqueness by
                 * reference to unique indexes.  Make sure there's at least one
-                * suitable unique index.  It must be immediately enforced, and if
-                * it's a partial index, it must match the query.  (Keep these
-                * conditions in sync with relation_has_unique_index_for!)
+                * suitable unique index.  It must be immediately enforced, and not a
+                * partial index. (Keep these conditions in sync with
+                * relation_has_unique_index_for!)
                 */
                ListCell   *lc;
 
@@ -612,8 +612,7 @@ rel_supports_distinctness(PlannerInfo *root, RelOptInfo *rel)
                {
                        IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
 
-                       if (ind->unique && ind->immediate &&
-                               (ind->indpred == NIL || ind->predOK))
+                       if (ind->unique && ind->immediate && ind->indpred == NIL)
                                return true;
                }
        }
index 580700fb9fbf311c45a407a156f3a67c7967a86b..c10c77f43f40792eade9c0b9dec9be5f6e91816e 100644 (file)
@@ -6176,6 +6176,23 @@ left join j2 on j1.id1 = j2.id1 where j1.id2 = 1;
          Output: j2.id1, j2.id2
 (8 rows)
 
+create unique index j1_id2_idx on j1(id2) where id2 is not null;
+-- ensure we don't use a partial unique index as unique proofs
+explain (verbose, costs off)
+select * from j1
+inner join j2 on j1.id2 = j2.id2;
+                QUERY PLAN                
+------------------------------------------
+ Nested Loop
+   Output: j1.id1, j1.id2, j2.id1, j2.id2
+   Join Filter: (j1.id2 = j2.id2)
+   ->  Seq Scan on public.j2
+         Output: j2.id1, j2.id2
+   ->  Seq Scan on public.j1
+         Output: j1.id1, j1.id2
+(7 rows)
+
+drop index j1_id2_idx;
 -- validate logic in merge joins which skips mark and restore.
 -- it should only do this if all quals which were used to detect the unique
 -- are present as join quals, and not plain quals.
index 4dd351b6a4579d6aca32004f4392dbdd4f5690a8..1ce557c657a2511a4c0cf93181e85eccadb42fbb 100644 (file)
@@ -2125,6 +2125,15 @@ explain (verbose, costs off)
 select * from j1
 left join j2 on j1.id1 = j2.id1 where j1.id2 = 1;
 
+create unique index j1_id2_idx on j1(id2) where id2 is not null;
+
+-- ensure we don't use a partial unique index as unique proofs
+explain (verbose, costs off)
+select * from j1
+inner join j2 on j1.id2 = j2.id2;
+
+drop index j1_id2_idx;
+
 -- validate logic in merge joins which skips mark and restore.
 -- it should only do this if all quals which were used to detect the unique
 -- are present as join quals, and not plain quals.