Some time ago, I wrote:
> I've been studying the bug reported at
> http://www.postgresql.org/message-id/20130617235236.GA1636@jeremyevans.local
> ...
> After some contemplation, I think that the most practical way to fix
> this is for deconstruct_recurse and distribute_qual_to_rels to
> effectively move such a qual to the place where it logically belongs;
> that is, rather than processing it when we look at the lower WHERE
> clause, set it aside for a moment and then add it back when looking at
> the ON clause of the appropriate outer join.  This should be reasonably
> easy to do by keeping a list of "postponed lateral clauses" while we're
> scanning the join tree.

Here's a draft patch for this.  The comments need a bit more work
probably, but barring objection I want to push this in before this
afternoon's 9.3rc1 wrap.

                        regards, tom lane

diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 98f601c..e055088 100644
*** a/src/backend/optimizer/plan/initsplan.c
--- b/src/backend/optimizer/plan/initsplan.c
*************** int			from_collapse_limit;
*** 36,47 ****
  int			join_collapse_limit;
  
  
  static void extract_lateral_references(PlannerInfo *root, RelOptInfo *brel,
  						   Index rtindex);
  static void add_lateral_info(PlannerInfo *root, Relids lhs, Relids rhs);
  static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode,
  					bool below_outer_join,
! 					Relids *qualscope, Relids *inner_join_rels);
  static SpecialJoinInfo *make_outerjoininfo(PlannerInfo *root,
  				   Relids left_rels, Relids right_rels,
  				   Relids inner_join_rels,
--- 36,56 ----
  int			join_collapse_limit;
  
  
+ /* Elements of the postponed_qual_list used during deconstruct_recurse */
+ typedef struct PostponedQual
+ {
+ 	Node	   *qual;			/* a qual clause waiting to be processed */
+ 	Relids		relids;			/* the set of baserels it references */
+ } PostponedQual;
+ 
+ 
  static void extract_lateral_references(PlannerInfo *root, RelOptInfo *brel,
  						   Index rtindex);
  static void add_lateral_info(PlannerInfo *root, Relids lhs, Relids rhs);
  static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode,
  					bool below_outer_join,
! 					Relids *qualscope, Relids *inner_join_rels,
! 					List **postponed_qual_list);
  static SpecialJoinInfo *make_outerjoininfo(PlannerInfo *root,
  				   Relids left_rels, Relids right_rels,
  				   Relids inner_join_rels,
*************** static void distribute_qual_to_rels(Plan
*** 53,59 ****
  						Relids qualscope,
  						Relids ojscope,
  						Relids outerjoin_nonnullable,
! 						Relids deduced_nullable_relids);
  static bool check_outerjoin_delay(PlannerInfo *root, Relids *relids_p,
  					  Relids *nullable_relids_p, bool is_pushed_down);
  static bool check_equivalence_delay(PlannerInfo *root,
--- 62,69 ----
  						Relids qualscope,
  						Relids ojscope,
  						Relids outerjoin_nonnullable,
! 						Relids deduced_nullable_relids,
! 						List **postponed_qual_list);
  static bool check_outerjoin_delay(PlannerInfo *root, Relids *relids_p,
  					  Relids *nullable_relids_p, bool is_pushed_down);
  static bool check_equivalence_delay(PlannerInfo *root,
*************** add_lateral_info(PlannerInfo *root, Reli
*** 630,644 ****
  List *
  deconstruct_jointree(PlannerInfo *root)
  {
  	Relids		qualscope;
  	Relids		inner_join_rels;
  
  	/* Start recursion at top of jointree */
  	Assert(root->parse->jointree != NULL &&
  		   IsA(root->parse->jointree, FromExpr));
  
! 	return deconstruct_recurse(root, (Node *) root->parse->jointree, false,
! 							   &qualscope, &inner_join_rels);
  }
  
  /*
--- 640,662 ----
  List *
  deconstruct_jointree(PlannerInfo *root)
  {
+ 	List	   *result;
  	Relids		qualscope;
  	Relids		inner_join_rels;
+ 	List	   *postponed_qual_list = NIL;
  
  	/* Start recursion at top of jointree */
  	Assert(root->parse->jointree != NULL &&
  		   IsA(root->parse->jointree, FromExpr));
  
! 	result = deconstruct_recurse(root, (Node *) root->parse->jointree, false,
! 								 &qualscope, &inner_join_rels,
! 								 &postponed_qual_list);
! 
! 	/* Shouldn't be any leftover quals */
! 	Assert(postponed_qual_list == NIL);
! 
! 	return result;
  }
  
  /*
*************** deconstruct_jointree(PlannerInfo *root)
*** 656,668 ****
   *	*inner_join_rels gets the set of base Relids syntactically included in
   *		inner joins appearing at or below this jointree node (do not modify
   *		or free this, either)
   *	Return value is the appropriate joinlist for this jointree node
   *
   * In addition, entries will be added to root->join_info_list for outer joins.
   */
  static List *
  deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
! 					Relids *qualscope, Relids *inner_join_rels)
  {
  	List	   *joinlist;
  
--- 674,689 ----
   *	*inner_join_rels gets the set of base Relids syntactically included in
   *		inner joins appearing at or below this jointree node (do not modify
   *		or free this, either)
+  *	*postponed_qual_list: list of PostponedQual structs, which we can add
+  *		quals to if they turn out to belong to a higher join level
   *	Return value is the appropriate joinlist for this jointree node
   *
   * In addition, entries will be added to root->join_info_list for outer joins.
   */
  static List *
  deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
! 					Relids *qualscope, Relids *inner_join_rels,
! 					List **postponed_qual_list)
  {
  	List	   *joinlist;
  
*************** deconstruct_recurse(PlannerInfo *root, N
*** 685,690 ****
--- 706,712 ----
  	else if (IsA(jtnode, FromExpr))
  	{
  		FromExpr   *f = (FromExpr *) jtnode;
+ 		List	   *child_postponed_quals = NIL;
  		int			remaining;
  		ListCell   *l;
  
*************** deconstruct_recurse(PlannerInfo *root, N
*** 707,713 ****
  			sub_joinlist = deconstruct_recurse(root, lfirst(l),
  											   below_outer_join,
  											   &sub_qualscope,
! 											   inner_join_rels);
  			*qualscope = bms_add_members(*qualscope, sub_qualscope);
  			sub_members = list_length(sub_joinlist);
  			remaining--;
--- 729,736 ----
  			sub_joinlist = deconstruct_recurse(root, lfirst(l),
  											   below_outer_join,
  											   &sub_qualscope,
! 											   inner_join_rels,
! 											   &child_postponed_quals);
  			*qualscope = bms_add_members(*qualscope, sub_qualscope);
  			sub_members = list_length(sub_joinlist);
  			remaining--;
*************** deconstruct_recurse(PlannerInfo *root, N
*** 729,734 ****
--- 752,774 ----
  			*inner_join_rels = *qualscope;
  
  		/*
+ 		 * Try to process any quals postponed by children.	If they need
+ 		 * further postponement, add them to my output postponed_qual_list.
+ 		 */
+ 		foreach(l, child_postponed_quals)
+ 		{
+ 			PostponedQual *pq = (PostponedQual *) lfirst(l);
+ 
+ 			if (bms_is_subset(pq->relids, *qualscope))
+ 				distribute_qual_to_rels(root, pq->qual,
+ 										false, below_outer_join, JOIN_INNER,
+ 										*qualscope, NULL, NULL, NULL,
+ 										NULL);
+ 			else
+ 				*postponed_qual_list = lappend(*postponed_qual_list, pq);
+ 		}
+ 
+ 		/*
  		 * Now process the top-level quals.
  		 */
  		foreach(l, (List *) f->quals)
*************** deconstruct_recurse(PlannerInfo *root, N
*** 737,748 ****
  
  			distribute_qual_to_rels(root, qual,
  									false, below_outer_join, JOIN_INNER,
! 									*qualscope, NULL, NULL, NULL);
  		}
  	}
  	else if (IsA(jtnode, JoinExpr))
  	{
  		JoinExpr   *j = (JoinExpr *) jtnode;
  		Relids		leftids,
  					rightids,
  					left_inners,
--- 777,790 ----
  
  			distribute_qual_to_rels(root, qual,
  									false, below_outer_join, JOIN_INNER,
! 									*qualscope, NULL, NULL, NULL,
! 									postponed_qual_list);
  		}
  	}
  	else if (IsA(jtnode, JoinExpr))
  	{
  		JoinExpr   *j = (JoinExpr *) jtnode;
+ 		List	   *child_postponed_quals = NIL;
  		Relids		leftids,
  					rightids,
  					left_inners,
*************** deconstruct_recurse(PlannerInfo *root, N
*** 771,780 ****
  			case JOIN_INNER:
  				leftjoinlist = deconstruct_recurse(root, j->larg,
  												   below_outer_join,
! 												   &leftids, &left_inners);
  				rightjoinlist = deconstruct_recurse(root, j->rarg,
  													below_outer_join,
! 													&rightids, &right_inners);
  				*qualscope = bms_union(leftids, rightids);
  				*inner_join_rels = *qualscope;
  				/* Inner join adds no restrictions for quals */
--- 813,824 ----
  			case JOIN_INNER:
  				leftjoinlist = deconstruct_recurse(root, j->larg,
  												   below_outer_join,
! 												   &leftids, &left_inners,
! 												   &child_postponed_quals);
  				rightjoinlist = deconstruct_recurse(root, j->rarg,
  													below_outer_join,
! 													&rightids, &right_inners,
! 													&child_postponed_quals);
  				*qualscope = bms_union(leftids, rightids);
  				*inner_join_rels = *qualscope;
  				/* Inner join adds no restrictions for quals */
*************** deconstruct_recurse(PlannerInfo *root, N
*** 784,793 ****
  			case JOIN_ANTI:
  				leftjoinlist = deconstruct_recurse(root, j->larg,
  												   below_outer_join,
! 												   &leftids, &left_inners);
  				rightjoinlist = deconstruct_recurse(root, j->rarg,
  													true,
! 													&rightids, &right_inners);
  				*qualscope = bms_union(leftids, rightids);
  				*inner_join_rels = bms_union(left_inners, right_inners);
  				nonnullable_rels = leftids;
--- 828,839 ----
  			case JOIN_ANTI:
  				leftjoinlist = deconstruct_recurse(root, j->larg,
  												   below_outer_join,
! 												   &leftids, &left_inners,
! 												   &child_postponed_quals);
  				rightjoinlist = deconstruct_recurse(root, j->rarg,
  													true,
! 													&rightids, &right_inners,
! 													&child_postponed_quals);
  				*qualscope = bms_union(leftids, rightids);
  				*inner_join_rels = bms_union(left_inners, right_inners);
  				nonnullable_rels = leftids;
*************** deconstruct_recurse(PlannerInfo *root, N
*** 795,804 ****
  			case JOIN_SEMI:
  				leftjoinlist = deconstruct_recurse(root, j->larg,
  												   below_outer_join,
! 												   &leftids, &left_inners);
  				rightjoinlist = deconstruct_recurse(root, j->rarg,
  													below_outer_join,
! 													&rightids, &right_inners);
  				*qualscope = bms_union(leftids, rightids);
  				*inner_join_rels = bms_union(left_inners, right_inners);
  				/* Semi join adds no restrictions for quals */
--- 841,852 ----
  			case JOIN_SEMI:
  				leftjoinlist = deconstruct_recurse(root, j->larg,
  												   below_outer_join,
! 												   &leftids, &left_inners,
! 												   &child_postponed_quals);
  				rightjoinlist = deconstruct_recurse(root, j->rarg,
  													below_outer_join,
! 													&rightids, &right_inners,
! 													&child_postponed_quals);
  				*qualscope = bms_union(leftids, rightids);
  				*inner_join_rels = bms_union(left_inners, right_inners);
  				/* Semi join adds no restrictions for quals */
*************** deconstruct_recurse(PlannerInfo *root, N
*** 807,816 ****
  			case JOIN_FULL:
  				leftjoinlist = deconstruct_recurse(root, j->larg,
  												   true,
! 												   &leftids, &left_inners);
  				rightjoinlist = deconstruct_recurse(root, j->rarg,
  													true,
! 													&rightids, &right_inners);
  				*qualscope = bms_union(leftids, rightids);
  				*inner_join_rels = bms_union(left_inners, right_inners);
  				/* each side is both outer and inner */
--- 855,866 ----
  			case JOIN_FULL:
  				leftjoinlist = deconstruct_recurse(root, j->larg,
  												   true,
! 												   &leftids, &left_inners,
! 												   &child_postponed_quals);
  				rightjoinlist = deconstruct_recurse(root, j->rarg,
  													true,
! 													&rightids, &right_inners,
! 													&child_postponed_quals);
  				*qualscope = bms_union(leftids, rightids);
  				*inner_join_rels = bms_union(left_inners, right_inners);
  				/* each side is both outer and inner */
*************** deconstruct_recurse(PlannerInfo *root, N
*** 853,859 ****
  			ojscope = NULL;
  		}
  
! 		/* Process the qual clauses */
  		foreach(l, (List *) j->quals)
  		{
  			Node	   *qual = (Node *) lfirst(l);
--- 903,934 ----
  			ojscope = NULL;
  		}
  
! 		/*
! 		 * Try to process any quals postponed by children.	If they need
! 		 * further postponement, add them to my output postponed_qual_list.
! 		 */
! 		foreach(l, child_postponed_quals)
! 		{
! 			PostponedQual *pq = (PostponedQual *) lfirst(l);
! 
! 			if (bms_is_subset(pq->relids, *qualscope))
! 				distribute_qual_to_rels(root, pq->qual,
! 										false, below_outer_join, j->jointype,
! 										*qualscope,
! 										ojscope, nonnullable_rels, NULL,
! 										NULL);
! 			else
! 			{
! 				/*
! 				 * We should not be postponing any quals past an outer join.
! 				 * If this Assert fires, pull_up_subqueries() messed up.
! 				 */
! 				Assert(j->jointype == JOIN_INNER);
! 				*postponed_qual_list = lappend(*postponed_qual_list, pq);
! 			}
! 		}
! 
! 		/* Process the JOIN's qual clauses */
  		foreach(l, (List *) j->quals)
  		{
  			Node	   *qual = (Node *) lfirst(l);
*************** deconstruct_recurse(PlannerInfo *root, N
*** 861,867 ****
  			distribute_qual_to_rels(root, qual,
  									false, below_outer_join, j->jointype,
  									*qualscope,
! 									ojscope, nonnullable_rels, NULL);
  		}
  
  		/* Now we can add the SpecialJoinInfo to join_info_list */
--- 936,943 ----
  			distribute_qual_to_rels(root, qual,
  									false, below_outer_join, j->jointype,
  									*qualscope,
! 									ojscope, nonnullable_rels, NULL,
! 									postponed_qual_list);
  		}
  
  		/* Now we can add the SpecialJoinInfo to join_info_list */
*************** make_outerjoininfo(PlannerInfo *root,
*** 1154,1160 ****
   *	  the appropriate list for each rel.  Alternatively, if the clause uses a
   *	  mergejoinable operator and is not delayed by outer-join rules, enter
   *	  the left- and right-side expressions into the query's list of
!  *	  EquivalenceClasses.
   *
   * 'clause': the qual clause to be distributed
   * 'is_deduced': TRUE if the qual came from implied-equality deduction
--- 1230,1237 ----
   *	  the appropriate list for each rel.  Alternatively, if the clause uses a
   *	  mergejoinable operator and is not delayed by outer-join rules, enter
   *	  the left- and right-side expressions into the query's list of
!  *	  EquivalenceClasses.  Alternatively, if the clause needs to be treated
!  *	  as belonging to a higher join level, just add it to postponed_qual_list.
   *
   * 'clause': the qual clause to be distributed
   * 'is_deduced': TRUE if the qual came from implied-equality deduction
*************** make_outerjoininfo(PlannerInfo *root,
*** 1170,1175 ****
--- 1247,1254 ----
   *		equal qualscope)
   * 'deduced_nullable_relids': if is_deduced is TRUE, the nullable relids to
   *		impute to the clause; otherwise NULL
+  * 'postponed_qual_list': list of PostponedQual structs, which we can add
+  *		this qual to if it turns out to belong to a higher join level
   *
   * 'qualscope' identifies what level of JOIN the qual came from syntactically.
   * 'ojscope' is needed if we decide to force the qual up to the outer-join
*************** distribute_qual_to_rels(PlannerInfo *roo
*** 1190,1196 ****
  						Relids qualscope,
  						Relids ojscope,
  						Relids outerjoin_nonnullable,
! 						Relids deduced_nullable_relids)
  {
  	Relids		relids;
  	bool		is_pushed_down;
--- 1269,1276 ----
  						Relids qualscope,
  						Relids ojscope,
  						Relids outerjoin_nonnullable,
! 						Relids deduced_nullable_relids,
! 						List **postponed_qual_list)
  {
  	Relids		relids;
  	bool		is_pushed_down;
*************** distribute_qual_to_rels(PlannerInfo *roo
*** 1207,1226 ****
  	relids = pull_varnos(clause);
  
  	/*
! 	 * Normally relids is a subset of qualscope, and we like to check that
! 	 * here as a crosscheck on the parser and rewriter.  That need not be the
! 	 * case when there are LATERAL RTEs, however: the clause could contain
! 	 * references to rels outside its syntactic scope as a consequence of
! 	 * pull-up of such references from a LATERAL subquery below it.  So, only
! 	 * check if the query contains no LATERAL RTEs.
! 	 *
! 	 * However, if it's an outer-join clause, we always insist that relids be
! 	 * a subset of ojscope.  This is safe because is_simple_subquery()
! 	 * disallows pullup of LATERAL subqueries that could cause the restriction
! 	 * to be violated.
  	 */
- 	if (!root->hasLateralRTEs && !bms_is_subset(relids, qualscope))
- 		elog(ERROR, "JOIN qualification cannot refer to other relations");
  	if (ojscope && !bms_is_subset(relids, ojscope))
  		elog(ERROR, "JOIN qualification cannot refer to other relations");
  
--- 1287,1322 ----
  	relids = pull_varnos(clause);
  
  	/*
! 	 * In ordinary SQL, a WHERE or JOIN/ON clause can't reference any rels
! 	 * that aren't within its syntactic scope; however, if we pulled up a
! 	 * LATERAL subquery then we might find such references in quals that have
! 	 * been pulled up.	We need to treat such quals as belonging to the join
! 	 * level that includes every rel they reference.  Although we could make
! 	 * pull_up_subqueries() place such quals correctly to begin with, it's
! 	 * easier to handle it here.  When we find a clause that contains Vars
! 	 * outside its syntactic scope, we add it to the postponed_clauses list,
! 	 * and process it once we've recursed back up to the appropriate join
! 	 * level.
! 	 */
! 	if (!bms_is_subset(relids, qualscope))
! 	{
! 		PostponedQual *pq = (PostponedQual *) palloc(sizeof(PostponedQual));
! 
! 		Assert(root->hasLateralRTEs);	/* shouldn't happen otherwise */
! 		Assert(jointype == JOIN_INNER); /* mustn't postpone past outer join */
! 		Assert(!is_deduced);	/* shouldn't be deduced, either */
! 		pq->qual = clause;
! 		pq->relids = relids;
! 		*postponed_qual_list = lappend(*postponed_qual_list, pq);
! 		return;
! 	}
! 
! 	/*
! 	 * In any case, if it's an outer-join clause, we insist that relids be a
! 	 * subset of ojscope.  (It's pull_up_subqueries()'s responsibility to not
! 	 * pull up a LATERAL subquery if that would cause this to fail; the
! 	 * semantics that would result from such a situation are unclear.)
  	 */
  	if (ojscope && !bms_is_subset(relids, ojscope))
  		elog(ERROR, "JOIN qualification cannot refer to other relations");
  
*************** process_implied_equality(PlannerInfo *ro
*** 1874,1880 ****
  	 */
  	distribute_qual_to_rels(root, (Node *) clause,
  							true, below_outer_join, JOIN_INNER,
! 							qualscope, NULL, NULL, nullable_relids);
  }
  
  /*
--- 1970,1977 ----
  	 */
  	distribute_qual_to_rels(root, (Node *) clause,
  							true, below_outer_join, JOIN_INNER,
! 							qualscope, NULL, NULL, nullable_relids,
! 							NULL);
  }
  
  /*
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 875baef..bb6d983 100644
*** a/src/backend/optimizer/prep/prepjointree.c
--- b/src/backend/optimizer/prep/prepjointree.c
*************** static bool is_simple_union_all(Query *s
*** 84,89 ****
--- 84,91 ----
  static bool is_simple_union_all_recurse(Node *setOp, Query *setOpQuery,
  							List *colTypes);
  static bool is_safe_append_member(Query *subquery);
+ static bool jointree_contains_lateral_outer_refs(Node *jtnode, bool restricted,
+ 									 Relids safe_upper_varnos);
  static void replace_vars_in_jointree(Node *jtnode,
  						 pullup_replace_vars_context *context,
  						 JoinExpr *lowest_nulling_outer_join);
*************** is_simple_subquery(Query *subquery, Rang
*** 1303,1322 ****
  		return false;
  
  	/*
! 	 * If the subquery is LATERAL, and we're below any outer join, and the
! 	 * subquery contains lateral references to rels outside the outer join,
! 	 * don't pull up.  Doing so would risk creating outer-join quals that
! 	 * contain references to rels outside the outer join, which is a semantic
! 	 * mess that doesn't seem worth addressing at the moment.
  	 */
! 	if (rte->lateral && lowest_outer_join != NULL)
  	{
! 		Relids		lvarnos = pull_varnos_of_level((Node *) subquery, 1);
! 		Relids		jvarnos = get_relids_in_jointree((Node *) lowest_outer_join,
! 													 true);
  
! 		if (!bms_is_subset(lvarnos, jvarnos))
  			return false;
  	}
  
  	/*
--- 1305,1351 ----
  		return false;
  
  	/*
! 	 * If the subquery is LATERAL, check to see if its WHERE or JOIN/ON quals
! 	 * contain any lateral references to rels outside an upper outer join
! 	 * (including the case where the outer join is within the subquery
! 	 * itself).  If so, don't pull up.  Doing so would result in a situation
! 	 * where we need to postpone quals from below an outer join to above it,
! 	 * which is probably completely wrong and in any case is a complication
! 	 * that doesn't seem worth addressing at the moment.
  	 */
! 	if (rte->lateral)
  	{
! 		bool		restricted;
! 		Relids		safe_upper_varnos;
  
! 		if (lowest_outer_join != NULL)
! 		{
! 			restricted = true;
! 			safe_upper_varnos = get_relids_in_jointree((Node *) lowest_outer_join,
! 													   true);
! 		}
! 		else
! 		{
! 			restricted = false;
! 			safe_upper_varnos = NULL;	/* doesn't matter */
! 		}
! 
! 		if (jointree_contains_lateral_outer_refs((Node *) subquery->jointree,
! 											  restricted, safe_upper_varnos))
  			return false;
+ 
+ 		/*
+ 		 * If there's an upper outer join, also disallow any targetlist
+ 		 * references outside it, since these might get pulled into quals
+ 		 * above this subquery.
+ 		 */
+ 		if (lowest_outer_join != NULL)
+ 		{
+ 			Relids		lvarnos = pull_varnos_of_level((Node *) subquery->targetList, 1);
+ 
+ 			if (!bms_is_subset(lvarnos, safe_upper_varnos))
+ 				return false;
+ 		}
  	}
  
  	/*
*************** is_simple_subquery(Query *subquery, Rang
*** 1344,1355 ****
  	 * correctly generate a Result plan for a jointree that's totally empty,
  	 * but we can't cope with an empty FromExpr appearing lower down in a
  	 * jointree: we identify join rels via baserelid sets, so we couldn't
! 	 * distinguish a join containing such a FromExpr from one without it.
! 	 * This would for example break the PlaceHolderVar mechanism, since we'd
! 	 * have no way to identify where to evaluate a PHV coming out of the
! 	 * subquery.  Not worth working hard on this, just to collapse
! 	 * SubqueryScan/Result into Result; especially since the SubqueryScan can
! 	 * often be optimized away by setrefs.c anyway.
  	 */
  	if (subquery->jointree->fromlist == NIL)
  		return false;
--- 1373,1384 ----
  	 * correctly generate a Result plan for a jointree that's totally empty,
  	 * but we can't cope with an empty FromExpr appearing lower down in a
  	 * jointree: we identify join rels via baserelid sets, so we couldn't
! 	 * distinguish a join containing such a FromExpr from one without it. This
! 	 * would for example break the PlaceHolderVar mechanism, since we'd have
! 	 * no way to identify where to evaluate a PHV coming out of the subquery.
! 	 * Not worth working hard on this, just to collapse SubqueryScan/Result
! 	 * into Result; especially since the SubqueryScan can often be optimized
! 	 * away by setrefs.c anyway.
  	 */
  	if (subquery->jointree->fromlist == NIL)
  		return false;
*************** is_safe_append_member(Query *subquery)
*** 1467,1472 ****
--- 1496,1575 ----
  }
  
  /*
+  * jointree_contains_lateral_outer_refs
+  *		Check for disallowed lateral references in a jointree's quals
+  *
+  * If restricted is false, all level-1 Vars are allowed (but we still must
+  * search the jointree, since it might contain outer joins below which there
+  * will be restrictions).  If restricted is true, return TRUE when any qual
+  * in the jointree contains level-1 Vars coming from outside the rels listed
+  * in safe_upper_varnos.
+  */
+ static bool
+ jointree_contains_lateral_outer_refs(Node *jtnode, bool restricted,
+ 									 Relids safe_upper_varnos)
+ {
+ 	if (jtnode == NULL)
+ 		return false;
+ 	if (IsA(jtnode, RangeTblRef))
+ 		return false;
+ 	else if (IsA(jtnode, FromExpr))
+ 	{
+ 		FromExpr   *f = (FromExpr *) jtnode;
+ 		ListCell   *l;
+ 
+ 		/* First, recurse to check child joins */
+ 		foreach(l, f->fromlist)
+ 		{
+ 			if (jointree_contains_lateral_outer_refs(lfirst(l),
+ 													 restricted,
+ 													 safe_upper_varnos))
+ 				return true;
+ 		}
+ 
+ 		/* Then check the top-level quals */
+ 		if (restricted &&
+ 			!bms_is_subset(pull_varnos_of_level(f->quals, 1),
+ 						   safe_upper_varnos))
+ 			return true;
+ 	}
+ 	else if (IsA(jtnode, JoinExpr))
+ 	{
+ 		JoinExpr   *j = (JoinExpr *) jtnode;
+ 
+ 		/*
+ 		 * If this is an outer join, we mustn't allow any upper lateral
+ 		 * references in or below it.
+ 		 */
+ 		if (j->jointype != JOIN_INNER)
+ 		{
+ 			restricted = true;
+ 			safe_upper_varnos = NULL;
+ 		}
+ 
+ 		/* Check the child joins */
+ 		if (jointree_contains_lateral_outer_refs(j->larg,
+ 												 restricted,
+ 												 safe_upper_varnos))
+ 			return true;
+ 		if (jointree_contains_lateral_outer_refs(j->rarg,
+ 												 restricted,
+ 												 safe_upper_varnos))
+ 			return true;
+ 
+ 		/* Check the JOIN's qual clauses */
+ 		if (restricted &&
+ 			!bms_is_subset(pull_varnos_of_level(j->quals, 1),
+ 						   safe_upper_varnos))
+ 			return true;
+ 	}
+ 	else
+ 		elog(ERROR, "unrecognized node type: %d",
+ 			 (int) nodeTag(jtnode));
+ 	return false;
+ }
+ 
+ /*
   * Helper routine for pull_up_subqueries: do pullup_replace_vars on every
   * expression in the jointree, without changing the jointree structure itself.
   * Ugly, but there's no other way...
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index fc3e168..98aacd3 100644
*** a/src/test/regress/expected/join.out
--- b/src/test/regress/expected/join.out
*************** explain (costs off)
*** 3161,3167 ****
   Nested Loop Left Join
     ->  Seq Scan on int4_tbl x
     ->  Index Scan using tenk1_unique1 on tenk1
!          Index Cond: (unique1 = x.f1)
  (4 rows)
  
  -- check scoping of lateral versus parent references
--- 3161,3167 ----
   Nested Loop Left Join
     ->  Seq Scan on int4_tbl x
     ->  Index Scan using tenk1_unique1 on tenk1
!          Index Cond: (x.f1 = unique1)
  (4 rows)
  
  -- check scoping of lateral versus parent references
*************** select * from int4_tbl i left join
*** 3648,3659 ****
    lateral (select * from int2_tbl j where i.f1 = j.f1) k on true;
                  QUERY PLAN                 
  -------------------------------------------
!  Nested Loop Left Join
     Output: i.f1, j.f1
!    Filter: (i.f1 = j.f1)
     ->  Seq Scan on public.int4_tbl i
           Output: i.f1
!    ->  Materialize
           Output: j.f1
           ->  Seq Scan on public.int2_tbl j
                 Output: j.f1
--- 3648,3659 ----
    lateral (select * from int2_tbl j where i.f1 = j.f1) k on true;
                  QUERY PLAN                 
  -------------------------------------------
!  Hash Left Join
     Output: i.f1, j.f1
!    Hash Cond: (i.f1 = j.f1)
     ->  Seq Scan on public.int4_tbl i
           Output: i.f1
!    ->  Hash
           Output: j.f1
           ->  Seq Scan on public.int2_tbl j
                 Output: j.f1
*************** select * from int4_tbl i left join
*** 3661,3670 ****
  
  select * from int4_tbl i left join
    lateral (select * from int2_tbl j where i.f1 = j.f1) k on true;
!  f1 | f1 
! ----+----
!   0 |  0
! (1 row)
  
  explain (verbose, costs off)
  select * from int4_tbl i left join
--- 3661,3674 ----
  
  select * from int4_tbl i left join
    lateral (select * from int2_tbl j where i.f1 = j.f1) k on true;
!      f1      | f1 
! -------------+----
!            0 |  0
!       123456 |   
!      -123456 |   
!   2147483647 |   
!  -2147483647 |   
! (5 rows)
  
  explain (verbose, costs off)
  select * from int4_tbl i left join
*************** select * from int4_tbl i left join
*** 3691,3696 ****
--- 3695,3723 ----
   -2147483647 | 
  (5 rows)
  
+ explain (verbose, costs off)
+ select * from int4_tbl a,
+   lateral (
+     select * from int4_tbl b left join int8_tbl c on (b.f1 = q1 and a.f1 = q2)
+   ) ss;
+                    QUERY PLAN                    
+ -------------------------------------------------
+  Nested Loop
+    Output: a.f1, b.f1, c.q1, c.q2
+    ->  Seq Scan on public.int4_tbl a
+          Output: a.f1
+    ->  Hash Left Join
+          Output: b.f1, c.q1, c.q2
+          Hash Cond: (b.f1 = c.q1)
+          ->  Seq Scan on public.int4_tbl b
+                Output: b.f1
+          ->  Hash
+                Output: c.q1, c.q2
+                ->  Seq Scan on public.int8_tbl c
+                      Output: c.q1, c.q2
+                      Filter: (a.f1 = c.q2)
+ (14 rows)
+ 
  -- lateral reference in a PlaceHolderVar evaluated at join level
  explain (verbose, costs off)
  select * from
diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql
index 36853dd..c0ed8b0 100644
*** a/src/test/regress/sql/join.sql
--- b/src/test/regress/sql/join.sql
*************** select * from int4_tbl i left join
*** 1022,1027 ****
--- 1022,1032 ----
    lateral (select coalesce(i) from int2_tbl j where i.f1 = j.f1) k on true;
  select * from int4_tbl i left join
    lateral (select coalesce(i) from int2_tbl j where i.f1 = j.f1) k on true;
+ explain (verbose, costs off)
+ select * from int4_tbl a,
+   lateral (
+     select * from int4_tbl b left join int8_tbl c on (b.f1 = q1 and a.f1 = q2)
+   ) ss;
  
  -- lateral reference in a PlaceHolderVar evaluated at join level
  explain (verbose, costs off)
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to