diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 0463c477e7..910a738c20 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2270,8 +2270,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_OID_FIELD(userid); WRITE_BOOL_FIELD(useridiscurrent); /* we don't try to print fdwroutine or fdw_private */ - WRITE_NODE_FIELD(unique_for_rels); - /* can't print non_unique_for_rels; BMSes aren't Nodes */ + /* can't print unique_for_rels/non_unique_for_rels; BMSes aren't Nodes */ WRITE_NODE_FIELD(baserestrictinfo); WRITE_UINT_FIELD(baserestrict_min_security); WRITE_NODE_FIELD(joininfo); @@ -2343,19 +2342,6 @@ _outStatisticExtInfo(StringInfo str, const StatisticExtInfo *node) WRITE_BITMAPSET_FIELD(keys); } -static void -_outUniqueRelInfo(StringInfo str, const UniqueRelInfo *node) -{ - WRITE_NODE_TYPE("UNIQUERELINFO"); - - WRITE_BITMAPSET_FIELD(outerrelids); - if (node->index) - { - WRITE_OID_FIELD(index->indexoid); - WRITE_NODE_FIELD(column_values); - } -} - static void _outEquivalenceClass(StringInfo str, const EquivalenceClass *node) { @@ -4109,9 +4095,6 @@ outNode(StringInfo str, const void *obj) case T_StatisticExtInfo: _outStatisticExtInfo(str, obj); break; - case T_UniqueRelInfo: - _outUniqueRelInfo(str, obj); - break; case T_ExtensibleNode: _outExtensibleNode(str, obj); break; diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index 88e61d4c54..3434219dbd 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -3583,8 +3583,7 @@ ec_member_matches_indexcol(PlannerInfo *root, RelOptInfo *rel, * relation_has_unique_index_for * Determine whether the relation provably has at most one row satisfying * a set of equality conditions, because the conditions constrain all - * columns of some unique index. If index_info is not null, it is set to - * point to a new UniqueRelInfo containing the index and conditions. + * columns of some unique index. * * The conditions can be represented in either or both of two ways: * 1. A list of RestrictInfo nodes, where the caller has already determined @@ -3605,16 +3604,12 @@ ec_member_matches_indexcol(PlannerInfo *root, RelOptInfo *rel, bool relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel, List *restrictlist, - List *exprlist, List *oprlist, - UniqueRelInfo **unique_info) + List *exprlist, List *oprlist) { ListCell *ic; Assert(list_length(exprlist) == list_length(oprlist)); - if (unique_info) - *unique_info = NULL; - /* Short-circuit if no indexes... */ if (rel->indexlist == NIL) return false; @@ -3665,7 +3660,6 @@ relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel, { IndexOptInfo *ind = (IndexOptInfo *) lfirst(ic); int c; - List *column_values = NIL; /* * If the index is not unique, or not immediately enforced, or if it's @@ -3714,9 +3708,6 @@ relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel, if (match_index_to_operand(rexpr, c, ind)) { matched = true; /* column is unique */ - column_values = lappend(column_values, rinfo->outer_is_left - ? get_leftop(rinfo->clause) - : get_rightop(rinfo->clause)); break; } } @@ -3759,22 +3750,7 @@ relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel, /* Matched all key columns of this index? */ if (c == ind->nkeycolumns) - { - if (unique_info) - { - /* This may be called in GEQO memory context. */ - MemoryContext oldContext = MemoryContextSwitchTo(root->planner_cxt); - *unique_info = makeNode(UniqueRelInfo); - (*unique_info)->index = ind; - (*unique_info)->column_values = list_copy(column_values); - MemoryContextSwitchTo(oldContext); - } - if (column_values) - list_free(column_values); return true; - } - if (column_values) - list_free(column_values); } return false; diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c index 89cd236306..d8ff4bf432 100644 --- a/src/backend/optimizer/path/joinpath.c +++ b/src/backend/optimizer/path/joinpath.c @@ -176,8 +176,7 @@ add_paths_to_joinrel(PlannerInfo *root, innerrel, JOIN_INNER, restrictlist, - false, - NULL /*index_info*/); + false); break; default: extra.inner_unique = innerrel_is_unique(root, @@ -186,8 +185,7 @@ add_paths_to_joinrel(PlannerInfo *root, innerrel, jointype, restrictlist, - false, - NULL /*index_info*/); + false); break; } diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c index a310d89a39..7efcf9f569 100644 --- a/src/backend/optimizer/plan/analyzejoins.c +++ b/src/backend/optimizer/plan/analyzejoins.c @@ -34,27 +34,29 @@ #include "utils/lsyscache.h" /* local functions */ -static bool join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo); +static bool leftjoin_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo); static void remove_rel_from_query(PlannerInfo *root, int relid, Relids joinrelids); static List *remove_rel_from_joinlist(List *joinlist, int relid, int *nremoved); static bool rel_supports_distinctness(PlannerInfo *root, RelOptInfo *rel); static bool rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel, - List *clause_list, UniqueRelInfo **unique_info); + List *clause_list); static Oid distinct_col_search(int colno, List *colnos, List *opids); static bool is_innerrel_unique_for(PlannerInfo *root, Relids joinrelids, Relids outerrelids, RelOptInfo *innerrel, JoinType jointype, - List *restrictlist, - UniqueRelInfo **unique_info); + List *restrictlist); +static void split_selfjoin_quals(PlannerInfo *root, List *joinquals, + List **selfjoinquals, + List **otherjoinquals); /* - * remove_useless_joins - * Check for relations that don't actually need to be joined at all, - * and remove them from the query. + * remove_useless_left_joins + * Check for left joined relations that don't actually need to be joined + * at all, and remove them from the query. * * We are passed the current joinlist and return the updated list. Other * data structures that have to be updated are accessible via "root". @@ -76,11 +78,11 @@ restart: int nremoved; /* Skip if not removable */ - if (!join_is_removable(root, sjinfo)) + if (!leftjoin_is_removable(root, sjinfo)) continue; /* - * Currently, join_is_removable can only succeed when the sjinfo's + * Currently, leftjoin_is_removable can only succeed when the sjinfo's * righthand is a single baserel. Remove that rel from the query and * joinlist. */ @@ -148,9 +150,9 @@ clause_sides_match_join(RestrictInfo *rinfo, Relids outerrelids, } /* - * join_is_removable - * Check whether we need not perform this special join at all, because - * it will just duplicate its left input. + * leftjoin_is_removable + * Check whether we need not perform this left join at all, because it will + * just duplicate its left input. * * This is true for a left join for which the join condition cannot match * more than one inner-side row. (There are other possibly interesting @@ -159,7 +161,7 @@ clause_sides_match_join(RestrictInfo *rinfo, Relids outerrelids, * above the join. */ static bool -join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo) +leftjoin_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo) { int innerrelid; RelOptInfo *innerrel; @@ -253,8 +255,7 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo) } return is_innerrel_unique_for(root, joinrelids, sjinfo->min_lefthand, - innerrel, sjinfo->jointype, innerrel->joininfo, - NULL /*unique_index*/); + innerrel, sjinfo->jointype, innerrel->joininfo); } /* @@ -339,7 +340,8 @@ remove_rel_from_query(PlannerInfo *root, int relid, Relids joinrelids) * joinrelids. However, a PHV used at a partner rel could not have the * target rel in ph_eval_at, so we check that while deciding whether to * remove or just update the PHV. There is no corresponding test in - * join_is_removable because it doesn't need to distinguish those cases. + * leftjoin_is_removable because it doesn't need to distinguish those + * cases. */ for (l = list_head(root->placeholder_list); l != NULL; l = nextl) { @@ -526,7 +528,7 @@ reduce_unique_semijoins(PlannerInfo *root) /* Test whether the innerrel is unique for those clauses. */ if (!innerrel_is_unique(root, joinrelids, sjinfo->min_lefthand, innerrel, - JOIN_SEMI, restrictlist, true, NULL /*index_info*/)) + JOIN_SEMI, restrictlist, true)) continue; /* OK, remove the SpecialJoinInfo from the list. */ @@ -601,17 +603,10 @@ rel_supports_distinctness(PlannerInfo *root, RelOptInfo *rel) * Note that the passed-in clause_list may be destructively modified! This * is OK for current uses, because the clause_list is built by the caller for * the sole purpose of passing to this function. - * - * If unique_index is not null, it is set to point to the index that guarantees - * uniqueness for a base relation. */ static bool -rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel, List *clause_list, - UniqueRelInfo **unique_info) +rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel, List *clause_list) { - if (unique_info) - *unique_info = NULL; - /* * We could skip a couple of tests here if we assume all callers checked * rel_supports_distinctness first, but it doesn't seem worth taking any @@ -626,8 +621,8 @@ rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel, List *clause_list, * relation_has_unique_index_for automatically adds any usable * restriction clauses for the rel, so we needn't do that here. */ - return relation_has_unique_index_for(root, rel, clause_list, NIL, NIL, - unique_info); + if (relation_has_unique_index_for(root, rel, clause_list, NIL, NIL)) + return true; } else if (rel->rtekind == RTE_SUBQUERY) { @@ -931,10 +926,6 @@ distinct_col_search(int colno, List *colnos, List *opids) * heuristic about whether to cache negative answers; it should be "true" * if making an inquiry that is not part of the normal bottom-up join search * sequence. - * - * If index_info_out is not null, it is set to point to a new UniqueRelInfo - * allocated in root memory context, that describes the index that guarantees - * uniqueness. */ bool innerrel_is_unique(PlannerInfo *root, @@ -943,23 +934,12 @@ innerrel_is_unique(PlannerInfo *root, RelOptInfo *innerrel, JoinType jointype, List *restrictlist, - bool force_cache, - UniqueRelInfo **unique_info_out) + bool force_cache) { MemoryContext old_context; ListCell *lc; - UniqueRelInfo *unique_info; - - if (unique_info_out) - *unique_info_out = NULL; - /* - * It is possible to prove uniqueness even in the absence of joinclauses, - * just from baserestrictinfos alone. However, in these cases the inner - * relation returns one row at most, so join removal won't give much - * benefit. It seems better to save some planning time by ignoring these - * cases. - */ + /* Certainly can't prove uniqueness when there are no joinclauses */ if (restrictlist == NIL) return false; @@ -979,14 +959,10 @@ innerrel_is_unique(PlannerInfo *root, */ foreach(lc, innerrel->unique_for_rels) { - unique_info = (UniqueRelInfo *) lfirst(lc); + Relids unique_for_rels = (Relids) lfirst(lc); - if (bms_is_subset(unique_info->outerrelids, outerrelids)) - { - if (unique_info_out) - *unique_info_out = unique_info; + if (bms_is_subset(unique_for_rels, outerrelids)) return true; /* Success! */ - } } /* @@ -1003,7 +979,7 @@ innerrel_is_unique(PlannerInfo *root, /* No cached information, so try to make the proof. */ if (is_innerrel_unique_for(root, joinrelids, outerrelids, innerrel, - jointype, restrictlist, &unique_info)) + jointype, restrictlist)) { /* * Cache the positive result for future probes, being sure to keep it @@ -1016,15 +992,10 @@ innerrel_is_unique(PlannerInfo *root, * supersets of them anyway. */ old_context = MemoryContextSwitchTo(root->planner_cxt); - if (!unique_info) - unique_info = makeNode(UniqueRelInfo); - unique_info->outerrelids = bms_copy(outerrelids); - innerrel->unique_for_rels = lappend(innerrel->unique_for_rels, unique_info); + innerrel->unique_for_rels = lappend(innerrel->unique_for_rels, + bms_copy(outerrelids)); MemoryContextSwitchTo(old_context); - if (unique_info_out) - *unique_info_out = unique_info; - return true; /* Success! */ } else @@ -1070,15 +1041,11 @@ is_innerrel_unique_for(PlannerInfo *root, Relids outerrelids, RelOptInfo *innerrel, JoinType jointype, - List *restrictlist, - UniqueRelInfo **unique_info) + List *restrictlist) { List *clause_list = NIL; ListCell *lc; - if (unique_info) - *unique_info = NULL; - /* * Search for mergejoinable clauses that constrain the inner rel against * the outer rel. If an operator is mergejoinable then it behaves like @@ -1116,7 +1083,7 @@ is_innerrel_unique_for(PlannerInfo *root, } /* Let rel_is_distinct_for() do the hard work */ - return rel_is_distinct_for(root, innerrel, clause_list, unique_info); + return rel_is_distinct_for(root, innerrel, clause_list); } typedef struct @@ -1589,53 +1556,72 @@ remove_self_join_rel(UsjScratch *scratch, Relids joinrelids, List *joinclauses, } /* - * Test whether the relations are joined on the same unique column. + * split_selfjoin_quals + * Processes 'joinquals' building two lists, one with a list of quals + * where the columns/exprs on either side of the join match and another + * list containing the remaining quals. + * + * 'joinquals' must only contain quals for a RTE_RELATION being joined to + * itself. */ -static bool -is_unique_self_join(PlannerInfo *root, Relids joinrelids, RelOptInfo *outer, - RelOptInfo *inner, List *restrictlist) +static void +split_selfjoin_quals(PlannerInfo *root, List *joinquals, List **selfjoinquals, + List **otherjoinquals) { - UniqueRelInfo *outerinfo = NULL; - UniqueRelInfo *innerinfo = NULL; - List *outerValues, *innerValues; + ListCell *lc; + List *sjoinquals = NIL; + List *rjoinquals = NIL; - innerrel_is_unique(root, joinrelids, inner->relids, - outer, JOIN_INNER, restrictlist, true, &outerinfo); - if (!outerinfo || !outerinfo->index) - return false; + foreach(lc, joinquals) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + OpExpr *expr; + Expr *leftexpr; + Expr *rightexpr; - innerrel_is_unique(root, joinrelids, outer->relids, - inner, JOIN_INNER, restrictlist, true, &innerinfo); - if (!innerinfo || !innerinfo->index) - return false; + if (bms_num_members(rinfo->clause_relids) != 2) + continue; - /* We must have the same unique index for both relations. */ - if (outerinfo->index->indexoid != innerinfo->index->indexoid) - return false; + expr = (OpExpr *) rinfo->clause; - /* - * We have proven that for both relations, the same unique index guarantees - * that there is at most one row where columns equal given values. These - * values must be the same for both relations, or else we won't match the - * same row on each side of join. A value may be either Const or Var of some - * other relation. For the purposes of this proof, the Vars of the inner and - * outer relation are the same, so we replace outer varno with inner and - * compare the column values using equal(). - */ - innerValues = copyObject(innerinfo->column_values); - outerValues = copyObject(outerinfo->column_values); - change_varno((Expr *) innerValues, outer->relid, inner->relid); - change_varno((Expr *) outerValues, outer->relid, inner->relid); - if (!equal(outerValues, innerValues)) - { - list_free_deep(outerValues); - list_free_deep(innerValues); - return false; + if (!IsA(expr, OpExpr) || list_length(expr->args) != 2) + continue; + + leftexpr = linitial(expr->args); + rightexpr = lsecond(expr->args); + + /* Can't match of the exprs are not of the same type */ + if (leftexpr->type != rightexpr->type) + continue; + + /* Easy case, both exprs are Vars */ + if (IsA(leftexpr, Var)) + { + Var *leftvar = (Var *) leftexpr; + Var *rightvar = (Var *) rightexpr; + + Assert(leftvar->varlevelsup == 0); + Assert(rightvar->varlevelsup == 0); + + /* + * Ensure the caller didn't pass us any join quals that are not + * self join quals. + */ + Assert(root->simple_rte_array[leftvar->varno]->relid == + root->simple_rte_array[rightvar->varno]->relid); + + if (leftvar->varattno == rightvar->varattno) + sjoinquals = lappend(sjoinquals, rinfo); + else + rjoinquals = lappend(rjoinquals, rinfo); + + } + + /* TODO handle complex exprs by replacing varnos on RHS expr */ } - list_free_deep(outerValues); - list_free_deep(innerValues); - return true; + *selfjoinquals = sjoinquals; + *otherjoinquals = rjoinquals; } /* @@ -1662,7 +1648,9 @@ remove_self_joins_one_group(UsjScratch *scratch, Index *relids, int n) for (i = o + 1; i < n; i++) { RelOptInfo *inner = root->simple_rel_array[relids[i]]; - List *restrictlist; + List *restrictlist; + List *selfjoinquals; + List *otherjoinquals; /* A sanity check: the relations have the same Oid. */ Assert(root->simple_rte_array[relids[i]]->relid @@ -1688,8 +1676,24 @@ remove_self_joins_one_group(UsjScratch *scratch, Index *relids, int n) /* Is it a unique self join? */ restrictlist = build_joinrel_restrictlist(root, joinrelids, outer, inner); - if (!is_unique_self_join(root, joinrelids, outer, inner, - restrictlist)) + + /* + * Process restrictlist to seperate out the self join quals from + * the other quals. e.g x = x goes to selfjoinquals and a = b to + * otherjoinquals. + */ + split_selfjoin_quals(root, restrictlist, &selfjoinquals, + &otherjoinquals); + + /* + * Determine if the inner table can duplicate outer rows. We must + * bypass the unique rel cache here since we're possibly using a + * subset of join quals. We can use 'force_cache' = true when all + * join quals are selfjoin quals. Otherwise we could end up + * putting false negatives in the cache. + */ + if (!innerrel_is_unique(root, joinrelids, inner->relids, + outer, JOIN_INNER, selfjoinquals, list_length(otherjoinquals) == 0)) continue; /* diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 493f4255dd..169e51e792 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -1578,8 +1578,7 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, if (rel->rtekind == RTE_RELATION && sjinfo->semi_can_btree && relation_has_unique_index_for(root, rel, NIL, sjinfo->semi_rhs_exprs, - sjinfo->semi_operators, - NULL /*index_info*/)) + sjinfo->semi_operators)) { pathnode->umethod = UNIQUE_PATH_NOOP; pathnode->path.rows = rel->rows; diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 59ca1fe40e..ffb4cd4bcc 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -272,7 +272,6 @@ typedef enum NodeTag T_RollupData, T_GroupingSetData, T_StatisticExtInfo, - T_UniqueRelInfo, /* * TAGS FOR MEMORY NODES (memnodes.h) diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 0592b64bb7..253e0b7e48 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -522,11 +522,8 @@ typedef struct PartitionSchemeData *PartitionScheme; * populate these fields, for base rels; but someday they might be used for * join rels too: * - * unique_for_rels - list of UniqueRelInfos, each of them recording - * a set of other rels for which this one has been proven - * unique. If this is a baserel that is made unique by an - * index, UniqueRelInfo also stores the information about - * that index. + * unique_for_rels - list of Relid sets, each one being a set of other + * rels for which this one has been proven unique * non_unique_for_rels - list of Relid sets, each one being a set of * other rels for which we have tried and failed to prove * this one unique @@ -2474,19 +2471,4 @@ typedef struct JoinCostWorkspace double inner_rows_total; } JoinCostWorkspace; -/* - * UniqueRelInfo records a fact that a relation is unique when being joined - * to other relation(s) specified by outerrelids. If the uniqueness is - * guaranteed by a unique index, this index is also saved. The values that - * constrain index columns, be it Vars of outer relations or Consts, are saved - * to column_values list. - */ -typedef struct UniqueRelInfo -{ - NodeTag tag; - Relids outerrelids; - IndexOptInfo *index; - List *column_values; -} UniqueRelInfo; - #endif /* PATHNODES_H */ diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 4260227b9f..36d12bc376 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -70,11 +70,9 @@ extern void debug_print_rel(PlannerInfo *root, RelOptInfo *rel); * routines to generate index paths */ extern void create_index_paths(PlannerInfo *root, RelOptInfo *rel); - extern bool relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel, List *restrictlist, - List *exprlist, List *oprlist, - UniqueRelInfo **info); + List *exprlist, List *oprlist); extern bool indexcol_is_bool_constant_for_query(IndexOptInfo *index, int indexcol); extern bool match_index_to_operand(Node *operand, int indexcol, diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index 252b8da686..15fa476907 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -103,8 +103,7 @@ extern bool query_is_distinct_for(Query *query, List *colnos, List *opids); extern bool innerrel_is_unique(PlannerInfo *root, Relids joinrelids, Relids outerrelids, RelOptInfo *innerrel, - JoinType jointype, List *restrictlist, bool force_cache, - UniqueRelInfo **unique_info); + JoinType jointype, List *restrictlist, bool force_cache); extern void remove_useless_self_joins(PlannerInfo *root, List **jointree, List *tlist); diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 38e3842278..71d6cf7dc7 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -4365,13 +4365,11 @@ explain (costs off) select p.* from (parent p left join child c on (p.k = c.k)) join parent x on p.k = x.k where p.k = 1 and p.k = 2; - QUERY PLAN ------------------------------------------------- + QUERY PLAN +-------------------------- Result One-Time Filter: false - -> Index Scan using parent_pkey on parent x - Index Cond: (k = 1) -(4 rows) +(2 rows) -- bug 5255: this is not optimizable by join removal begin; @@ -4674,27 +4672,19 @@ explain (costs off) select 1 from reset join_collapse_limit; reset enable_seqscan; --- If index conditions are different for each side, we won't select the same --- row on both sides, so the join can't be removed. +-- We can remove the join even if we find the join can't duplicate rows and +-- the base quals of each side are different. In the following case we end up +-- moving quals over to s1 to make it so it can't match any rows. create table sl(a int, b int); create unique index sl_ab on sl(a, b); explain (costs off) select * from sl t1, sl t2 where t1.a = t2.a and t1.b = 1 and t2.b = 2; - QUERY PLAN ----------------------------------------------- - Nested Loop - Join Filter: (t1.a = t2.a) - -> Bitmap Heap Scan on sl t1 - Recheck Cond: (b = 1) - -> Bitmap Index Scan on sl_ab - Index Cond: (b = 1) - -> Materialize - -> Bitmap Heap Scan on sl t2 - Recheck Cond: (b = 2) - -> Bitmap Index Scan on sl_ab - Index Cond: (b = 2) -(11 rows) + QUERY PLAN +--------------------------------------------------------- + Index Only Scan using sl_ab on sl t2 + Index Cond: ((a IS NOT NULL) AND (b = 2) AND (b = 1)) +(2 rows) reset enable_hashjoin; reset enable_mergejoin; diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index 001b41b8b7..262b3988f8 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -1651,8 +1651,9 @@ explain (costs off) select 1 from reset join_collapse_limit; reset enable_seqscan; --- If index conditions are different for each side, we won't select the same --- row on both sides, so the join can't be removed. +-- We can remove the join even if we find the join can't duplicate rows and +-- the base quals of each side are different. In the following case we end up +-- moving quals over to s1 to make it so it can't match any rows. create table sl(a int, b int); create unique index sl_ab on sl(a, b);