diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index 77df5a24ea24d67abc4b0b087960aacf76cb012d..74bc7ac7c634503c383ae127c31b7f4e9c447964 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -2253,21 +2253,74 @@ find_clauses_for_join(PlannerInfo *root, RelOptInfo *rel, * a set of equality conditions, because the conditions constrain all * columns of some unique index. * - * The conditions are provided as a list of RestrictInfo nodes, where the - * caller has already determined that each condition is a mergejoinable - * equality with an expression in this relation on one side, and an - * expression not involving this relation on the other. The transient - * outer_is_left flag is used to identify which side we should look at: - * left side if outer_is_left is false, right side if it is true. + * The conditions can be represented in either or both of two ways: + * 1. A list of RestrictInfo nodes, where the caller has already determined + * that each condition is a mergejoinable equality with an expression in + * this relation on one side, and an expression not involving this relation + * on the other. The transient outer_is_left flag is used to identify which + * side we should look at: left side if outer_is_left is false, right side + * if it is true. + * 2. A list of expressions in this relation, and a corresponding list of + * equality operators. The caller must have already checked that the operators + * represent equality. (Note: the operators could be cross-type; the + * expressions should correspond to their RHS inputs.) + * + * The caller need only supply equality conditions arising from joins; + * this routine automatically adds in any usable baserestrictinfo clauses. + * (Note that the passed-in restrictlist will be destructively modified!) */ bool relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel, - List *restrictlist) + List *restrictlist, + List *exprlist, List *oprlist) { ListCell *ic; + Assert(list_length(exprlist) == list_length(oprlist)); + + /* Short-circuit if no indexes... */ + if (rel->indexlist == NIL) + return false; + + /* + * Examine the rel's restriction clauses for usable var = const clauses + * that we can add to the restrictlist. + */ + foreach(ic, rel->baserestrictinfo) + { + RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(ic); + + /* + * Note: can_join won't be set for a restriction clause, but + * mergeopfamilies will be if it has a mergejoinable operator and + * doesn't contain volatile functions. + */ + if (restrictinfo->mergeopfamilies == NIL) + continue; /* not mergejoinable */ + + /* + * The clause certainly doesn't refer to anything but the given rel. + * If either side is pseudoconstant then we can use it. + */ + if (bms_is_empty(restrictinfo->left_relids)) + { + /* righthand side is inner */ + restrictinfo->outer_is_left = true; + } + else if (bms_is_empty(restrictinfo->right_relids)) + { + /* lefthand side is inner */ + restrictinfo->outer_is_left = false; + } + else + continue; + + /* OK, add to list */ + restrictlist = lappend(restrictlist, restrictinfo); + } + /* Short-circuit the easy case */ - if (restrictlist == NIL) + if (restrictlist == NIL && exprlist == NIL) return false; /* Examine each index of the relation ... */ @@ -2285,12 +2338,14 @@ relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel, continue; /* - * Try to find each index column in the list of conditions. This is - * O(n^2) or worse, but we expect all the lists to be short. + * Try to find each index column in the lists of conditions. This is + * O(N^2) or worse, but we expect all the lists to be short. */ for (c = 0; c < ind->ncolumns; c++) { + bool matched = false; ListCell *lc; + ListCell *lc2; foreach(lc, restrictlist) { @@ -2319,10 +2374,45 @@ relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel, rexpr = get_leftop(rinfo->clause); if (match_index_to_operand(rexpr, c, ind)) - break; /* found a match; column is unique */ + { + matched = true; /* column is unique */ + break; + } + } + + if (matched) + continue; + + forboth(lc, exprlist, lc2, oprlist) + { + Node *expr = (Node *) lfirst(lc); + Oid opr = lfirst_oid(lc2); + + /* See if the expression matches the index key */ + if (!match_index_to_operand(expr, c, ind)) + continue; + + /* + * The equality operator must be a member of the index + * opfamily, else it is not asserting the right kind of + * equality behavior for this index. We assume the caller + * determined it is an equality operator, so we don't need to + * check any more tightly than this. + */ + if (!op_in_opfamily(opr, ind->opfamily[c])) + continue; + + /* + * XXX at some point we may need to check collations here too. + * For the moment we assume all collations reduce to the same + * notion of equality. + */ + + matched = true; /* column is unique */ + break; } - if (lc == NULL) + if (!matched) break; /* no match; this index doesn't help us */ } diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c index 1784ac2fc5bd77f4ddafe4a17625ef8128a3280a..b83c936a5e35575caa3ac6bbda65f8b111b21989 100644 --- a/src/backend/optimizer/plan/analyzejoins.c +++ b/src/backend/optimizer/plan/analyzejoins.c @@ -264,42 +264,13 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo) clause_list = lappend(clause_list, restrictinfo); } - /* Now examine the rel's restriction clauses for var = const clauses */ - foreach(l, innerrel->baserestrictinfo) - { - RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(l); - - /* - * Note: can_join won't be set for a restriction clause, but - * mergeopfamilies will be if it has a mergejoinable operator and - * doesn't contain volatile functions. - */ - if (restrictinfo->mergeopfamilies == NIL) - continue; /* not mergejoinable */ - - /* - * The clause certainly doesn't refer to anything but the given rel. - * If either side is pseudoconstant then we can use it. - */ - if (bms_is_empty(restrictinfo->left_relids)) - { - /* righthand side is inner */ - restrictinfo->outer_is_left = true; - } - else if (bms_is_empty(restrictinfo->right_relids)) - { - /* lefthand side is inner */ - restrictinfo->outer_is_left = false; - } - else - continue; - - /* OK, add to list */ - clause_list = lappend(clause_list, restrictinfo); - } + /* + * relation_has_unique_index_for automatically adds any usable restriction + * clauses for the innerrel, so we needn't do that here. + */ /* Now examine the indexes to see if we have a matching unique index */ - if (relation_has_unique_index_for(root, innerrel, clause_list)) + if (relation_has_unique_index_for(root, innerrel, clause_list, NIL, NIL)) return true; /* diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 6aa34412def0b7d127ef6ea16270af0b8aeeeeeb..1e7aac95ef4fba83b97b5a47ab0f4b6f29bee693 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -1021,8 +1021,8 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, pathnode->path.parent = rel; /* - * Treat the output as always unsorted, since we don't necessarily have - * pathkeys to represent it. + * Assume the output is unsorted, since we don't necessarily have pathkeys + * to represent it. (This might get overridden below.) */ pathnode->path.pathkeys = NIL; @@ -1030,6 +1030,29 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, pathnode->in_operators = in_operators; pathnode->uniq_exprs = uniq_exprs; + /* + * If the input is a relation and it has a unique index that proves the + * uniq_exprs are unique, then we don't need to do anything. Note that + * relation_has_unique_index_for automatically considers restriction + * clauses for the rel, as well. + */ + if (rel->rtekind == RTE_RELATION && all_btree && + relation_has_unique_index_for(root, rel, NIL, + uniq_exprs, in_operators)) + { + pathnode->umethod = UNIQUE_PATH_NOOP; + pathnode->rows = rel->rows; + pathnode->path.startup_cost = subpath->startup_cost; + pathnode->path.total_cost = subpath->total_cost; + pathnode->path.pathkeys = subpath->pathkeys; + + rel->cheapest_unique_path = (Path *) pathnode; + + MemoryContextSwitchTo(oldcontext); + + return pathnode; + } + /* * If the input is a subquery whose output must be unique already, then we * don't need to do anything. The test for uniqueness has to consider diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 7f1353a2a39d2043122a2cee2469cd1c2fc3fc9d..c62f4a8122a993a52f5373e4a4e61c013a3818a8 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -50,7 +50,8 @@ extern void best_inner_indexscan(PlannerInfo *root, RelOptInfo *rel, RelOptInfo *outer_rel, JoinType jointype, Path **cheapest_startup, Path **cheapest_total); extern bool relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel, - List *restrictlist); + List *restrictlist, + List *exprlist, List *oprlist); extern bool eclass_matches_any_index(EquivalenceClass *ec, EquivalenceMember *em, RelOptInfo *rel);