From f2f5a2fd92ed9cfbabc3bcb67cb1a3c5ae99bc7c Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 6 Jun 2018 20:07:31 +0300
Subject: [PATCH 2/2] WIP: Allow pushing join quals down into subqueries.

Plan subqueries a second time, to create parameterized plans with join
quals.
---
 src/backend/nodes/outfuncs.c                     |   2 -
 src/backend/optimizer/path/allpaths.c            | 143 ++++++++++++++++++++---
 src/backend/optimizer/path/costsize.c            |   3 +-
 src/backend/optimizer/plan/createplan.c          |  30 ++++-
 src/backend/optimizer/plan/setrefs.c             |   8 +-
 src/backend/optimizer/plan/subselect.c           |   4 +-
 src/backend/optimizer/prep/prepunion.c           |  22 ++--
 src/backend/optimizer/util/pathnode.c            |  16 ++-
 src/backend/optimizer/util/relnode.c             |   7 +-
 src/backend/utils/adt/selfuncs.c                 |  25 +++-
 src/include/nodes/relation.h                     |   8 +-
 src/include/optimizer/cost.h                     |   2 +-
 src/include/optimizer/pathnode.h                 |   7 +-
 src/test/regress/expected/subselect_pushdown.out |  17 ++-
 src/test/regress/sql/subselect_pushdown.sql      |   7 +-
 15 files changed, 227 insertions(+), 74 deletions(-)

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 610f9edaf5..0bc7855d0d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2340,8 +2340,6 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node)
 	WRITE_UINT_FIELD(pages);
 	WRITE_FLOAT_FIELD(tuples, "%.0f");
 	WRITE_FLOAT_FIELD(allvisfrac, "%.6f");
-	WRITE_NODE_FIELD(subroot);
-	WRITE_NODE_FIELD(subplan_params);
 	WRITE_INT_FIELD(rel_parallel_workers);
 	WRITE_OID_FIELD(serverid);
 	WRITE_OID_FIELD(userid);
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 35537272a3..bdcdc30985 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -106,6 +106,11 @@ static void accumulate_append_subpath(Path *path,
 						  List **subpaths, List **special_subpaths);
 static void set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 					  Index rti, RangeTblEntry *rte);
+static void add_subqueryscan_variant(PlannerInfo *root, RelOptInfo *rel,
+						 Index rti, RangeTblEntry *rte,
+						 Bitmapset *required_outer,
+						 Query *subquery, List *pushed_down_clauses, double tuple_fraction,
+						 bool update_estimates);
 static void set_function_pathlist(PlannerInfo *root, RelOptInfo *rel,
 					  RangeTblEntry *rte);
 static void set_values_pathlist(PlannerInfo *root, RelOptInfo *rel,
@@ -2080,13 +2085,13 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 					  Index rti, RangeTblEntry *rte)
 {
 	Query	   *parse = root->parse;
+	Query	   *unparameterized_subquery;
 	Query	   *subquery = rte->subquery;
 	Relids		required_outer;
 	pushdown_safety_info safetyInfo;
 	double		tuple_fraction;
-	RelOptInfo *sub_final_rel;
-	ListCell   *lc;
 	List	   *pushed_down_ec_joins = NIL;
+	bool		sq_is_pushdown_safe;
 
 	/*
 	 * Must copy the Query so that planning doesn't mess up the RTE contents
@@ -2147,9 +2152,10 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 	 * XXX Are there any cases where we want to make a policy decision not to
 	 * push down a pushable qual, because it'd result in a worse plan?
 	 */
-	if ((rel->baserestrictinfo != NIL ||
-		 (!bms_is_empty(required_outer) && (rel->joininfo || rel->has_eclass_joins))) &&
-		subquery_is_pushdown_safe(subquery, subquery, &safetyInfo))
+	sq_is_pushdown_safe = subquery_is_pushdown_safe(subquery, subquery, &safetyInfo);
+	if (sq_is_pushdown_safe &&
+		(rel->baserestrictinfo != NIL ||
+		 (!bms_is_empty(required_outer) && (rel->joininfo || rel->has_eclass_joins))))
 	{
 		/* OK to consider pushing down individual quals */
 		ListCell   *l;
@@ -2218,6 +2224,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 			if (rel->has_eclass_joins)
 			{
 				List	   *clauses;
+				ListCell *lc;
 
 				clauses = generate_join_implied_equalities(root,
 														   available_relids,
@@ -2243,8 +2250,6 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 		}
 	}
 
-	pfree(safetyInfo.unsafeColumns);
-
 	/*
 	 * The upper query might not use all the subquery's output columns; if
 	 * not, we can simplify.
@@ -2268,16 +2273,112 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 	else
 		tuple_fraction = root->tuple_fraction;
 
+	unparameterized_subquery = copyObject(subquery);
+
+	add_subqueryscan_variant(root, rel, rti, rte,
+							 required_outer, subquery, pushed_down_ec_joins, tuple_fraction, true);
+
+	/*
+	 * Also create parameterized join paths, where we push the join condition
+	 * down to the subquery.
+	 *
+	 * To keep the planning time reasonable, this is all-or-nothing. We try to
+	 * push all join conditions down to the subquery, and create paths for that.
+	 * We don't create paths for every combination of join conditions that we
+	 * could push down.
+	 */
+	if ((rel->has_eclass_joins || rel->joininfo) &&
+		sq_is_pushdown_safe)
+	{
+		List	   *clauses;
+		ListCell   *lc;
+		List	   *pushed_down_clauses = list_copy(pushed_down_ec_joins);
+		Bitmapset  *available_relids;
+		Bitmapset  *other_relids;
+
+		subquery = copyObject(unparameterized_subquery);
+
+		required_outer = bms_copy(required_outer);
+
+		foreach(lc, rel->joininfo)
+		{
+			RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+			Node	   *clause = (Node *) rinfo->clause;
+
+			if (!rinfo->pseudoconstant &&
+				qual_is_pushdown_safe(subquery, rti, clause, &safetyInfo))
+			{
+				/* Push it down */
+				required_outer = bms_union(required_outer,
+										   pull_varnos(clause));
+				required_outer = bms_del_member(required_outer, rti);
+
+				subquery_push_qual(subquery, rte, rti, clause, 0);
+
+				pushed_down_clauses = lappend(pushed_down_clauses, rinfo);
+			}
+		}
+
+		/*
+		 * We already pushed down any join quals with LATERAL referenced rels, don't add
+		 * them again.
+		 */
+		available_relids = bms_difference(root->all_baserels, rel->lateral_referencers);
+		other_relids = bms_del_member(bms_copy(available_relids), rti);
+
+		clauses = generate_join_implied_equalities(root,
+												   available_relids,
+												   other_relids,
+												   rel);
+		foreach(lc, clauses)
+		{
+			RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+			Node	   *clause = (Node *) rinfo->clause;
+
+			if (!rinfo->pseudoconstant &&
+				qual_is_pushdown_safe(subquery, rti, clause, &safetyInfo))
+			{
+				/* Push it down */
+				required_outer = bms_union(required_outer,
+										   pull_varnos(clause));
+				required_outer = bms_del_member(required_outer, rti);
+
+				subquery_push_qual(subquery, rte, rti, clause, 0);
+
+				pushed_down_clauses = lappend(pushed_down_clauses, rinfo);
+			}
+		}
+		if (pushed_down_clauses)
+			add_subqueryscan_variant(root, rel, rti, rte,
+									 required_outer,
+									 subquery, pushed_down_clauses, tuple_fraction, false);
+	}
+
+	pfree(safetyInfo.unsafeColumns);
+}
+
+static void
+add_subqueryscan_variant(PlannerInfo *root, RelOptInfo *rel,
+						 Index rti, RangeTblEntry *rte,
+						 Bitmapset *required_outer,
+						 Query *subquery, List *pushed_down_clauses, double tuple_fraction,
+						 bool update_estimates)
+{
+	RelOptInfo *sub_final_rel;
+	ListCell   *lc;
+	PlannerInfo *subroot;
+	List	   *subplan_params;
+
 	/* plan_params should not be in use in current query level */
 	Assert(root->plan_params == NIL);
 
 	/* Generate a subroot and Paths for the subquery */
-	rel->subroot = subquery_planner(root->glob, subquery,
-									root,
-									false, tuple_fraction);
+	subroot = subquery_planner(root->glob, subquery,
+							   root,
+							   false, tuple_fraction);
 
 	/* Isolate the params needed by this specific subplan */
-	rel->subplan_params = root->plan_params;
+	subplan_params = root->plan_params;
 	root->plan_params = NIL;
 
 	/*
@@ -2285,7 +2386,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 	 * so, it's desirable to produce an unadorned dummy path so that we will
 	 * recognize appropriate optimizations at this query level.
 	 */
-	sub_final_rel = fetch_upper_rel(rel->subroot, UPPERREL_FINAL, NULL);
+	sub_final_rel = fetch_upper_rel(subroot, UPPERREL_FINAL, NULL);
 
 	if (IS_DUMMY_REL(sub_final_rel))
 	{
@@ -2297,8 +2398,13 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 	 * Mark rel with estimated output rows, width, etc.  Note that we have to
 	 * do this before generating outer-query paths, else cost_subqueryscan is
 	 * not happy.
+	 *
+	 * Don't overwrite the estimates when we're creating parameterized paths
+	 * for joins. The estimate for a parameterized path includes the effects
+	 * of the join clauses.
 	 */
-	set_subquery_size_estimates(root, rel);
+	if (update_estimates)
+		set_subquery_size_estimates(root, rel, subroot);
 
 	/*
 	 * For each Path that subquery_planner produced, make a SubqueryScanPath
@@ -2317,8 +2423,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 
 		/* Generate outer path using this subpath */
 		add_path(rel, (Path *)
-				 create_subqueryscan_path(root, rel, subpath,
-										  pathkeys, required_outer, pushed_down_ec_joins));
+				 create_subqueryscan_path(root, rel, subroot, subplan_params, subpath,
+										  pathkeys, required_outer, pushed_down_clauses));
 	}
 
 	/* If outer rel allows parallelism, do same for partial paths. */
@@ -2342,9 +2448,9 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 
 			/* Generate outer path using this subpath */
 			add_partial_path(rel, (Path *)
-							 create_subqueryscan_path(root, rel, subpath,
+							 create_subqueryscan_path(root, rel, subroot, subplan_params, subpath,
 													  pathkeys,
-													  required_outer, pushed_down_ec_joins));
+													  required_outer, pushed_down_clauses));
 		}
 	}
 }
@@ -3231,8 +3337,7 @@ qual_is_pushdown_safe(Query *subquery, Index rti, Node *qual,
 	Assert(!contain_window_function(qual));
 
 	/*
-	 * Examine all Vars used in clause; since it's a restriction clause, all
-	 * such Vars must refer to subselect output columns.
+	 * Examine all Vars used in clause.
 	 */
 	vars = pull_var_clause(qual, PVC_INCLUDE_PLACEHOLDERS);
 	foreach(vl, vars)
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index a2a7e0c520..5f93328e22 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -4807,9 +4807,8 @@ get_foreign_key_join_selectivity(PlannerInfo *root,
  * We set the same fields as set_baserel_size_estimates.
  */
 void
-set_subquery_size_estimates(PlannerInfo *root, RelOptInfo *rel)
+set_subquery_size_estimates(PlannerInfo *root, RelOptInfo *rel, PlannerInfo *subroot)
 {
-	PlannerInfo *subroot = rel->subroot;
 	RelOptInfo *sub_final_rel;
 	ListCell   *lc;
 
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index d34c1bf128..8261c3e17c 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -3188,18 +3188,40 @@ create_subqueryscan_plan(PlannerInfo *root, SubqueryScanPath *best_path,
 	Plan	   *subplan;
 	ListCell   *l;
 	List	   *qpqual;
-	List	   *sq_quals = best_path->pushed_down_ec_joins;
+	List	   *sq_quals = best_path->pushed_down_clauses;
 
 	/* it should be a subquery base rel... */
 	Assert(scan_relid > 0);
 	Assert(rel->rtekind == RTE_SUBQUERY);
+	Assert(rel->chosen_plan == NULL);
 
 	/*
 	 * Recursively create Plan from Path for subquery.  Since we are entering
 	 * a different planner context (subroot), recurse to create_plan not
 	 * create_plan_recurse.
 	 */
-	subplan = create_plan(rel->subroot, best_path->subpath);
+	subplan = create_plan(best_path->subroot, best_path->subpath);
+
+	/*
+	 * If this path used join quals that were pushed down to the subquery,
+	 * we don't need to re-check those quals on the SubqueryScan node itself.
+	 */
+	if (best_path->pushed_down_clauses)
+	{
+		List	   *new_clauses = NIL;
+		ListCell   *l;
+
+		foreach(l, scan_clauses)
+		{
+			RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
+
+			if (list_member_ptr(best_path->pushed_down_clauses, rinfo))
+				continue;
+
+			new_clauses = lappend(new_clauses, rinfo);
+		}
+		scan_clauses = new_clauses;
+	}
 
 	/*
 	 * If we had pushed down any join clauses to the subquery, we don't need
@@ -3240,7 +3262,7 @@ create_subqueryscan_plan(PlannerInfo *root, SubqueryScanPath *best_path,
 		scan_clauses = (List *)
 			replace_nestloop_params(root, (Node *) scan_clauses);
 		process_subquery_nestloop_params(root,
-										 rel->subplan_params);
+										 best_path->subplan_params);
 	}
 
 	scan_plan = make_subqueryscan(tlist,
@@ -3250,6 +3272,8 @@ create_subqueryscan_plan(PlannerInfo *root, SubqueryScanPath *best_path,
 
 	copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
 
+	rel->chosen_plan = best_path->subroot;
+
 	return scan_plan;
 }
 
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 69dd327f0c..29b26c086a 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -323,12 +323,12 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
 				 * that some upper query level is treating this one as dummy,
 				 * and so we won't scan this level's plan tree at all.
 				 */
-				if (rel->subroot == NULL)
+				if (rel->chosen_plan == NULL)
 					flatten_unplanned_rtes(glob, rte);
 				else if (recursing ||
-						 IS_DUMMY_REL(fetch_upper_rel(rel->subroot,
+						 IS_DUMMY_REL(fetch_upper_rel(rel->chosen_plan,
 													  UPPERREL_FINAL, NULL)))
-					add_rtes_to_flat_rtable(rel->subroot, true);
+					add_rtes_to_flat_rtable(rel->chosen_plan, true);
 			}
 		}
 		rti++;
@@ -1070,7 +1070,7 @@ set_subqueryscan_references(PlannerInfo *root,
 	rel = find_base_rel(root, plan->scan.scanrelid);
 
 	/* Recursively process the subplan */
-	plan->subplan = set_plan_references(rel->subroot, plan->subplan);
+	plan->subplan = set_plan_references(rel->chosen_plan, plan->subplan);
 
 	if (trivial_subqueryscan(plan))
 	{
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 83008d7661..f756febe03 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2418,11 +2418,11 @@ finalize_plan(PlannerInfo *root, Plan *plan,
 
 				/* We must run finalize_plan on the subquery */
 				rel = find_base_rel(root, sscan->scan.scanrelid);
-				subquery_params = rel->subroot->outer_params;
+				subquery_params = rel->chosen_plan->outer_params;
 				if (gather_param >= 0)
 					subquery_params = bms_add_member(bms_copy(subquery_params),
 													 gather_param);
-				finalize_plan(rel->subroot, sscan->subplan, gather_param,
+				finalize_plan(rel->chosen_plan, sscan->subplan, gather_param,
 							  subquery_params, NULL);
 
 				/* Now we can add its extParams to the parent's params */
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 8f424031be..8bf864f768 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -266,10 +266,10 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 		Assert(root->plan_params == NIL);
 
 		/* Generate a subroot and Paths for the subquery */
-		subroot = rel->subroot = subquery_planner(root->glob, subquery,
-												  root,
-												  false,
-												  root->tuple_fraction);
+		subroot = subquery_planner(root->glob, subquery,
+								   root,
+								   false,
+								   root->tuple_fraction);
 
 		/*
 		 * It should not be possible for the primitive query to contain any
@@ -295,7 +295,7 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 		 * to do this before generating outer-query paths, else
 		 * cost_subqueryscan is not happy.
 		 */
-		set_subquery_size_estimates(root, rel);
+		set_subquery_size_estimates(root, rel, subroot);
 
 		/*
 		 * Since we may want to add a partial path to this relation, we must
@@ -320,8 +320,12 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 		 * the SubqueryScanPath with nil pathkeys.  (XXX that should change
 		 * soon too, likely.)
 		 */
-		path = (Path *) create_subqueryscan_path(root, rel, subpath,
-												 NIL, NULL, NIL);
+		path = (Path *) create_subqueryscan_path(root, rel,
+												 subroot,
+												 NIL,
+												 subpath,
+												 NIL,
+												 NULL, NIL);
 
 		add_path(rel, path);
 
@@ -338,8 +342,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 
 			partial_subpath = linitial(final_rel->partial_pathlist);
 			partial_path = (Path *)
-				create_subqueryscan_path(root, rel, partial_subpath,
-										 NIL, NULL, NIL);
+				create_subqueryscan_path(root, rel, subroot, NIL, partial_subpath, NIL,
+										 NULL, NIL);
 			add_partial_path(rel, partial_path);
 		}
 
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index e4909c1855..748e0120f1 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1868,8 +1868,12 @@ create_gather_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
  *	  returning the pathnode.
  */
 SubqueryScanPath *
-create_subqueryscan_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
-						 List *pathkeys, Relids required_outer, List *pushed_down_ec_joins)
+create_subqueryscan_path(PlannerInfo *root, RelOptInfo *rel,
+						 PlannerInfo *subroot,
+						 List *subplan_params,
+						 Path *subpath,
+						 List *pathkeys, Relids required_outer,
+						 List *pushed_down_clauses)
 {
 	SubqueryScanPath *pathnode = makeNode(SubqueryScanPath);
 
@@ -1884,7 +1888,9 @@ create_subqueryscan_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
 	pathnode->path.parallel_workers = subpath->parallel_workers;
 	pathnode->path.pathkeys = pathkeys;
 	pathnode->subpath = subpath;
-	pathnode->pushed_down_ec_joins = pushed_down_ec_joins;
+	pathnode->subplan_params = subplan_params;
+	pathnode->subroot = subroot;
+	pathnode->pushed_down_clauses = pushed_down_clauses;
 
 	cost_subqueryscan(pathnode, root, rel, pathnode->path.param_info);
 
@@ -3561,10 +3567,12 @@ reparameterize_path(PlannerInfo *root, Path *path,
 
 				return (Path *) create_subqueryscan_path(root,
 														 rel,
+														 spath->subroot,
+														 spath->subplan_params,
 														 spath->subpath,
 														 spath->path.pathkeys,
 														 required_outer,
-														 spath->pushed_down_ec_joins);
+														 spath->pushed_down_clauses);
 			}
 		case T_Append:
 			{
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 82b78420e7..b1b89310cb 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -135,8 +135,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->pages = 0;
 	rel->tuples = 0;
 	rel->allvisfrac = 0;
-	rel->subroot = NULL;
-	rel->subplan_params = NIL;
 	rel->rel_parallel_workers = -1; /* set up in get_relation_info */
 	rel->serverid = InvalidOid;
 	rel->userid = rte->checkAsUser;
@@ -549,8 +547,6 @@ build_join_rel(PlannerInfo *root,
 	joinrel->pages = 0;
 	joinrel->tuples = 0;
 	joinrel->allvisfrac = 0;
-	joinrel->subroot = NULL;
-	joinrel->subplan_params = NIL;
 	joinrel->rel_parallel_workers = -1;
 	joinrel->serverid = InvalidOid;
 	joinrel->userid = InvalidOid;
@@ -724,8 +720,7 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
 	joinrel->pages = 0;
 	joinrel->tuples = 0;
 	joinrel->allvisfrac = 0;
-	joinrel->subroot = NULL;
-	joinrel->subplan_params = NIL;
+	joinrel->chosen_plan = NULL;
 	joinrel->serverid = InvalidOid;
 	joinrel->userid = InvalidOid;
 	joinrel->useridiscurrent = false;
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 4b08cdb721..a813da2b00 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5037,6 +5037,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 		Query	   *subquery = rte->subquery;
 		RelOptInfo *rel;
 		TargetEntry *ste;
+		PlannerInfo *subroot;
 
 		/*
 		 * Punt if it's a whole-row var rather than a plain column reference.
@@ -5065,10 +5066,24 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 		 */
 		rel = find_base_rel(root, var->varno);
 
-		/* If the subquery hasn't been planned yet, we have to punt */
-		if (rel->subroot == NULL)
+		if (rel->chosen_plan)
+			subroot = rel->chosen_plan;
+		else if (rel->pathlist && IsA(linitial(rel->pathlist), SubqueryScanPath))
+		{
+			/*
+			 * Use the estimates from the first path. XXX: what if it's a parameterized
+			 * path?
+			 */
+			SubqueryScanPath *sqpath = (SubqueryScanPath *) linitial(rel->pathlist);
+
+			subroot = sqpath->subroot;
+		}
+		else
+		{
+			/* If the subquery hasn't been planned yet, we have to punt */
 			return;
-		Assert(IsA(rel->subroot, PlannerInfo));
+		}
+		Assert(IsA(subroot, PlannerInfo));
 
 		/*
 		 * Switch our attention to the subquery as mangled by the planner. It
@@ -5078,7 +5093,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 		 * planning, Vars in the targetlist might have gotten replaced, and we
 		 * need to see the replacement expressions.
 		 */
-		subquery = rel->subroot->parse;
+		subquery = subroot->parse;
 		Assert(IsA(subquery, Query));
 
 		/* Get the subquery output expression referenced by the upper Var */
@@ -5130,7 +5145,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 			 * if the underlying column is unique, the subquery may have
 			 * joined to other tables in a way that creates duplicates.
 			 */
-			examine_simple_variable(rel->subroot, var, vardata);
+			examine_simple_variable(subroot, var, vardata);
 		}
 	}
 	else
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index de942b4e40..aca74a4ece 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -651,8 +651,7 @@ typedef struct RelOptInfo
 	BlockNumber pages;			/* size estimates derived from pg_class */
 	double		tuples;
 	double		allvisfrac;
-	PlannerInfo *subroot;		/* if subquery */
-	List	   *subplan_params; /* if subquery */
+	PlannerInfo *chosen_plan;
 	int			rel_parallel_workers;	/* wanted number of parallel workers */
 
 	/* Information about foreign tables and foreign joins */
@@ -1237,7 +1236,10 @@ typedef struct SubqueryScanPath
 {
 	Path		path;
 	Path	   *subpath;		/* path representing subquery execution */
-	List	   *pushed_down_ec_joins; /* pushed-down quals derived from ECs */
+	PlannerInfo *subroot;		/* */
+	List	   *subplan_params; /* */
+
+	List	   *pushed_down_clauses; /* pushed-down join quals */
 } SubqueryScanPath;
 
 /*
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index 77ca7ff837..82bb0f51a3 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -189,7 +189,7 @@ extern void set_joinrel_size_estimates(PlannerInfo *root, RelOptInfo *rel,
 						   RelOptInfo *inner_rel,
 						   SpecialJoinInfo *sjinfo,
 						   List *restrictlist);
-extern void set_subquery_size_estimates(PlannerInfo *root, RelOptInfo *rel);
+extern void set_subquery_size_estimates(PlannerInfo *root, RelOptInfo *rel, PlannerInfo *subroot);
 extern void set_function_size_estimates(PlannerInfo *root, RelOptInfo *rel);
 extern void set_values_size_estimates(PlannerInfo *root, RelOptInfo *rel);
 extern void set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel,
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 848839ed15..596815ecfc 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -91,8 +91,11 @@ extern GatherMergePath *create_gather_merge_path(PlannerInfo *root,
 						 Relids required_outer,
 						 double *rows);
 extern SubqueryScanPath *create_subqueryscan_path(PlannerInfo *root,
-						 RelOptInfo *rel, Path *subpath,
-						 List *pathkeys, Relids required_outer, List *pushed_down_ec_joins);
+						 RelOptInfo *rel,
+						 PlannerInfo *subroot,
+						 List *subplan_params,
+						 Path *subpath,
+						 List *pathkeys, Relids required_outer, List *pushed_down_clauses);
 extern Path *create_functionscan_path(PlannerInfo *root, RelOptInfo *rel,
 						 List *pathkeys, Relids required_outer);
 extern Path *create_tablexprscan_path(PlannerInfo *root, RelOptInfo *rel,
diff --git a/src/test/regress/expected/subselect_pushdown.out b/src/test/regress/expected/subselect_pushdown.out
index f160bc84c8..792f66e7fd 100644
--- a/src/test/regress/expected/subselect_pushdown.out
+++ b/src/test/regress/expected/subselect_pushdown.out
@@ -25,7 +25,7 @@ where smalltab.i = subq.i and smalltab.i = 123;
                Index Cond: (i = 123)
 (7 rows)
 
--- Join quals are not currently pushed down
+-- Push down join quals.
 explain (costs off)
 select * from smalltab,
 (
@@ -36,18 +36,17 @@ select * from smalltab,
 where smalltab.i = subq.i;
                    QUERY PLAN                    
 -------------------------------------------------
- Merge Join
-   Merge Cond: (smalltab.i = bigtab.i)
-   ->  Sort
-         Sort Key: smalltab.i
-         ->  Seq Scan on smalltab
+ Nested Loop
+   ->  Seq Scan on smalltab
    ->  GroupAggregate
          Group Key: bigtab.i
          ->  Index Scan using bigtab_i on bigtab
-(8 rows)
+               Index Cond: (smalltab.i = i)
+(6 rows)
 
--- Except when the subquery is LATERAL, and already references the other relation.
--- Such join clauses can be pushed down.
+-- Subquery is LATERAL, and already references the other relation. The join
+-- qual is always pushed down in that case, as the plan is "parameterized"
+-- in respect to the other relation even if it was not pushed down.
 explain (costs off)
 select * from smalltab,
 lateral (
diff --git a/src/test/regress/sql/subselect_pushdown.sql b/src/test/regress/sql/subselect_pushdown.sql
index c9e7a43794..d6431d314c 100644
--- a/src/test/regress/sql/subselect_pushdown.sql
+++ b/src/test/regress/sql/subselect_pushdown.sql
@@ -20,7 +20,7 @@ select * from smalltab,
 ) as subq(i, avg)
 where smalltab.i = subq.i and smalltab.i = 123;
 
--- Join quals are not currently pushed down
+-- Push down join quals.
 explain (costs off)
 select * from smalltab,
 (
@@ -30,8 +30,9 @@ select * from smalltab,
 ) as subq(i, avg)
 where smalltab.i = subq.i;
 
--- Except when the subquery is LATERAL, and already references the other relation.
--- Such join clauses can be pushed down.
+-- Subquery is LATERAL, and already references the other relation. The join
+-- qual is always pushed down in that case, as the plan is "parameterized"
+-- in respect to the other relation even if it was not pushed down.
 explain (costs off)
 select * from smalltab,
 lateral (
-- 
2.11.0

