aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/backend/optimizer/path/joinpath.c55
-rw-r--r--src/backend/optimizer/plan/setrefs.c10
-rw-r--r--src/test/regress/expected/join.out21
-rw-r--r--src/test/regress/sql/join.sql7
4 files changed, 81 insertions, 12 deletions
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index cd80e61fd75..5ba266fdb6c 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -421,12 +421,33 @@ have_unsafe_outer_join_ref(PlannerInfo *root,
/*
* paraminfo_get_equal_hashops
- * Determine if param_info and innerrel's lateral_vars can be hashed.
- * Returns true the hashing is possible, otherwise return false.
+ * Determine if the clauses in param_info and innerrel's lateral_vars
+ * can be hashed.
+ * Returns true if hashing is possible, otherwise false.
*
- * Additionally we also collect the outer exprs and the hash operators for
- * each parameter to innerrel. These set in 'param_exprs', 'operators' and
- * 'binary_mode' when we return true.
+ * Additionally, on success we collect the outer expressions and the
+ * appropriate equality operators for each hashable parameter to innerrel.
+ * These are returned in parallel lists in *param_exprs and *operators.
+ * We also set *binary_mode to indicate whether strict binary matching is
+ * required.
+ *
+ * A complication is that innerrel's lateral_vars may contain nullingrel
+ * markers that need adjustment. This occurs if we have applied outer join
+ * identity 3,
+ * (A leftjoin B on (Pab)) leftjoin C on (Pb*c)
+ * = A leftjoin (B leftjoin C on (Pbc)) on (Pab)
+ * and C contains lateral references to B. It's still safe to apply the
+ * identity, but the parser will have created those references in the form
+ * "b*" (i.e., with varnullingrels listing the A/B join), while what we will
+ * have available from the nestloop's outer side is just "b". We deal with
+ * that here by stripping the nullingrels down to what is available from the
+ * outer side according to outerrel->relids.
+ * That fixes matters for the case of forward application of identity 3.
+ * If the identity was applied in the reverse direction, we will have
+ * innerrel's lateral_vars containing too few nullingrel bits rather than
+ * too many. Currently, that causes no problems because setrefs.c applies
+ * only a subset check to nullingrels in NestLoopParams, but we'd have to
+ * work harder if we ever want to tighten that check.
*/
static bool
paraminfo_get_equal_hashops(PlannerInfo *root, ParamPathInfo *param_info,
@@ -441,6 +462,7 @@ paraminfo_get_equal_hashops(PlannerInfo *root, ParamPathInfo *param_info,
*operators = NIL;
*binary_mode = false;
+ /* Add join clauses from param_info to the hash key */
if (param_info != NULL)
{
List *clauses = param_info->ppi_clauses;
@@ -510,7 +532,7 @@ paraminfo_get_equal_hashops(PlannerInfo *root, ParamPathInfo *param_info,
Node *expr = (Node *) lfirst(lc);
TypeCacheEntry *typentry;
- /* Reject if there are any volatile functions */
+ /* Reject if there are any volatile functions in PHVs */
if (contain_volatile_functions(expr))
{
list_free(*operators);
@@ -521,7 +543,7 @@ paraminfo_get_equal_hashops(PlannerInfo *root, ParamPathInfo *param_info,
typentry = lookup_type_cache(exprType(expr),
TYPECACHE_HASH_PROC | TYPECACHE_EQ_OPR);
- /* can't use a memoize node without a valid hash equals operator */
+ /* can't use memoize without a valid hash proc and equals operator */
if (!OidIsValid(typentry->hash_proc) || !OidIsValid(typentry->eq_opr))
{
list_free(*operators);
@@ -529,6 +551,25 @@ paraminfo_get_equal_hashops(PlannerInfo *root, ParamPathInfo *param_info,
return false;
}
+ /* OK, but adjust its nullingrels before adding it to result */
+ expr = copyObject(expr);
+ if (IsA(expr, Var))
+ {
+ Var *var = (Var *) expr;
+
+ var->varnullingrels = bms_intersect(var->varnullingrels,
+ outerrel->relids);
+ }
+ else if (IsA(expr, PlaceHolderVar))
+ {
+ PlaceHolderVar *phv = (PlaceHolderVar *) expr;
+
+ phv->phnullingrels = bms_intersect(phv->phnullingrels,
+ outerrel->relids);
+ }
+ else
+ Assert(false);
+
*operators = lappend_oid(*operators, typentry->eq_opr);
*param_exprs = lappend(*param_exprs, expr);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 3585a703fbd..ec5552327fb 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -2289,11 +2289,11 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset)
* the outer-join level at which they are used, Vars seen in the
* NestLoopParam expression may have nullingrels that are just a
* subset of those in the Vars actually available from the outer
- * side. Another case that can cause that to happen is explained
- * in the comments for process_subquery_nestloop_params. Not
- * checking this exactly is a bit grotty, but the work needed to
- * make things match up perfectly seems well out of proportion to
- * the value.
+ * side. Lateral references can create the same situation, as
+ * explained in the comments for process_subquery_nestloop_params
+ * and paraminfo_get_equal_hashops. Not checking this exactly is
+ * a bit grotty, but the work needed to make things match up
+ * perfectly seems well out of proportion to the value.
*/
nlp->paramval = (Var *) fix_upper_expr(root,
(Node *) nlp->paramval,
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index 4999c99f3bc..98b2667821e 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -2607,6 +2607,27 @@ select * from int8_tbl t1
Filter: (q1 = t2.q1)
(8 rows)
+explain (costs off)
+select * from onek t1
+ left join onek t2 on true
+ left join lateral
+ (select * from onek t3 where t3.two = t2.two offset 0) s
+ on t2.unique1 = 1;
+ QUERY PLAN
+--------------------------------------------------
+ Nested Loop Left Join
+ -> Seq Scan on onek t1
+ -> Materialize
+ -> Nested Loop Left Join
+ Join Filter: (t2.unique1 = 1)
+ -> Seq Scan on onek t2
+ -> Memoize
+ Cache Key: t2.two
+ Cache Mode: binary
+ -> Seq Scan on onek t3
+ Filter: (two = t2.two)
+(11 rows)
+
--
-- check a case where we formerly got confused by conflicting sort orders
-- in redundant merge join path keys
diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql
index 56ca759772b..7daa390b1d4 100644
--- a/src/test/regress/sql/join.sql
+++ b/src/test/regress/sql/join.sql
@@ -521,6 +521,13 @@ select * from int8_tbl t1
(select * from int8_tbl t3 where t3.q1 = t2.q1 offset 0) s
on t2.q1 = 1;
+explain (costs off)
+select * from onek t1
+ left join onek t2 on true
+ left join lateral
+ (select * from onek t3 where t3.two = t2.two offset 0) s
+ on t2.unique1 = 1;
+
--
-- check a case where we formerly got confused by conflicting sort orders
-- in redundant merge join path keys