diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 32d03f7..7010076 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2129,6 +2129,7 @@ _outIndexOptInfo(StringInfo str, const IndexOptInfo *node) WRITE_OID_FIELD(relam); /* indexprs is redundant since we print indextlist */ WRITE_NODE_FIELD(indpred); + WRITE_NODE_FIELD(baserestrictinfo); WRITE_NODE_FIELD(indextlist); WRITE_BOOL_FIELD(predOK); WRITE_BOOL_FIELD(unique); diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index e1a5d33..7f94475 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -334,6 +334,24 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, switch (rel->rtekind) { case RTE_RELATION: + /* + * For index scans, some clauses in baserestrictinfo can be + * culled according to index definitions. So we are to + * retrieve baserestrictinfo for indexscans from + * IndexOptInfo. The baserestrictinfo has not been fixed until + * just before here so we initialize it here. + */ + if (rel->indexlist && rel->baserestrictinfo) + { + ListCell *lc; + + foreach (lc, rel->indexlist) + { + IndexOptInfo *index = (IndexOptInfo *) lfirst(lc); + index->baserestrictinfo = rel->baserestrictinfo; + } + } + if (rte->relkind == RELKIND_FOREIGN_TABLE) { /* Foreign table */ diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index b86fc5e..6a6e0c9 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -434,22 +434,26 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count) /* * Mark the path with the correct row estimate, and identify which quals * will need to be enforced as qpquals. + * + * Usually, qpquals come from the rel's restriction clauses and + * ppi_clauses. For partial indexes, we may ignore clauses implied by + * the index predicate. This reduced subset of restriction clauses was + * already computed in check_partial_indexes() and is available in + * IndexOptInfo, so we simply retrieve it from there. */ if (path->path.param_info) { path->path.rows = path->path.param_info->ppi_rows; - /* qpquals come from the rel's restriction clauses and ppi_clauses */ qpquals = list_concat( - extract_nonindex_conditions(baserel->baserestrictinfo, - path->indexquals), + extract_nonindex_conditions(path->indexinfo->baserestrictinfo, + path->indexquals), extract_nonindex_conditions(path->path.param_info->ppi_clauses, path->indexquals)); } else { path->path.rows = baserel->rows; - /* qpquals come from just the rel's restriction clauses */ - qpquals = extract_nonindex_conditions(baserel->baserestrictinfo, + qpquals = extract_nonindex_conditions(path->indexinfo->baserestrictinfo, path->indexquals); } diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index b48f5f2..2748ecf 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -30,6 +30,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/predtest.h" +#include "optimizer/prep.h" #include "optimizer/restrictinfo.h" #include "optimizer/var.h" #include "utils/builtins.h" @@ -1801,13 +1802,13 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index) * Check that all needed attributes of the relation are available from the * index. * - * XXX this is overly conservative for partial indexes, since we will - * consider attributes involved in the index predicate as required even - * though the predicate won't need to be checked at runtime. (The same is - * true for attributes used only in index quals, if we are certain that - * the index is not lossy.) However, it would be quite expensive to - * determine that accurately at this point, so for now we take the easy - * way out. + * For partial indexes we won't consider attributes involved in clauses + * implied by the index predicate, as those won't be needed at runtime. + * + * XXX Attributes used only in index quals are not needed at runtime + * either, if we are certain that the index is not lossy; however, it + * would be quite expensive to determine that accurately at this point, so + * for now we take the easy way out. */ /* @@ -1817,8 +1818,8 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index) */ pull_varattnos((Node *) rel->reltarget->exprs, rel->relid, &attrs_used); - /* Add all the attributes used by restriction clauses. */ - foreach(lc, rel->baserestrictinfo) + /* Add all the attributes used by index restriction clauses. */ + foreach(lc, index->baserestrictinfo) { RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); @@ -2023,7 +2024,7 @@ static void match_restriction_clauses_to_index(RelOptInfo *rel, IndexOptInfo *index, IndexClauseSet *clauseset) { - match_clauses_to_index(index, rel->baserestrictinfo, clauseset); + match_clauses_to_index(index, index->baserestrictinfo, clauseset); } /* @@ -2743,10 +2744,14 @@ check_partial_indexes(PlannerInfo *root, RelOptInfo *rel) otherrels, rel)); - /* Now try to prove each index predicate true */ + /* + * Now try to prove each index predicate true. We'll also tweak the index + * restrictions to exclude clauses implied by the predicate. + */ foreach(lc, rel->indexlist) { IndexOptInfo *index = (IndexOptInfo *) lfirst(lc); + ListCell *lcr; if (index->indpred == NIL) continue; /* ignore non-partial indexes */ @@ -2755,6 +2760,35 @@ check_partial_indexes(PlannerInfo *root, RelOptInfo *rel) continue; /* don't repeat work if already proven OK */ index->predOK = predicate_implied_by(index->indpred, clauselist); + + /* + * Update restrictinfo for this index by excluding clauses that are + * implied by the index predicates. We first quickly check if we can + * safely remove clauses. + */ + + if (rel->relid == root->parse->resultRelation || + get_plan_rowmark(root->rowMarks, rel->relid) != NULL) + continue; + + /* + * We could avoid creating a new baserestrictinfo for the index if it + * will match the one for the relation, but the few cycles saved from + * such an optimization are not worth the code complexity. Just create + * it unconditionally even though the lists may match. + */ + index->baserestrictinfo = NIL; + foreach (lcr, rel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lcr); + + /* add to index clauses if initial or not implied */ + if (contain_mutable_functions((Node *) rinfo->clause) || + !predicate_implied_by(list_make1(rinfo->clause), + index->indpred)) + index->baserestrictinfo = + lappend(index->baserestrictinfo, rinfo); + } } } diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index d159a17..4922e05 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -70,6 +70,7 @@ static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path, int flags); +static List *get_baserestrictinfo(PlannerInfo *root, Path *best_path); static Plan *create_scan_plan(PlannerInfo *root, Path *best_path, int flags); static List *build_path_tlist(PlannerInfo *root, Path *path); @@ -477,6 +478,27 @@ create_plan_recurse(PlannerInfo *root, Path *best_path, int flags) } /* + * get_baserestrictinfo + * Return the list of restriction clauses for the path. + * + * For index paths we return the list produced by by check_partial_indexes, + * i.e. without clauses implied by the index predicate (if present). + */ +static List * +get_baserestrictinfo(PlannerInfo *root, Path *best_path) +{ + switch (best_path->pathtype) + { + case T_IndexScan: + case T_IndexOnlyScan: + Assert(IsA(best_path, IndexPath)); + return ((IndexPath *) best_path)->indexinfo->baserestrictinfo; + default: + return best_path->parent->baserestrictinfo; + } +} + +/* * create_scan_plan * Create a scan plan for the parent relation of 'best_path'. */ @@ -492,9 +514,11 @@ create_scan_plan(PlannerInfo *root, Path *best_path, int flags) /* * Extract the relevant restriction clauses from the parent relation. The * executor must apply all these restrictions during the scan, except for - * pseudoconstants which we'll take care of below. + * pseudoconstants which we'll take care of below. Some types of paths + * override the baserestrictinfo of the parent relation (e.g., a partial + * index excludes redundant index conditions). */ - scan_clauses = rel->baserestrictinfo; + scan_clauses = get_baserestrictinfo(root, best_path); /* * If this is a parameterized scan, we also need to enforce all the join @@ -2400,21 +2424,14 @@ create_indexscan_plan(PlannerInfo *root, continue; /* simple duplicate */ if (is_redundant_derived_clause(rinfo, indexquals)) continue; /* derived from same EquivalenceClass */ - if (!contain_mutable_functions((Node *) rinfo->clause)) - { - List *clausel = list_make1(rinfo->clause); + /* + * scan_clauses don't contain clauses implied by index predicate. See + * check_partial_indexes() for detail. + */ + if (!contain_mutable_functions((Node *) rinfo->clause) && + predicate_implied_by(list_make1(rinfo->clause), indexquals)) + continue; /* provably implied by indexquals */ - if (predicate_implied_by(clausel, indexquals)) - continue; /* provably implied by indexquals */ - if (best_path->indexinfo->indpred) - { - if (baserelid != root->parse->resultRelation && - get_plan_rowmark(root->rowMarks, baserelid) == NULL) - if (predicate_implied_by(clausel, - best_path->indexinfo->indpred)) - continue; /* implied by index predicate */ - } - } qpqual = lappend(qpqual, rinfo); } diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 546067b..b662859 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -331,6 +331,12 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, */ info->indexprs = RelationGetIndexExpressions(indexRelation); info->indpred = RelationGetIndexPredicate(indexRelation); + + /* + * Index baserestrictinfo cannot be calculated yet; leave it NULL + * for now. + */ + info->baserestrictinfo = NULL; if (info->indexprs && varno != 1) ChangeVarNodes((Node *) info->indexprs, 1, varno, 0); if (info->indpred && varno != 1) diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index ee7007a..cc0a08f 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -592,9 +592,11 @@ typedef struct IndexOptInfo List *indexprs; /* expressions for non-simple index columns */ List *indpred; /* predicate if a partial index, else NIL */ - + List *baserestrictinfo; /* a list of RestrictInfo nodes from the + * query's WHERE or JOIN conditions, maybe + * excluding those implied by the index + * predicate */ List *indextlist; /* targetlist representing index columns */ - bool predOK; /* true if predicate matches query */ bool unique; /* true if a unique index */ bool immediate; /* is uniqueness enforced immediately? */ diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out index 601bdb4..3ff6691 100644 --- a/src/test/regress/expected/aggregates.out +++ b/src/test/regress/expected/aggregates.out @@ -780,7 +780,6 @@ explain (costs off) -> Index Only Scan Backward using minmaxtest2i on minmaxtest2 Index Cond: (f1 IS NOT NULL) -> Index Only Scan using minmaxtest3i on minmaxtest3 - Index Cond: (f1 IS NOT NULL) InitPlan 2 (returns $1) -> Limit -> Merge Append @@ -792,8 +791,7 @@ explain (costs off) -> Index Only Scan using minmaxtest2i on minmaxtest2 minmaxtest2_1 Index Cond: (f1 IS NOT NULL) -> Index Only Scan Backward using minmaxtest3i on minmaxtest3 minmaxtest3_1 - Index Cond: (f1 IS NOT NULL) -(25 rows) +(23 rows) select min(f1), max(f1) from minmaxtest; min | max @@ -818,7 +816,6 @@ explain (costs off) -> Index Only Scan Backward using minmaxtest2i on minmaxtest2 Index Cond: (f1 IS NOT NULL) -> Index Only Scan using minmaxtest3i on minmaxtest3 - Index Cond: (f1 IS NOT NULL) InitPlan 2 (returns $1) -> Limit -> Merge Append @@ -830,11 +827,10 @@ explain (costs off) -> Index Only Scan using minmaxtest2i on minmaxtest2 minmaxtest2_1 Index Cond: (f1 IS NOT NULL) -> Index Only Scan Backward using minmaxtest3i on minmaxtest3 minmaxtest3_1 - Index Cond: (f1 IS NOT NULL) -> Sort Sort Key: ($0), ($1) -> Result -(28 rows) +(26 rows) select distinct min(f1), max(f1) from minmaxtest; min | max