bool is_clone,
List **postponed_oj_qual_list);
static bool check_redundant_nullability_qual(PlannerInfo *root, Node *clause);
+static Relids get_join_domain_min_rels(PlannerInfo *root, Relids domain_relids);
static void check_mergejoinable(RestrictInfo *restrictinfo);
static void check_hashjoinable(RestrictInfo *restrictinfo);
static void check_memoizable(RestrictInfo *restrictinfo);
* RestrictInfo lists for the moment, but eventually createplan.c will
* pull it out and make a gating Result node immediately above whatever
* plan node the pseudoconstant clause is assigned to. It's usually best
- * to put a gating node as high in the plan tree as possible, which we can
- * do by assigning it the full relid set of the current JoinDomain.
+ * to put a gating node as high in the plan tree as possible.
*/
if (bms_is_empty(relids))
{
}
else
{
- /* eval at join domain level */
- relids = bms_copy(jtitem->jdomain->jd_relids);
+ /*
+ * If we are in the top-level join domain, we can push the qual to
+ * the top of the plan tree. Otherwise, be conservative and eval
+ * it at original syntactic level. (Ideally we'd push it to the
+ * top of the current join domain in all cases, but that causes
+ * problems if we later rearrange outer-join evaluation order.
+ * Pseudoconstant quals below the top level are a pretty odd case,
+ * so it's not clear that it's worth working hard on.)
+ */
+ if (jtitem->jdomain == (JoinDomain *) linitial(root->join_domains))
+ relids = bms_copy(jtitem->jdomain->jd_relids);
+ else
+ relids = bms_copy(qualscope);
/* mark as gating qual */
pseudoconstant = true;
/* tell createplan.c to check for gating quals */
/*
* If the clause is variable-free, our normal heuristic for pushing it
* down to just the mentioned rels doesn't work, because there are none.
- * Apply it as a gating qual at the given qualscope.
+ * Apply it as a gating qual at the appropriate level (see comments for
+ * get_join_domain_min_rels).
*/
if (bms_is_empty(relids))
{
- /* eval at join domain level */
- relids = bms_copy(qualscope);
+ /* eval at join domain's safe level */
+ relids = get_join_domain_min_rels(root, qualscope);
/* mark as gating qual */
pseudoconstant = true;
/* tell createplan.c to check for gating quals */
return restrictinfo;
}
+/*
+ * get_join_domain_min_rels
+ * Identify the appropriate join level for derived quals belonging
+ * to the join domain with the given relids.
+ *
+ * When we derive a pseudoconstant (Var-free) clause from an EquivalenceClass,
+ * we'd ideally apply the clause at the top level of the EC's join domain.
+ * However, if there are any outer joins inside that domain that get commuted
+ * with joins outside it, that leads to not finding a correct place to apply
+ * the clause. Instead, remove any lower outer joins from the relid set,
+ * and apply the clause to just the remaining rels. This still results in a
+ * correct answer, since if the clause produces FALSE then the LHS of these
+ * joins will be empty leading to an empty join result.
+ *
+ * However, there's no need to remove outer joins if this is the top-level
+ * join domain of the query, since then there's nothing else to commute with.
+ *
+ * Note: it's tempting to use this in distribute_qual_to_rels where it's
+ * dealing with pseudoconstant quals; but we can't because the necessary
+ * SpecialJoinInfos aren't all formed at that point.
+ *
+ * The result is always freshly palloc'd; we do not modify domain_relids.
+ */
+static Relids
+get_join_domain_min_rels(PlannerInfo *root, Relids domain_relids)
+{
+ Relids result = bms_copy(domain_relids);
+ ListCell *lc;
+
+ /* Top-level join domain? */
+ if (bms_equal(result, root->all_query_rels))
+ return result;
+
+ /* Nope, look for lower outer joins that could potentially commute out */
+ foreach(lc, root->join_info_list)
+ {
+ SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(lc);
+
+ if (sjinfo->jointype == JOIN_LEFT &&
+ bms_is_member(sjinfo->ojrelid, result))
+ {
+ result = bms_del_member(result, sjinfo->ojrelid);
+ result = bms_del_members(result, sjinfo->syn_righthand);
+ }
+ }
+ return result;
+}
+
/*
* match_foreign_keys_to_quals
-> Seq Scan on onek t4
(13 rows)
+-- More tests of correct placement of pseudoconstant quals
+-- simple constant-false condition
+explain (costs off)
+select * from int8_tbl t1 left join
+ (int8_tbl t2 inner join int8_tbl t3 on false
+ left join int8_tbl t4 on t2.q2 = t4.q2)
+on t1.q1 = t2.q1;
+ QUERY PLAN
+--------------------------------------
+ Hash Left Join
+ Hash Cond: (t1.q1 = q1)
+ -> Seq Scan on int8_tbl t1
+ -> Hash
+ -> Result
+ One-Time Filter: false
+(6 rows)
+
+-- deduce constant-false from an EquivalenceClass
+explain (costs off)
+select * from int8_tbl t1 left join
+ (int8_tbl t2 inner join int8_tbl t3 on (t2.q1-t3.q2) = 0 and (t2.q1-t3.q2) = 1
+ left join int8_tbl t4 on t2.q2 = t4.q2)
+on t1.q1 = t2.q1;
+ QUERY PLAN
+--------------------------------------
+ Hash Left Join
+ Hash Cond: (t1.q1 = q1)
+ -> Seq Scan on int8_tbl t1
+ -> Hash
+ -> Result
+ One-Time Filter: false
+(6 rows)
+
+-- pseudoconstant based on an outer-level Param
+explain (costs off)
+select exists(
+ select * from int8_tbl t1 left join
+ (int8_tbl t2 inner join int8_tbl t3 on x0.f1 = 1
+ left join int8_tbl t4 on t2.q2 = t4.q2)
+ on t1.q1 = t2.q1
+) from int4_tbl x0;
+ QUERY PLAN
+---------------------------------------------------------------------
+ Seq Scan on int4_tbl x0
+ SubPlan 1
+ -> Nested Loop Left Join
+ Join Filter: (t2.q2 = t4.q2)
+ -> Nested Loop Left Join
+ Join Filter: (t1.q1 = t2.q1)
+ -> Seq Scan on int8_tbl t1
+ -> Materialize
+ -> Result
+ One-Time Filter: (x0.f1 = 1)
+ -> Nested Loop
+ -> Seq Scan on int8_tbl t2
+ -> Materialize
+ -> Seq Scan on int8_tbl t3
+ -> Materialize
+ -> Seq Scan on int8_tbl t4
+(16 rows)
+
-- check that join removal works for a left join when joining a subquery
-- that is guaranteed to be unique by its GROUP BY clause
explain (costs off)