v14-remove-unique-self-joins_delta.patch
application/octet-stream
Filename: v14-remove-unique-self-joins_delta.patch
Type: application/octet-stream
Part: 1
Message:
Re: Removing unneeded self joins
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
Series: patch v14
| File | + | − |
|---|---|---|
| src/backend/nodes/outfuncs.c | 1 | 18 |
| src/backend/optimizer/path/indxpath.c | 2 | 26 |
| src/backend/optimizer/path/joinpath.c | 2 | 4 |
| src/backend/optimizer/plan/analyzejoins.c | 110 | 106 |
| src/backend/optimizer/util/pathnode.c | 1 | 2 |
| src/include/nodes/nodes.h | 0 | 1 |
| src/include/nodes/pathnodes.h | 2 | 20 |
| src/include/optimizer/paths.h | 1 | 3 |
| src/include/optimizer/planmain.h | 1 | 2 |
| src/test/regress/expected/join.out | 11 | 21 |
| src/test/regress/sql/join.sql | 3 | 2 |
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);