diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index 0065c8992b..0a83db409b 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -3294,7 +3294,8 @@ match_clause_to_ordering_op(IndexOptInfo *index, void check_index_predicates(PlannerInfo *root, RelOptInfo *rel) { - List *clauselist; + List *basequals; + List *allquals; bool have_partial; bool is_target_rel; Relids otherrels; @@ -3320,6 +3321,8 @@ check_index_predicates(PlannerInfo *root, RelOptInfo *rel) if (!have_partial) return; + basequals = rel->baserestrictinfo; + /* * Construct a list of clauses that we can assume true for the purpose of * proving the index(es) usable. Restriction clauses for the rel are @@ -3327,7 +3330,7 @@ check_index_predicates(PlannerInfo *root, RelOptInfo *rel) * rel. Also, we can consider any EC-derivable join clauses (which must * be "movable to" this rel, by definition). */ - clauselist = list_copy(rel->baserestrictinfo); + allquals = list_copy(rel->baserestrictinfo); /* Scan the rel's join clauses */ foreach(lc, rel->joininfo) @@ -3338,7 +3341,7 @@ check_index_predicates(PlannerInfo *root, RelOptInfo *rel) if (!join_clause_is_movable_to(rinfo, rel)) continue; - clauselist = lappend(clauselist, rinfo); + allquals = lappend(allquals, rinfo); } /* @@ -3357,8 +3360,8 @@ check_index_predicates(PlannerInfo *root, RelOptInfo *rel) otherrels = bms_del_members(otherrels, rel->nulling_relids); if (!bms_is_empty(otherrels)) - clauselist = - list_concat(clauselist, + allquals = + list_concat(allquals, generate_join_implied_equalities(root, bms_union(rel->relids, otherrels), @@ -3396,8 +3399,16 @@ check_index_predicates(PlannerInfo *root, RelOptInfo *rel) if (index->indpred == NIL) continue; /* ignore non-partial indexes here */ + if (!index->predOKBase) + index->predOKBase = predicate_implied_by(index->indpred, + basequals, false); + + /* if the index satisfies the basequals, don't check allquals */ + if (index->predOKBase) + index->predOK = true; + if (!index->predOK) /* don't repeat work if already proven OK */ - index->predOK = predicate_implied_by(index->indpred, clauselist, + index->predOK = predicate_implied_by(index->indpred, allquals, false); /* If rel is an update target, leave indrestrictinfo as set above */ @@ -3549,13 +3560,11 @@ 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, 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. + * a partial index that doesn't match the rel's baserestrictinfo, it's + * useless here. */ - if (!ind->unique || !ind->immediate || ind->indpred != NIL) + if (!ind->unique || !ind->immediate || + (ind->indpred != NIL && !ind->predOKBase)) continue; /* diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c index 5f3cce873a..6322a70070 100644 --- a/src/backend/optimizer/plan/analyzejoins.c +++ b/src/backend/optimizer/plan/analyzejoins.c @@ -801,9 +801,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 not a - * partial index. (Keep these conditions in sync with - * relation_has_unique_index_for!) + * suitable unique index. It must be immediately enforced, and if + * it's a partial index, it must match the rel's baserestrictinfo. + * (Keep these conditions in sync with relation_has_unique_index_for!) */ ListCell *lc; @@ -811,7 +811,8 @@ rel_supports_distinctness(PlannerInfo *root, RelOptInfo *rel) { IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc); - if (ind->unique && ind->immediate && ind->indpred == NIL) + if (ind->unique && ind->immediate && + (ind->indpred == NIL || ind->predOKBase)) return true; } } diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 39932d3c2d..e18c4890de 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -427,6 +427,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, info->indrestrictinfo = NIL; /* set later, in indxpath.c */ info->predOK = false; /* set later, in indxpath.c */ + info->predOKBase = false; /* set later, in indxpath.c */ info->unique = index->indisunique; info->immediate = index->indimmediate; info->hypothetical = false; diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index c17b53f7ad..84d61ef32a 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -1159,8 +1159,10 @@ struct IndexOptInfo */ List *indrestrictinfo; - /* true if index predicate matches query */ + /* true if index predicate matches query (including join quals) */ bool predOK; + /* true if index predicate matches rel's baserestrictinfo */ + bool predOKBase; /* true if a unique index */ bool unique; /* is uniqueness enforced immediately? */ diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 6917faec14..44e53d0e6b 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -7692,7 +7692,8 @@ left join j2 on j1.id1 = j2.id1 where j1.id2 = 1; (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 +-- ensure we don't use partial unique indexes that were marked predOK as +-- unique proofs for unique joins. explain (verbose, costs off) select * from j1 inner join j2 on j1.id2 = j2.id2; @@ -7707,6 +7708,24 @@ inner join j2 on j1.id2 = j2.id2; Output: j1.id1, j1.id2 (7 rows) +-- we can use the unique index if it's marked as predOKBase +explain (verbose, costs off) +select * from j1 +inner join j2 on j1.id2 = j2.id2 +where j1.id2 is not null; + QUERY PLAN +------------------------------------------ + Nested Loop + Output: j1.id1, j1.id2, j2.id1, j2.id2 + Inner Unique: true + Join Filter: (j2.id2 = j1.id2) + -> Seq Scan on public.j2 + Output: j2.id1, j2.id2 + -> Seq Scan on public.j1 + Output: j1.id1, j1.id2 + Filter: (j1.id2 IS NOT NULL) +(9 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 diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index 55080bec9a..104c0b7d15 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -2781,11 +2781,18 @@ 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 +-- ensure we don't use partial unique indexes that were marked predOK as +-- unique proofs for unique joins. explain (verbose, costs off) select * from j1 inner join j2 on j1.id2 = j2.id2; +-- we can use the unique index if it's marked as predOKBase +explain (verbose, costs off) +select * from j1 +inner join j2 on j1.id2 = j2.id2 +where j1.id2 is not null; + drop index j1_id2_idx; -- validate logic in merge joins which skips mark and restore.