diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index 66b059a..b1ea55a 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -911,7 +911,7 @@ deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel,
 	 * We handle relations for foreign tables, joins between those and upper
 	 * relations.
 	 */
-	Assert(rel->reloptkind == RELOPT_JOINREL ||
+	Assert(IS_JOIN_REL(rel) ||
 		   rel->reloptkind == RELOPT_BASEREL ||
 		   rel->reloptkind == RELOPT_OTHER_MEMBER_REL ||
 		   rel->reloptkind == RELOPT_UPPER_REL);
@@ -990,7 +990,7 @@ deparseSelectSql(List *tlist, List **retrieved_attrs, deparse_expr_cxt *context)
 	 */
 	appendStringInfoString(buf, "SELECT ");
 
-	if (foreignrel->reloptkind == RELOPT_JOINREL ||
+	if (IS_JOIN_REL(foreignrel) ||
 		foreignrel->reloptkind == RELOPT_UPPER_REL)
 	{
 		/* For a join relation use the input tlist */
@@ -1030,7 +1030,7 @@ deparseFromExpr(List *quals, deparse_expr_cxt *context)
 
 	/* For upper relations, scanrel must be either a joinrel or a baserel */
 	Assert(context->foreignrel->reloptkind != RELOPT_UPPER_REL ||
-		   scanrel->reloptkind == RELOPT_JOINREL ||
+		   IS_JOIN_REL(scanrel) ||
 		   scanrel->reloptkind == RELOPT_BASEREL);
 
 	/* Construct FROM clause */
@@ -1178,7 +1178,7 @@ deparseLockingClause(deparse_expr_cxt *context)
 			appendStringInfoString(buf, " FOR UPDATE");
 
 			/* Add the relation alias if we are here for a join relation */
-			if (rel->reloptkind == RELOPT_JOINREL)
+			if (IS_JOIN_REL(rel))
 				appendStringInfo(buf, " OF %s%d", REL_ALIAS_PREFIX, relid);
 		}
 		else
@@ -1345,7 +1345,7 @@ deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel,
 {
 	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
 
-	if (foreignrel->reloptkind == RELOPT_JOINREL)
+	if (IS_JOIN_REL(foreignrel))
 	{
 		RelOptInfo *rel_o = fpinfo->outerrel;
 		RelOptInfo *rel_i = fpinfo->innerrel;
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index fbe6929..7b3d8bb 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -721,8 +721,8 @@ get_useful_ecs_for_relation(PlannerInfo *root, RelOptInfo *rel)
 		return useful_eclass_list;
 
 	/* If this is a child rel, we must use the topmost parent rel to search. */
-	if (rel->reloptkind == RELOPT_OTHER_MEMBER_REL)
-		relids = find_childrel_top_parent(root, rel)->relids;
+	if (IS_OTHER_REL(rel))
+		relids = find_childrel_top_parent(root, rel);
 	else
 		relids = rel->relids;
 
@@ -1183,7 +1183,7 @@ postgresGetForeignPlan(PlannerInfo *root,
 			local_exprs = lappend(local_exprs, rinfo->clause);
 	}
 
-	if (foreignrel->reloptkind == RELOPT_JOINREL ||
+	if (IS_JOIN_REL(foreignrel) ||
 		foreignrel->reloptkind == RELOPT_UPPER_REL)
 	{
 		/* For a join relation, get the conditions from fdw_private structure */
@@ -1249,7 +1249,7 @@ postgresGetForeignPlan(PlannerInfo *root,
 							 remote_conds,
 							 retrieved_attrs,
 							 makeInteger(fpinfo->fetch_size));
-	if (foreignrel->reloptkind == RELOPT_JOINREL ||
+	if (IS_JOIN_REL(foreignrel->reloptkind) ||
 		foreignrel->reloptkind == RELOPT_UPPER_REL)
 		fdw_private = lappend(fdw_private,
 							  makeString(fpinfo->relation_name->data));
@@ -2529,7 +2529,7 @@ estimate_path_cost_size(PlannerInfo *root,
 						   &remote_param_join_conds, &local_param_join_conds);
 
 		/* Build the list of columns to be fetched from the foreign server. */
-		if (foreignrel->reloptkind == RELOPT_JOINREL ||
+		if (IS_JOIN_REL(foreignrel) ||
 			foreignrel->reloptkind == RELOPT_UPPER_REL)
 			fdw_scan_tlist = build_tlist_to_deparse(foreignrel);
 		else
@@ -2611,7 +2611,7 @@ estimate_path_cost_size(PlannerInfo *root,
 			startup_cost = fpinfo->rel_startup_cost;
 			run_cost = fpinfo->rel_total_cost - fpinfo->rel_startup_cost;
 		}
-		else if (foreignrel->reloptkind == RELOPT_JOINREL)
+		else if (IS_JOIN_REL(foreignrel))
 		{
 			PgFdwRelationInfo *fpinfo_i;
 			PgFdwRelationInfo *fpinfo_o;
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 9980582..41ffc94 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -20,6 +20,7 @@
 #include "access/nbtree.h"
 #include "access/sysattr.h"
 #include "catalog/dependency.h"
+#include "catalog/heap.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaddress.h"
 #include "catalog/partition.h"
@@ -139,6 +140,8 @@ static int32 partition_bound_cmp(PartitionKey key,
 static int partition_bound_bsearch(PartitionKey key,
 						PartitionBoundInfo boundinfo,
 						void *probe, bool probe_is_bound, bool *is_equal);
+static PartitionBoundInfo partition_bounds_copy(PartitionBoundInfo src,
+									PartitionKey key);
 
 /* Support get_partition_for_tuple() */
 static void FormPartitionKeyDatum(PartitionDispatch pd,
@@ -1942,3 +1945,250 @@ partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
 
 	return lo;
 }
+
+ /*
+ * Return a copy of given PartitionBoundInfo structure. The data types of bounds
+ * are described by given partition key specificiation.
+ */
+static PartitionBoundInfo
+partition_bounds_copy(PartitionBoundInfo src, PartitionKey key)
+{
+	PartitionBoundInfo	dest;
+	int		i;
+	int		ndatums;
+	int		partnatts;
+	int		num_indexes;
+
+	dest = (PartitionBoundInfo) palloc(sizeof(PartitionBoundInfoData));
+
+	dest->strategy = src->strategy;
+	ndatums = dest->ndatums = src->ndatums;
+	partnatts = key->partnatts;
+
+	/* Range partitioned table has an extra index. */
+	num_indexes = key->strategy == PARTITION_STRATEGY_RANGE ? ndatums + 1 : ndatums;
+
+	/* List partitioned tables have only a single partition key. */
+	Assert(key->strategy != PARTITION_STRATEGY_LIST || partnatts == 1);
+
+	dest->datums = (Datum **) palloc(sizeof(Datum *) * ndatums);
+
+	for (i = 0; i < ndatums; i++)
+	{
+		int		j;
+		dest->datums[i] = (Datum *) palloc(sizeof(Datum) * partnatts);
+
+		for (j = 0; j < partnatts; j++)
+			dest->datums[i][j] = datumCopy(src->datums[i][j],
+										   key->parttypbyval[j],
+										   key->parttyplen[j]);
+	}
+
+	if (src->content)
+	{
+		dest->content = (RangeDatumContent **) palloc(ndatums *
+												sizeof(RangeDatumContent *));
+		for (i = 0; i < ndatums; i++)
+		{
+			dest->content[i] = (RangeDatumContent *) palloc(partnatts *
+												sizeof(RangeDatumContent));
+
+			memcpy(dest->content[i], src->content[i],
+				   sizeof(RangeDatumContent) * key->partnatts);
+		}
+	}
+	else
+		dest->content = NULL;
+
+	dest->indexes = (int *) palloc(sizeof(int) * num_indexes);
+	memcpy(dest->indexes, src->indexes, sizeof(int) * num_indexes);
+
+	dest->has_null = src->has_null;
+	dest->null_index = src->null_index;
+
+	return dest;
+}
+
+/*
+ * find_partition_scheme
+ *
+ * The function returns a canonical partition scheme which exactly matches the
+ * partitioning properties of the given relation if one exists in the of
+ * canonical partitioning schemes maintained in PlannerInfo. If none of the
+ * existing partitioning schemes match, the function creates a canonical
+ * partition scheme and adds it to the list.
+ *
+ * For an unpartitioned table or for a multi-level partitioned table it returns
+ * NULL.
+ */
+extern PartitionScheme
+find_partition_scheme(PlannerInfo *root, Relation relation)
+{
+	PartitionKey	part_key = RelationGetPartitionKey(relation);
+	PartitionDesc	part_desc = RelationGetPartitionDesc(relation);
+	ListCell	   *lc;
+	int		nparts;
+	int		partnatts;
+	int		cnt_pks;
+	int		cnt_parts;
+	PartitionScheme	part_scheme = NULL;
+
+	/* No partition scheme for an unpartitioned relation. */
+	if (!part_desc || !part_key)
+		return NULL;
+
+	nparts = part_desc->nparts;
+	partnatts = part_key->partnatts;
+
+	/*
+	 * For a multi-level partitioned table, we do not retain the partitioning
+	 * hierarchy while expanding RTE for the topmost parent. Thus the number of
+	 * children as per root->append_rel_list does not match the number of
+	 * partitions specified in the partition descriptor and hence the
+	 * partitioning scheme of a multi-partitioned table does not reflect the
+	 * true picture. So for now, treat a multi-partitioned table as not
+	 * partitioned.
+	 */
+	for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++)
+	{
+		if (has_subclass(part_desc->oids[cnt_parts]))
+			return NULL;
+	}
+
+	/* Search for a matching partition scheme and return if found one. */
+	foreach (lc, root->part_schemes)
+	{
+		part_scheme = lfirst(lc);
+
+		/* Match number of partitions and partitioning strategy. */
+		if (nparts != part_scheme->nparts ||
+			part_key->strategy != part_scheme->strategy ||
+			partnatts != part_scheme->partnatts)
+			continue;
+
+		/* Match the partition key types. */
+		for (cnt_pks = 0; cnt_pks < partnatts; cnt_pks++)
+		{
+			/*
+			 * For types, it suffices to match the type id, mod and collation;
+			 * len, byval and align are depedent on the first two.
+			 */
+			if (part_key->partopfamily[cnt_pks] != part_scheme->partopfamily[cnt_pks] ||
+				part_key->partopcintype[cnt_pks] != part_scheme->partopcintype[cnt_pks] ||
+				part_key->parttypid[cnt_pks] != part_scheme->key_types[cnt_pks] ||
+				part_key->parttypmod[cnt_pks] != part_scheme->key_typmods[cnt_pks] ||
+				part_key->parttypcoll[cnt_pks] != part_scheme->key_collations[cnt_pks])
+				break;
+		}
+
+		/* Some partition key didn't match. Check next partitioning scheme. */
+		if (cnt_pks < partnatts)
+			continue;
+
+		if (!partition_bounds_equal(part_key, part_desc->boundinfo,
+									part_scheme->boundinfo))
+			continue;
+
+		/* Found matching partition scheme. */
+		return part_scheme;
+	}
+
+	/* Did not find matching partition scheme. Create one. */
+	part_scheme = (PartitionScheme) palloc0(sizeof(PartitionSchemeData));
+
+	/* Copy partition bounds/lists. */
+	part_scheme->nparts = part_desc->nparts;
+	part_scheme->strategy = part_key->strategy;
+	part_scheme->boundinfo = partition_bounds_copy(part_desc->boundinfo,
+												   part_key);
+
+	/* Store partition key information. */
+	part_scheme->partnatts = part_key->partnatts;
+
+	part_scheme->partopfamily = (Oid *) palloc(sizeof(Oid) * partnatts);
+	memcpy(part_scheme->partopfamily, part_key->partopfamily,
+		   sizeof(Oid) * partnatts);
+
+	part_scheme->partopcintype = (Oid *) palloc(sizeof(Oid) * partnatts);
+	memcpy(part_scheme->partopcintype, part_key->partopcintype,
+		   sizeof(Oid) * partnatts);
+
+	part_scheme->key_types = (Oid *) palloc(sizeof(Oid) * partnatts);
+	memcpy(part_scheme->key_types, part_key->parttypid,
+		   sizeof(Oid) * partnatts);
+
+	part_scheme->key_typmods = (int32 *) palloc(sizeof(int32) * partnatts);
+	memcpy(part_scheme->key_typmods, part_key->parttypmod,
+		   sizeof(int32) * partnatts);
+
+	part_scheme->key_collations = (Oid *) palloc(sizeof(Oid) * partnatts);
+	memcpy(part_scheme->key_collations, part_key->parttypcoll,
+		   sizeof(Oid) * partnatts);
+
+	/* Add the partitioning scheme to PlannerInfo. */
+	root->part_schemes = lappend(root->part_schemes, part_scheme);
+
+	return part_scheme;
+}
+
+/*
+ * build_baserel_partition_key_exprs
+ *
+ * Collect partition key expressions for a given base relation. The function
+ * converts any single column partition keys into corresponding Var nodes. It
+ * restamps Var nodes in partition key expressions by given varno. The
+ * partition key expressions are returned as an array of single element Lists
+ * to be stored in RelOptInfo of the base relation.
+ */
+extern List **
+build_baserel_partition_key_exprs(Relation relation, Index varno)
+{
+	PartitionKey	part_key = RelationGetPartitionKey(relation);
+	int		num_pkexprs;
+	int		cnt_pke;
+	List		  **partexprs;
+	ListCell	   *lc;
+
+	if (!part_key || part_key->partnatts <= 0)
+		return NULL;
+
+	num_pkexprs = part_key->partnatts;
+	partexprs = (List **) palloc(sizeof(List *) * num_pkexprs);
+	lc = list_head(part_key->partexprs);
+
+	for (cnt_pke = 0; cnt_pke < num_pkexprs; cnt_pke++)
+	{
+		AttrNumber attno = part_key->partattrs[cnt_pke];
+		Expr	  *pkexpr;
+
+		if (attno != InvalidAttrNumber)
+		{
+			/* Single column partition key is stored as a Var node. */
+			Form_pg_attribute att_tup;
+
+			if (attno < 0)
+				att_tup = SystemAttributeDefinition(attno,
+												relation->rd_rel->relhasoids);
+			else
+				att_tup = relation->rd_att->attrs[attno - 1];
+
+			pkexpr = (Expr *) makeVar(varno, attno, att_tup->atttypid,
+									  att_tup->atttypmod,
+									  att_tup->attcollation, 0);
+		}
+		else
+		{
+			if (lc == NULL)
+				elog(ERROR, "wrong number of partition key expressions");
+
+			/* Re-stamp the expressions with given varno. */
+			pkexpr = (Expr *) copyObject(lfirst(lc));
+			ChangeVarNodes((Node *) pkexpr, 1, varno, 0);
+			lc = lnext(lc);
+		}
+
+		partexprs[cnt_pke] = list_make1(pkexpr);
+	}
+
+	return partexprs;
+}
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index 242d6d2..75c95e4 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -721,7 +721,7 @@ GetExistingLocalJoinPath(RelOptInfo *joinrel)
 {
 	ListCell   *lc;
 
-	Assert(joinrel->reloptkind == RELOPT_JOINREL);
+	Assert(IS_JOIN_REL(joinrel));
 
 	foreach(lc, joinrel->pathlist)
 	{
@@ -786,7 +786,7 @@ GetExistingLocalJoinPath(RelOptInfo *joinrel)
 			ForeignPath *foreign_path;
 
 			foreign_path = (ForeignPath *) joinpath->outerjoinpath;
-			if (foreign_path->path.parent->reloptkind == RELOPT_JOINREL)
+			if (IS_JOIN_REL(foreign_path->path.parent))
 				joinpath->outerjoinpath = foreign_path->fdw_outerpath;
 		}
 
@@ -795,7 +795,7 @@ GetExistingLocalJoinPath(RelOptInfo *joinrel)
 			ForeignPath *foreign_path;
 
 			foreign_path = (ForeignPath *) joinpath->innerjoinpath;
-			if (foreign_path->path.parent->reloptkind == RELOPT_JOINREL)
+			if (IS_JOIN_REL(foreign_path->path.parent))
 				joinpath->innerjoinpath = foreign_path->fdw_outerpath;
 		}
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index d973225..fa0569c 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2049,6 +2049,14 @@ _copyRestrictInfo(const RestrictInfo *from)
 	COPY_SCALAR_FIELD(hashjoinoperator);
 	COPY_SCALAR_FIELD(left_bucketsize);
 	COPY_SCALAR_FIELD(right_bucketsize);
+	/*
+	 * Do not copy parent_rinfo and child_rinfos because 1. they create a
+	 * circular dependency between child and parent RestrictInfo 2. dropping
+	 * those links just means that we loose some memory optimizations. 3. There
+	 * is a possibility that the child and parent RestrictInfots themselves may
+	 * have got copied and thus the old links may no longer be valid. The
+	 * caller may set up those links itself, if needed.
+	 */
 
 	return newnode;
 }
diff --git a/src/backend/optimizer/README b/src/backend/optimizer/README
index 775bcc3..d755f1f 100644
--- a/src/backend/optimizer/README
+++ b/src/backend/optimizer/README
@@ -974,3 +974,56 @@ be desirable to postpone the Gather stage until as near to the top of the
 plan as possible.  Expanding the range of cases in which more work can be
 pushed below the Gather (and costing them accurately) is likely to keep us
 busy for a long time to come.
+
+Partition-wise joins
+--------------------
+A join between two similarly partitioned tables can be broken down into joins
+between their matching partitions if there exists an equi-join condition
+between the partition keys of the joining tables. The equi-join between
+partition keys implies that for a given row in a given partition of a given
+partitioned table, its joining row, if exists, should exist only in the
+matching partition of the other partitioned table; no row from non-matching
+partitions in the other partitioned table can join with the given row from the
+first table. This condition allows the join between partitioned table to be
+broken into joins between the matching partitions. The resultant join is
+partitioned in the same way as the joining relations, thus allowing an N-way
+join between similarly partitioned tables having equi-join condition between
+their partition keys to be broken down into N-way joins between their matching
+partitions. This technique of breaking down a join between partition tables
+into join between their partitions is called partition-wise join. We will use
+term "partitioned relation" for both partitioned table as well as join between
+partitioned tables which can use partition-wise join technique.
+
+Partitioning properties of a partitioned table are stored in
+PartitionSchemeData structure. Planner maintains a list of canonical partition
+schemes (distinct PartitionSchemeData objects) so that any two partitioned
+relations with same partitioning scheme share the same PartitionSchemeData
+object. This reduces memory consumed by PartitionSchemeData objects and makes
+it easy to compare the partition schemes of joining relations. RelOptInfos of
+partitioned relations hold partition key expressions and the RelOptInfos of
+the partition relations of that relation.
+
+Partition-wise joins are planned in two phases
+
+1. First phase creates the RelOptInfos for joins between matching partitions,
+henceforth referred to as child-joins. The number of paths created for a
+child-join i.e. join between partitions is same as the number of paths created
+for join between parents. That number grows exponentially with the number of
+base relations being joined. The time and memory consumed to create paths for
+each child-join will be proporional to the number of partitions. This will not
+scale well with thousands of partitions. Instead of that we estimate
+partition-wise join cost based on the costs of sampled child-joins. We choose
+child-joins with higher sizes to have realistic estimates. If the number of
+sampled child-joins is same as the number of live child-joins, we create append
+paths as we know costs of all required child-joins. Otherwise we create
+PartitionJoinPaths with cost estimates based on the costs of sampled
+child-joins. While creating append paths or PartitionJoin paths we create paths
+for all the different possible parameterizations and pathkeys available in the
+sampled child-joins.
+
+2. If PartitionJoinPath emerges as the best possible path, we create paths for
+each unsampled child-join. From every child-join we choose the cheapest path
+with same parameterization or pathkeys as the PartitionJoinPath. This path is
+converted into a plan and all the child-join plans are combined using an Append
+or MergeAppend plan as appropriate. We use a fresh memory context for planning
+each unsampled child-join, thus reducing memory consumption.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 9753a26..0a2c131 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -18,8 +18,10 @@
 #include <limits.h>
 #include <math.h>
 
+#include "miscadmin.h"
 #include "access/sysattr.h"
 #include "access/tsmapi.h"
+#include "catalog/partition.h"
 #include "catalog/pg_class.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_proc.h"
@@ -44,6 +46,7 @@
 #include "parser/parsetree.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
+#include "utils/rel.h"
 
 
 /* results of subquery_is_pushdown_safe */
@@ -93,8 +96,8 @@ static void set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
 static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 						Index rti, RangeTblEntry *rte);
 static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel,
-						   List *live_childrels,
-						   List *all_child_pathkeys);
+						   List *live_childrels, List *all_child_pathkeys,
+						   bool partition_join_path);
 static Path *get_cheapest_parameterized_child_path(PlannerInfo *root,
 									  RelOptInfo *rel,
 									  Relids required_outer);
@@ -126,6 +129,8 @@ static void subquery_push_qual(Query *subquery,
 static void recurse_push_qual(Node *setOp, Query *topquery,
 				  RangeTblEntry *rte, Index rti, Node *qual);
 static void remove_unused_subquery_outputs(Query *subquery, RelOptInfo *rel);
+static void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
+								List *live_childrels, bool partition_join_path);
 
 
 /*
@@ -868,6 +873,34 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
 	double	   *parent_attrsizes;
 	int			nattrs;
 	ListCell   *l;
+	Oid		   *part_oids = NULL;
+	int			nparts = 0;
+
+	/*
+	 * We require OIDs of the partitions to arrange the child RelOptInfos to
+	 * match the lists/ranges specified in the partitioning scheme. Fetch those
+	 * here so as keep those handy when going to child RelOptInfos below.
+	 */
+	if (rel->part_scheme)
+	{
+		RangeTblEntry	*rte = root->simple_rte_array[rel->relid];
+
+		/*
+		 * We need not lock the relation since it was already locked, either by
+		 * the rewriter or when expand_inherited_rtentry() added it to the
+		 * query's rangetable.
+		 */
+		Relation	relation = heap_open(rte->relid, NoLock);
+		PartitionDesc	part_desc = RelationGetPartitionDesc(relation);
+
+		part_oids = part_desc->oids;
+		nparts = part_desc->nparts;
+
+		Assert(part_oids && nparts > 0);
+
+		rel->part_rels = (RelOptInfo **)palloc0(sizeof(RelOptInfo *) * nparts);
+		heap_close(relation, NoLock);
+	}
 
 	/*
 	 * Initialize to compute size estimates for whole append relation.
@@ -899,6 +932,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
 		Node	   *childqual;
 		ListCell   *parentvars;
 		ListCell   *childvars;
+		int			cnt_parts;
+		List	   *appinfos = list_make1(appinfo);
 
 		/* append_rel_list contains all append rels; ignore others */
 		if (appinfo->parent_relid != parentRTindex)
@@ -912,8 +947,95 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
 		 * add_base_rels_to_query.
 		 */
 		childrel = find_base_rel(root, childRTindex);
+
+		/*
+		 * Recursively save topmost parent's relid in RelOptInfos of
+		 * partitions.
+		 */
+		if (rel->top_parent_relids)
+			childrel->top_parent_relids = rel->top_parent_relids;
+		else
+			childrel->top_parent_relids = bms_copy(rel->relids);
+
 		Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
 
+
+		/*
+		 * For two partitioned tables with the same partitioning scheme, it is
+		 * assumed that the Oids of matching partitions from both the tables
+		 * are placed at the same position in the array of partition oids in
+		 * respective partition descriptors. Saving the RelOptInfo of a
+		 * partition in the same cardinal position as its Oid makes it easy to
+		 * find the RelOptInfos of matching partitions for partition-wise join.
+		 */
+		for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++)
+		{
+			if (part_oids[cnt_parts] == childRTE->relid)
+			{
+				Assert(!rel->part_rels[cnt_parts]);
+				rel->part_rels[cnt_parts] = childrel;
+			}
+		}
+
+		/*
+		 * For a partitioned tables, individual partitions can participate in
+		 * the pair-wise joins. We need attr_needed data for building pair-wise
+		 * join relations. Partition tables should have same layout as the
+		 * parent table and hence should not need any translation. But rest of
+		 * the code still uses inheritance mechanism. So does this code. For
+		 * other inherited children, attr_needed is only examined for base
+		 * relations, no otherrels. So we compute attr_needed only for children
+		 * of a partitioned table.
+		 */
+		if (rel->part_scheme)
+		{
+			AttrNumber attno;
+			for (attno = rel->min_attr; attno <= rel->max_attr; attno++)
+			{
+				int	index = attno - rel->min_attr;
+				Relids	attr_needed = bms_copy(rel->attr_needed[index]);
+
+				/*
+				 * System attributes do not need translation. In such a case,
+				 * the attribute numbers of the parent and the child should
+				 * start from the same minimum attribute.
+				 */
+				if (attno <= 0)
+				{
+					Assert(rel->min_attr == childrel->min_attr);
+					childrel->attr_needed[index] = attr_needed;
+				}
+				else
+				{
+					Var *var = list_nth(appinfo->translated_vars,
+										attno - 1);
+					int child_index;
+
+					/* Parent Var translates to child Var. */
+					Assert(IsA(var, Var));
+
+					child_index = var->varattno - childrel->min_attr;
+					childrel->attr_needed[child_index] = attr_needed;
+				}
+			}
+		}
+
+		/*
+		 * Copy/Modify targetlist. Even if this child is deemed empty, we need
+		 * its targetlist in case it falls on nullable side in a child-join
+		 * because of partition-wise join.
+		 *
+		 * NB: the resulting childrel->reltarget->exprs may contain arbitrary
+		 * expressions, which otherwise would not occur in a rel's targetlist.
+		 * Code that might be looking at an appendrel child must cope with
+		 * such.  (Normally, a rel's targetlist would only include Vars and
+		 * PlaceHolderVars.)  XXX we do not bother to update the cost or width
+		 * fields of childrel->reltarget; not clear if that would be useful.
+		 */
+		childrel->reltarget->exprs = (List *)
+			adjust_appendrel_attrs(root,
+								   (Node *) rel->reltarget->exprs, appinfos);
+
 		/*
 		 * We have to copy the parent's targetlist and quals to the child,
 		 * with appropriate substitution of variables.  However, only the
@@ -931,7 +1053,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
 		childquals = get_all_actual_clauses(rel->baserestrictinfo);
 		childquals = (List *) adjust_appendrel_attrs(root,
 													 (Node *) childquals,
-													 appinfo);
+													 appinfos);
 		childqual = eval_const_expressions(root, (Node *)
 										   make_ands_explicit(childquals));
 		if (childqual && IsA(childqual, Const) &&
@@ -960,24 +1082,9 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
 			continue;
 		}
 
-		/*
-		 * CE failed, so finish copying/modifying targetlist and join quals.
-		 *
-		 * NB: the resulting childrel->reltarget->exprs may contain arbitrary
-		 * expressions, which otherwise would not occur in a rel's targetlist.
-		 * Code that might be looking at an appendrel child must cope with
-		 * such.  (Normally, a rel's targetlist would only include Vars and
-		 * PlaceHolderVars.)  XXX we do not bother to update the cost or width
-		 * fields of childrel->reltarget; not clear if that would be useful.
-		 */
-		childrel->joininfo = (List *)
-			adjust_appendrel_attrs(root,
-								   (Node *) rel->joininfo,
-								   appinfo);
-		childrel->reltarget->exprs = (List *)
-			adjust_appendrel_attrs(root,
-								   (Node *) rel->reltarget->exprs,
-								   appinfo);
+		/* CE failed, so finish copying/modifying join quals. */
+		childrel->joininfo = build_child_clauses(root, rel->joininfo,
+												 appinfos);
 
 		/*
 		 * We have to make child entries in the EquivalenceClass data
@@ -992,14 +1099,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
 		childrel->has_eclass_joins = rel->has_eclass_joins;
 
 		/*
-		 * Note: we could compute appropriate attr_needed data for the child's
-		 * variables, by transforming the parent's attr_needed through the
-		 * translated_vars mapping.  However, currently there's no need
-		 * because attr_needed is only examined for base relations not
-		 * otherrels.  So we just leave the child's attr_needed empty.
-		 */
-
-		/*
 		 * If parallelism is allowable for this query in general, see whether
 		 * it's allowable for this childrel in particular.  But if we've
 		 * already decided the appendrel is not parallel-safe as a whole,
@@ -1080,6 +1179,16 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
 		}
 	}
 
+	/* Should have found all the childrels of a partitioned relation. */
+	if (rel->part_scheme)
+	{
+		int		cnt_parts;
+		for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++)
+			if (!rel->part_rels[cnt_parts])
+				elog(ERROR, "could not find the RelOptInfo of a partition with oid %u",
+					 part_oids[cnt_parts]);
+	}
+
 	if (has_live_children)
 	{
 		/*
@@ -1122,19 +1231,11 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 {
 	int			parentRTindex = rti;
 	List	   *live_childrels = NIL;
-	List	   *subpaths = NIL;
-	bool		subpaths_valid = true;
-	List	   *partial_subpaths = NIL;
-	bool		partial_subpaths_valid = true;
-	List	   *all_child_pathkeys = NIL;
-	List	   *all_child_outers = NIL;
 	ListCell   *l;
 
 	/*
-	 * Generate access paths for each member relation, and remember the
-	 * cheapest path for each one.  Also, identify all pathkeys (orderings)
-	 * and parameterizations (required_outer sets) available for the member
-	 * relations.
+	 * Generate access paths for each member relation and remember the
+	 * non-dummy children.
 	 */
 	foreach(l, root->append_rel_list)
 	{
@@ -1142,7 +1243,6 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 		int			childRTindex;
 		RangeTblEntry *childRTE;
 		RelOptInfo *childrel;
-		ListCell   *lcp;
 
 		/* append_rel_list contains all append rels; ignore others */
 		if (appinfo->parent_relid != parentRTindex)
@@ -1177,6 +1277,64 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 		 * Child is live, so add it to the live_childrels list for use below.
 		 */
 		live_childrels = lappend(live_childrels, childrel);
+	}
+
+	/* Add Append/MergeAppend paths to the "append" relation. */
+	add_paths_to_append_rel(root, rel, live_childrels, false);
+}
+
+/*
+ * add_paths_to_append_rel
+ *		Generate Append/MergeAppend paths for given "append" relation. An
+ *		"append" relation can be a base parent relation or a join between
+ *		partitioned tables.
+ *
+ * The function collects all parameterizations and orderings supported by the
+ * non-dummy children. For every such parameterization or ordering, it creates
+ * an append path collecting one path from each non-dummy child with given
+ * parameterization or ordering. Similarly it collects partial paths from
+ * non-dummy children to create partial append paths.
+ */
+static void
+add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
+						List *live_childrels, bool partition_join_path)
+{
+	List	   *subpaths = NIL;
+	bool		subpaths_valid = true;
+	List	   *partial_subpaths = NIL;
+	bool		partial_subpaths_valid;
+	List	   *all_child_pathkeys = NIL;
+	List	   *all_child_outers = NIL;
+	ListCell   *l;
+
+	/*
+	 * While creating PartitionJoinPath, we sample paths from only a few child
+	 * relations. Even if all of sampled children have partial paths, it's not
+	 * guaranteed that all the unsampled children will have partial paths.
+	 * Hence we do not create partial PartitionJoinPaths.
+	 */
+	partial_subpaths_valid = !partition_join_path ? true : false;
+
+	/* An append relation with all its children dummy is dummy. */
+	if (live_childrels == NIL)
+	{
+		/* Mark the relation as dummy, if not already done so. */
+		if (!IS_DUMMY_REL(rel))
+			set_dummy_rel_pathlist(rel);
+
+		/* No more paths need to be added. */
+		return;
+	}
+
+	/*
+	 * For every non-dummy child, remember the cheapest path.  Also, identify
+	 * all pathkeys (orderings) and parameterizations (required_outer sets)
+	 * available for the non-dummy member relations.
+	 */
+	foreach (l, live_childrels)
+	{
+		RelOptInfo *childrel = lfirst(l);
+		ListCell   *lcp;
 
 		/*
 		 * If child has an unparameterized cheapest-total path, add that to
@@ -1267,7 +1425,17 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 	 * if we have zero or one live subpath due to constraint exclusion.)
 	 */
 	if (subpaths_valid)
-		add_path(rel, (Path *) create_append_path(rel, subpaths, NULL, 0));
+	{
+		Path *path;
+
+		if (partition_join_path)
+			path = (Path *) create_partition_join_path(rel, subpaths,
+													   NULL);
+		else
+			path = (Path *) create_append_path(rel, subpaths, NULL, 0);
+
+		add_path(rel, path);
+	}
 
 	/*
 	 * Consider an append of partial unordered, unparameterized partial paths.
@@ -1278,6 +1446,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 		ListCell   *lc;
 		int			parallel_workers = 0;
 
+		Assert(!partition_join_path);
+
 		/*
 		 * Decide on the number of workers to request for this append path.
 		 * For now, we just use the maximum value from among the members.  It
@@ -1304,7 +1474,7 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 	 */
 	if (subpaths_valid)
 		generate_mergeappend_paths(root, rel, live_childrels,
-								   all_child_pathkeys);
+								   all_child_pathkeys, partition_join_path);
 
 	/*
 	 * Build Append paths for each parameterization seen among the child rels.
@@ -1345,8 +1515,16 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 		}
 
 		if (subpaths_valid)
-			add_path(rel, (Path *)
-					 create_append_path(rel, subpaths, required_outer, 0));
+		{
+			Path *path;
+
+			if (partition_join_path)
+				path = (Path *) create_partition_join_path(rel, subpaths, required_outer);
+			else
+				path = (Path *) create_append_path(rel, subpaths, required_outer, 0);
+
+			add_path(rel, path);
+		}
 	}
 }
 
@@ -1376,7 +1554,7 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 static void
 generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel,
 						   List *live_childrels,
-						   List *all_child_pathkeys)
+						   List *all_child_pathkeys, bool partition_join_path)
 {
 	ListCell   *lcp;
 
@@ -1387,6 +1565,7 @@ generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel,
 		List	   *total_subpaths = NIL;
 		bool		startup_neq_total = false;
 		ListCell   *lcr;
+		Path	   *path;
 
 		/* Select the child paths for this ordering... */
 		foreach(lcr, live_childrels)
@@ -1434,17 +1613,29 @@ generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel,
 		}
 
 		/* ... and build the MergeAppend paths */
-		add_path(rel, (Path *) create_merge_append_path(root,
-														rel,
-														startup_subpaths,
-														pathkeys,
-														NULL));
+		if (partition_join_path)
+			path = (Path *) create_partition_join_path_with_pathkeys(root, rel,
+															 startup_subpaths,
+															 pathkeys, NULL);
+		else
+			path = (Path *) create_merge_append_path(root, rel,
+													 startup_subpaths,
+													 pathkeys, NULL);
+		add_path(rel, path);
+
 		if (startup_neq_total)
-			add_path(rel, (Path *) create_merge_append_path(root,
-															rel,
-															total_subpaths,
-															pathkeys,
-															NULL));
+		{
+			if (partition_join_path)
+				path = (Path *) create_partition_join_path_with_pathkeys(root,
+																rel,
+																total_subpaths,
+																pathkeys, NULL);
+			else
+				path = (Path *) create_merge_append_path(root, rel,
+														 total_subpaths,
+														 pathkeys, NULL);
+			add_path(rel, path);
+		}
 	}
 }
 
@@ -2186,15 +2377,27 @@ standard_join_search(PlannerInfo *root, int levels_needed, List *initial_rels)
 
 		/*
 		 * Run generate_gather_paths() for each just-processed joinrel.  We
-		 * could not do this earlier because both regular and partial paths
-		 * can get added to a particular joinrel at multiple times within
-		 * join_search_one_level.  After that, we're done creating paths for
-		 * the joinrel, so run set_cheapest().
+		 * could not do this earlier because both regular and partial paths can
+		 * get added to a particular joinrel at multiple times within
+		 * join_search_one_level.
+		 *
+		 * Similarly, create paths for joinrels which used partition-wise join
+		 * technique. generate_partition_wise_join_paths() creates paths for
+		 * only few of the child-joins with highest sizes. Though we calculate
+		 * size of a child-join only once; when it gets created, it may be
+		 * deemed empty while considering various join orders within
+		 * join_search_one_level.
+		 *
+		 * After that, we're done creating paths for the joinrel, so run
+		 * set_cheapest().
 		 */
 		foreach(lc, root->join_rel_level[lev])
 		{
 			rel = (RelOptInfo *) lfirst(lc);
 
+			/* Create paths for partition-wise joins. */
+			generate_partition_wise_join_paths(root, rel);
+
 			/* Create GatherPaths for any useful partial paths for rel */
 			generate_gather_paths(root, rel);
 
@@ -2866,6 +3069,151 @@ remove_unused_subquery_outputs(Query *subquery, RelOptInfo *rel)
 	}
 }
 
+/* Fraction of child relations to base cost on. */
+#define FRACTION_PARTS_TO_PLAN 0.01
+
+/*
+ * generate_partition_wise_join_paths
+ *
+ * 		Create paths representing partition-wise join for given partitioned
+ * 		join relation.
+ *
+ * The number of paths created for a child-join is same as the number of paths
+ * created for join between parents. That number grows exponentially with the
+ * number of base relations being joined. The time and memory consumed to
+ * create paths for each child-join will be proporional to the number of
+ * partitions. This will not scale well with thousands of partitions. Instead
+ * of that we estimate partition-wise join cost based on the costs of sampled
+ * child-joins. We choose child-joins with higher sizes to have realistic
+ * estimates.
+ *
+ * This must be called after we have considered all joining orders since
+ * certain join orders may allow us to deem a child-join as dummy.
+ */
+void
+generate_partition_wise_join_paths(PlannerInfo *root, RelOptInfo *rel)
+{
+	List   *sampled_children = NIL;
+	List   *ordered_child_nos = NIL;
+	int		cnt_part;
+	int		num_part_to_plan;
+	int		num_parts;
+	bool	partition_join_path = false;
+	int		num_dummy_parts = 0;
+	ListCell   *lc;
+
+	/* Handle only join relations. */
+	if (!IS_JOIN_REL(rel))
+		return;
+
+	/*
+	 * If none of the join orders for this relation could use partition-wise
+	 * join technique, the join is not partitioned. Reset the partitioning
+	 * scheme.
+	 */
+	if (!rel->part_rels)
+		rel->part_scheme = NULL;
+
+	/* If the relation is not partitioned or is proven dummy, nothing to do. */
+	if (!rel->part_scheme || IS_DUMMY_REL(rel))
+		return;
+
+	/* Guard against stack overflow due to overly deep partition hierarchy. */
+	check_stack_depth();
+
+	num_parts = rel->part_scheme->nparts;
+
+	/* Calculate number of child-joins to sample. */
+	num_part_to_plan = num_parts * FRACTION_PARTS_TO_PLAN;
+	if (num_part_to_plan < 1)
+		num_part_to_plan = 1;
+
+	/* Order the child-join relations by their size. */
+	for (cnt_part = 0; cnt_part < num_parts; cnt_part++)
+	{
+		RelOptInfo *child_rel = rel->part_rels[cnt_part];
+		ListCell   *insert_after;
+
+		insert_after = NULL;
+
+		/* Dummy children will not be scanned, so ingore those. */
+		if (IS_DUMMY_REL(child_rel))
+		{
+			num_dummy_parts++;
+			continue;
+		}
+
+		/*
+		 * Add this relation to the list of samples ordered by the increasing
+		 * number of rows at appropriate place.
+		 */
+		foreach (lc, ordered_child_nos)
+		{
+			int	child_no = lfirst_int(lc);
+			RelOptInfo *other_childrel = rel->part_rels[child_no];
+
+			/*
+			 * Keep track of child with lowest number of rows but higher than the
+			 * that of the child being inserted. Insert the child before a
+			 * child with highest number of rows lesser than it.
+			 */
+			if (child_rel->rows <= other_childrel->rows)
+				insert_after = lc;
+			else
+				break;
+		}
+
+		if (insert_after)
+			lappend_cell_int(ordered_child_nos, insert_after, cnt_part);
+		else
+			ordered_child_nos = lcons_int(cnt_part, ordered_child_nos);
+	}
+
+	/*
+	 * Create paths for the child-joins as they appear in the list ordered by
+	 * their size. Stop when we have created paths for required number of
+	 * child-joins.
+	 */
+	foreach (lc, ordered_child_nos)
+	{
+		int	child_no = lfirst_int(lc);
+		RelOptInfo *child_rel = rel->part_rels[child_no];
+
+		/* Create paths for this child. */
+		add_paths_to_child_joinrel(root, rel, child_no);
+
+		/* Dummy children will not be scanned, so ingore those. */
+		if (IS_DUMMY_REL(child_rel))
+		{
+			num_dummy_parts++;
+			continue;
+		}
+
+#ifdef OPTIMIZER_DEBUG
+		debug_print_rel(root, rel);
+#endif
+
+		sampled_children = lappend(sampled_children, child_rel);
+
+		if (list_length(sampled_children) >= num_part_to_plan)
+			break;
+	}
+
+	/*
+	 * If the number of samples is same as the number of live children, an
+	 * append path will do. Otherwise, we will cost the partition-wise join
+	 * based on the sampled children using PartitionJoinPath.
+	 */
+	if (num_part_to_plan < num_parts - num_dummy_parts)
+		partition_join_path = true;
+
+	/* Add paths for partition-wise join based on the sampled children. */
+	add_paths_to_append_rel(root, rel, sampled_children, partition_join_path);
+
+	if (sampled_children)
+		list_free(sampled_children);
+}
+
 /*****************************************************************************
  *			DEBUG SUPPORT
  *****************************************************************************/
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 415edad..ba2b238 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -126,6 +126,8 @@ bool		enable_nestloop = true;
 bool		enable_material = true;
 bool		enable_mergejoin = true;
 bool		enable_hashjoin = true;
+bool		enable_partition_wise_join = true;
+double		partition_wise_plan_weight = DEFAULT_PARTITION_WISE_PLAN_WEIGHT;
 
 typedef struct
 {
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 0e50ad5..b1ea2ba 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -972,6 +972,10 @@ generate_base_implied_equalities_broken(PlannerInfo *root,
  * appropriate clauses using child EC members.  add_child_rel_equivalences
  * must already have been done for the child rel.
  *
+ * For a join between child relations, joinrelids, outer_relids and
+ * inner_rel all point to child relations. In this case, we need to find the
+ * parent relids to search the applicable equivalence classes.
+ *
  * The results are sufficient for use in merge, hash, and plain nestloop join
  * methods.  We do not worry here about selecting clauses that are optimal
  * for use in a parameterized indexscan.  indxpath.c makes its own selections
@@ -1021,12 +1025,27 @@ generate_join_implied_equalities_for_ecs(PlannerInfo *root,
 	ListCell   *lc;
 
 	/* If inner rel is a child, extra setup work is needed */
-	if (inner_rel->reloptkind == RELOPT_OTHER_MEMBER_REL)
+	if (IS_OTHER_REL(inner_rel))
 	{
+		RelOptInfo *outer_rel;
+		Relids		nominal_outer_relids;
+
+		if (bms_num_members(outer_relids) > 1)
+			outer_rel = find_join_rel(root, outer_relids);
+		else
+			outer_rel = find_base_rel(root, bms_singleton_member(outer_relids));
+
 		/* Fetch relid set for the topmost parent rel */
-		nominal_inner_relids = find_childrel_top_parent(root, inner_rel)->relids;
-		/* ECs will be marked with the parent's relid, not the child's */
-		nominal_join_relids = bms_union(outer_relids, nominal_inner_relids);
+		nominal_inner_relids = find_childrel_top_parent(root, inner_rel);
+
+		/* ECs will be marked with the parent's relid, not the child's. */
+		if (outer_rel && IS_OTHER_REL(outer_rel))
+		{
+			nominal_outer_relids = find_childrel_top_parent(root, outer_rel);
+			nominal_join_relids = bms_union(nominal_outer_relids, nominal_inner_relids);
+		}
+		else
+			nominal_join_relids = bms_union(outer_relids, nominal_inner_relids);
 	}
 	else
 	{
@@ -2062,7 +2081,7 @@ add_child_rel_equivalences(PlannerInfo *root,
 				child_expr = (Expr *)
 					adjust_appendrel_attrs(root,
 										   (Node *) cur_em->em_expr,
-										   appinfo);
+										   list_make1(appinfo));
 
 				/*
 				 * Transform em_relids to match.  Note we do *not* do
@@ -2364,8 +2383,8 @@ eclass_useful_for_merging(PlannerInfo *root,
 	 */
 
 	/* If specified rel is a child, we must consider the topmost parent rel */
-	if (rel->reloptkind == RELOPT_OTHER_MEMBER_REL)
-		relids = find_childrel_top_parent(root, rel)->relids;
+	if (IS_OTHER_REL(rel))
+		relids = find_childrel_top_parent(root, rel);
 	else
 		relids = rel->relids;
 
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 96f00fc..efa4af0 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -25,9 +25,19 @@
 /* Hook for plugins to get control in add_paths_to_joinrel() */
 set_join_pathlist_hook_type set_join_pathlist_hook = NULL;
 
-#define PATH_PARAM_BY_REL(path, rel)  \
+/*
+ * Paths parameterized by the parent can be considered to be parameterized by
+ * any of its child.
+ */
+#define PATH_PARAM_BY_PARENT(path, rel)	\
+	((path)->param_info && bms_overlap(PATH_REQ_OUTER(path),	\
+									   (rel)->top_parent_relids))
+#define PATH_PARAM_BY_REL_SELF(path, rel)  \
 	((path)->param_info && bms_overlap(PATH_REQ_OUTER(path), (rel)->relids))
 
+#define PATH_PARAM_BY_REL(path, rel)	\
+	(PATH_PARAM_BY_REL_SELF(path, rel) || PATH_PARAM_BY_PARENT(path, rel))
+
 static void sort_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
 					 RelOptInfo *outerrel, RelOptInfo *innerrel,
 					 JoinType jointype, JoinPathExtraData *extra);
@@ -132,6 +142,19 @@ add_paths_to_joinrel(PlannerInfo *root,
 	foreach(lc, root->join_info_list)
 	{
 		SpecialJoinInfo *sjinfo2 = (SpecialJoinInfo *) lfirst(lc);
+		Relids	joinrelids;
+
+		/*
+		 * PlannerInfo doesn't contain the SpecialJoinInfos created for joins
+		 * between child relations, even if there is a SpecialJoinInfo node for
+		 * the join between the topmost parents. Hence while calculating Relids
+		 * set representing the restriction, consider relids of topmost parent
+		 * of partitions.
+		 */
+		if (joinrel->reloptkind == RELOPT_OTHER_JOINREL)
+			joinrelids = joinrel->top_parent_relids;
+		else
+			joinrelids = joinrel->relids;
 
 		/*
 		 * SJ is relevant to this join if we have some part of its RHS
@@ -140,16 +163,16 @@ add_paths_to_joinrel(PlannerInfo *root,
 		 * join has already been proven legal.)  If the SJ is relevant, it
 		 * presents constraints for joining to anything not in its RHS.
 		 */
-		if (bms_overlap(joinrel->relids, sjinfo2->min_righthand) &&
-			!bms_overlap(joinrel->relids, sjinfo2->min_lefthand))
+		if (bms_overlap(joinrelids, sjinfo2->min_righthand) &&
+			!bms_overlap(joinrelids, sjinfo2->min_lefthand))
 			extra.param_source_rels = bms_join(extra.param_source_rels,
 										   bms_difference(root->all_baserels,
 													sjinfo2->min_righthand));
 
 		/* full joins constrain both sides symmetrically */
 		if (sjinfo2->jointype == JOIN_FULL &&
-			bms_overlap(joinrel->relids, sjinfo2->min_lefthand) &&
-			!bms_overlap(joinrel->relids, sjinfo2->min_righthand))
+			bms_overlap(joinrelids, sjinfo2->min_lefthand) &&
+			!bms_overlap(joinrelids, sjinfo2->min_righthand))
 			extra.param_source_rels = bms_join(extra.param_source_rels,
 										   bms_difference(root->all_baserels,
 													 sjinfo2->min_lefthand));
@@ -279,6 +302,22 @@ try_nestloop_path(PlannerInfo *root,
 	JoinCostWorkspace workspace;
 
 	/*
+	 * For a join between child relations, if the inner path is parameterized
+	 * by the parent of the outer relation, create a nestloop join path with
+	 * inner relation parameterized by the outer relation by translating the
+	 * inner path to be parameterized by the outer child relation.
+	 */
+	if (PATH_PARAM_BY_PARENT(inner_path, outer_path->parent))
+	{
+		inner_path = reparameterize_path_by_child(root, inner_path,
+												   outer_path->parent);
+
+		/* If we could not translate the path, don't produce nest loop path. */
+		if (!inner_path)
+			return;
+	}
+
+	/*
 	 * Check to see if proposed path is still parameterized, and reject if the
 	 * parameterization wouldn't be sensible --- unless allow_star_schema_join
 	 * says to allow it anyway.  Also, we must reject if have_dangerous_phv
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 01d4fea..47ff915 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -14,9 +14,14 @@
  */
 #include "postgres.h"
 
+#include "miscadmin.h"
+#include "catalog/partition.h"
+#include "optimizer/clauses.h"
 #include "optimizer/joininfo.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
+#include "optimizer/prep.h"
+#include "optimizer/cost.h"
 #include "utils/memutils.h"
 
 
@@ -32,7 +37,19 @@ static bool is_dummy_rel(RelOptInfo *rel);
 static void mark_dummy_rel(RelOptInfo *rel);
 static bool restriction_is_constant_false(List *restrictlist,
 							  bool only_pushed_down);
-
+static void populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
+							RelOptInfo *rel2, RelOptInfo *joinrel,
+							SpecialJoinInfo *sjinfo, List *restrictlist);
+static void try_partition_wise_join(PlannerInfo *root, RelOptInfo *rel1,
+						  RelOptInfo *rel2, RelOptInfo *joinrel,
+						  SpecialJoinInfo *parent_sjinfo,
+						  List *parent_restrictlist);
+static SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root,
+									SpecialJoinInfo *parent_sjinfo,
+									Relids left_relids, Relids right_relids);
+static bool have_partkey_equi_join(RelOptInfo *rel1, RelOptInfo *rel2,
+						   JoinType jointype, List *restrictlist);
+static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel);
 
 /*
  * join_search_one_level
@@ -724,6 +741,31 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
 		return joinrel;
 	}
 
+	/* Add paths to the join relation. */
+	populate_joinrel_with_paths(root, rel1, rel2, joinrel, sjinfo,
+								restrictlist);
+
+	/* Apply partition-wise join technique, if possible. */
+	try_partition_wise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+	bms_free(joinrelids);
+
+	return joinrel;
+}
+
+/*
+ * populate_joinrel_with_paths
+ *	  Add paths to the given joinrel for given pair of joining relations. The
+ *	  SpecialJoinInfo provides details about the join and the restrictlist
+ *	  contains the join clauses and the other clauses applicable for given pair
+ *	  of the joining relations.
+ */
+
+static void
+populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
+							RelOptInfo *rel2, RelOptInfo *joinrel,
+							SpecialJoinInfo *sjinfo, List *restrictlist)
+{
 	/*
 	 * Consider paths using each rel as both outer and inner.  Depending on
 	 * the join type, a provably empty outer or inner rel might mean the join
@@ -868,13 +910,8 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
 			elog(ERROR, "unrecognized join type: %d", (int) sjinfo->jointype);
 			break;
 	}
-
-	bms_free(joinrelids);
-
-	return joinrel;
 }
 
-
 /*
  * have_join_order_restriction
  *		Detect whether the two relations should be joined to satisfy
@@ -1249,3 +1286,477 @@ restriction_is_constant_false(List *restrictlist, bool only_pushed_down)
 	}
 	return false;
 }
+
+/* Free SpecialJoinInfo. */
+static void
+free_special_join_info(SpecialJoinInfo *sjinfo)
+{
+	bms_free(sjinfo->min_lefthand);
+	bms_free(sjinfo->syn_lefthand);
+	bms_free(sjinfo->syn_righthand);
+	pfree(sjinfo);
+}
+
+/*
+ * Assess whether join between given two partitioned relations can be broken
+ * down into joins between matching partitions; a technique called
+ * "partition-wise join"
+ *
+ * Partition-wise join is possible when a. Joining relations have same
+ * partitioning scheme b. There exists an equi-join between the partition keys
+ * of the two relations.
+ *
+ * Partition-wise join is planned as follows (details: optimizer/README.)
+ *
+ * 1. Create the RelOptInfos for joins between matching partitions i.e
+ * child-joins and estimate sizes of those. This function is responsible for
+ * this phase.
+ *
+ * 2. Add paths representing partition-wise join. The second phase is
+ * implemented by generate_partition_wise_join_paths(). In order to save time
+ * and memory consumed in creating paths for every child-join, we create paths
+ * for only few child-joins.
+ *
+ * 3. Create merge/append plan to combining plans for every child-join,
+ * creating paths for remaining child-joins.
+ *
+ * The RelOptInfo, SpecialJoinInfo and restrictlist for each child join are
+ * obtained by translating the respective parent join structures.
+ */
+static void
+try_partition_wise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
+						RelOptInfo *joinrel, SpecialJoinInfo *parent_sjinfo,
+						List *parent_restrictlist)
+{
+	int		nparts;
+	int		cnt_parts;
+	PartitionScheme		part_scheme;
+	PartitionedJoin	   *partitioned_join;
+
+	/* Guard against stack overflow due to overly deep partition hierarchy. */
+	check_stack_depth();
+
+	/* Nothing to do, if the join relation is not partitioned. */
+	if (!joinrel->part_scheme)
+		return;
+
+	/*
+	 * If any of the joining parent relations is proven empty, either the join
+	 * will be empty (INNER join) or will have the inner side all nullified. We
+	 * take care of such cases when creating join paths for parent relations.
+	 * Nothing to be done here. Also, nothing to do, if the parent join is
+	 * proven empty.
+	 */
+	if (IS_DUMMY_REL(rel1) || IS_DUMMY_REL(rel2) || IS_DUMMY_REL(joinrel))
+		return;
+
+	/*
+	 * Partitioning scheme in join relation indicates a possibilty that the
+	 * join may be partitioned, but it's not necessary that every pair of
+	 * joining relations can use partition-wise join technique. If one of
+	 * joining relations turns out to be unpartitioned, this pair of joining
+	 * relations can not use partition-wise join technique.
+	 */
+	if (!rel1->part_scheme || !rel2->part_scheme)
+		return;
+
+	/*
+	 * If an equi-join condition between the partition keys of the joining
+	 * relations does not exist, this pair of joining relations can not use
+	 * partition-wise technique.
+	 */
+	if (!have_partkey_equi_join(rel1, rel2, parent_sjinfo->jointype,
+								parent_restrictlist))
+		return;
+
+	/*
+	 * The partition scheme of the join relation should match that of the
+	 * joining relations.
+	 */
+	Assert(joinrel->part_scheme == rel1->part_scheme &&
+		   joinrel->part_scheme == rel2->part_scheme);
+
+	/* We should have RelOptInfos of the partitions available. */
+	Assert(rel1->part_rels && rel2->part_rels);
+
+	part_scheme = joinrel->part_scheme;
+	nparts = part_scheme->nparts;
+
+	/*
+	 * We do not store information about valid pairs of joining child
+	 * relations. The pair of joining relations for a child-join can be derived
+	 * from valid pairs of joining parent relations. Amongst the valid pairs of
+	 * parent joining relations, only those which result in partitioned join
+	 * matter for partition-wise join. Remember those so that we can use them
+	 * for creating paths for few child-joins in
+	 * generate_partition_wise_join_paths() later.
+	 */
+	partitioned_join = (PartitionedJoin *) palloc(sizeof(PartitionedJoin));
+	partitioned_join->rel1 = rel1;
+	partitioned_join->rel2 = rel2;
+	partitioned_join->sjinfo = copyObject(parent_sjinfo);
+	joinrel->partitioned_joins = lappend(joinrel->partitioned_joins,
+										 partitioned_join);
+
+	elog(DEBUG3, "join between relations %s and %s is considered for partition-wise join.",
+		 bmsToString(rel1->relids), bmsToString(rel2->relids));
+
+	/* We are done if child RelOptInfos are already created. */
+	if (joinrel->part_rels)
+		return;
+
+	/* Create all the child RelOptInfos. */
+	joinrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * nparts);
+
+	/*
+	 * Create child join relations for this partitioned join. While doing so,
+	 * we estimate sizes of these child join relations. These estimates are
+	 * used to find the representative child relations used for costing the
+	 * partition-wise join later.
+	 */
+	for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++)
+	{
+		RelOptInfo *child_rel1 = rel1->part_rels[cnt_parts];
+		RelOptInfo *child_rel2 = rel2->part_rels[cnt_parts];
+		SpecialJoinInfo	*child_sjinfo;
+		List	*child_restrictlist;
+		RelOptInfo *child_joinrel;
+
+		/* We should never try to join two overlapping sets of rels. */
+		Assert(!bms_overlap(child_rel1->relids, child_rel2->relids));
+
+		Assert (!joinrel->part_rels[cnt_parts]);
+
+		child_joinrel = build_child_join_rel(root, child_rel1, child_rel2,
+											 joinrel, parent_sjinfo->jointype);
+
+		joinrel->part_rels[cnt_parts] = child_joinrel;
+
+		/*
+		 * Construct restrictions applicable to the child join from
+		 * those applicable to the parent join.
+		 */
+		child_restrictlist = build_child_joinrel_restrictlist(root,
+															  child_joinrel,
+															  child_rel1,
+															  child_rel2);
+
+		/*
+		 * Construct SpecialJoinInfo from parent join relations's
+		 * SpecialJoinInfo.
+		 */
+		child_sjinfo = build_child_join_sjinfo(root, parent_sjinfo,
+											   child_rel1->relids,
+											   child_rel2->relids);
+
+		/*
+		 * Set estimates of the child-joinrel's size.
+		 */
+		set_joinrel_size_estimates(root, child_joinrel, child_rel1, child_rel2,
+								   child_sjinfo, child_restrictlist);
+
+		/*
+		 * If the child relations themselves are partitioned, try partition-wise join
+		 * recursively.
+		 */
+		try_partition_wise_join(root, child_rel1, child_rel2, child_joinrel,
+								child_sjinfo, child_restrictlist);
+
+		free_special_join_info(child_sjinfo);
+		child_sjinfo = NULL;
+	}
+}
+
+/*
+ * add_paths_to_child_join
+ * 		Add paths to 'child_id'th child of given parent join relation.
+ *
+ * The function creates paths for given child-join by joining corresponding
+ * children of every pair of joining parent relations which produces
+ * partitioned join. Since we create paths only for sampled child-joins, either
+ * of the children being joined may not have paths. In that case, this function
+ * is called recursively to populate paths for those.
+ */
+void
+add_paths_to_child_joinrel(PlannerInfo *root, RelOptInfo *parent_joinrel,
+						   int child_id)
+{
+	ListCell	*lc;
+	RelOptInfo	   *child_joinrel = parent_joinrel->part_rels[child_id];
+
+	Assert(IS_JOIN_REL(parent_joinrel));
+
+	/* If this child relation already has paths, nothing to do. */
+	if (child_joinrel->cheapest_total_path)
+		return;
+
+	/* A dummy relation will have a dummy path as the cheapest path. */
+	Assert(!is_dummy_rel(child_joinrel));
+
+	/*
+	 * For every partitioned join order, calculate paths for the joining
+	 * child relations and then calculate paths for given child.
+	 */
+	foreach (lc, parent_joinrel->partitioned_joins)
+	{
+		PartitionedJoin	   *pj = lfirst(lc);
+		RelOptInfo *rel1 = pj->rel1;
+		RelOptInfo *rel2 = pj->rel2;
+		RelOptInfo *child_rel1 = rel1->part_rels[child_id];
+		RelOptInfo *child_rel2 = rel2->part_rels[child_id];
+		SpecialJoinInfo	   *child_sjinfo;
+		List	   *child_restrictlist;
+
+		/*
+		 * Add paths to joining relation if it is a join itself.
+		 * Paths for child base relations are created in
+		 * set_append_rel_pathlist().
+		 */
+		if (IS_JOIN_REL(pj->rel1))
+			add_paths_to_child_joinrel(root, rel1, child_id);
+
+		if (IS_JOIN_REL(pj->rel2))
+			add_paths_to_child_joinrel(root, rel2, child_id);
+
+		/*
+		 * Construct SpecialJoinInfo from parent join relations's
+		 * SpecialJoinInfo.
+		 */
+		child_sjinfo = build_child_join_sjinfo(root, pj->sjinfo,
+											   child_rel1->relids,
+											   child_rel2->relids);
+
+
+		/*
+		 * Construct restrictions applicable to the child join from
+		 * those applicable to the parent join.
+		 */
+		child_restrictlist = build_child_joinrel_restrictlist(root,
+															  child_joinrel,
+															  child_rel1,
+															  child_rel2);
+
+		/* Add paths for child join. */
+		populate_joinrel_with_paths(root, rel1->part_rels[child_id],
+									rel2->part_rels[child_id], child_joinrel,
+									child_sjinfo, child_restrictlist);
+
+		/* Add partition-wise join paths for partitioned child-joins. */
+		generate_partition_wise_join_paths(root, child_joinrel);
+
+		free_special_join_info(child_sjinfo);
+		child_sjinfo = NULL;
+	}
+
+	set_cheapest(child_joinrel);
+}
+
+/*
+ * Construct the SpecialJoinInfo for a child-join by translating
+ * SpecialJoinInfo for the join between parents. left_relids and right_relids
+ * are the relids of left and right side of the join respectively.
+ */
+static SpecialJoinInfo *
+build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo,
+						Relids left_relids, Relids right_relids)
+{
+	SpecialJoinInfo *sjinfo = makeNode(SpecialJoinInfo);
+	MemoryContext	old_context;
+	List	   *left_appinfos = find_appinfos_by_relids(root, left_relids);
+	List	   *right_appinfos = find_appinfos_by_relids(root, right_relids);
+
+	memcpy(sjinfo, parent_sjinfo, sizeof(SpecialJoinInfo));
+
+	sjinfo->min_lefthand = adjust_child_relids(sjinfo->min_lefthand,
+											   left_appinfos);
+	sjinfo->min_righthand = adjust_child_relids(sjinfo->min_righthand,
+												right_appinfos);
+	sjinfo->syn_lefthand = adjust_child_relids(sjinfo->syn_lefthand,
+											   left_appinfos);
+	sjinfo->syn_righthand = adjust_child_relids(sjinfo->syn_righthand,
+												right_appinfos);
+
+	/*
+	 * Replace the Var nodes of parent with those of children in expressions.
+	 * This function may be called within a temporary context, but the
+	 * expressions will be shallow-copied into the plan. Hence copy those in
+	 * the planner's context.
+	 */
+	old_context = MemoryContextSwitchTo(root->planner_cxt);
+	sjinfo->semi_rhs_exprs = (List *) adjust_appendrel_attrs(root,
+											   (Node *) sjinfo->semi_rhs_exprs,
+															 right_appinfos);
+	MemoryContextSwitchTo(old_context);
+
+	list_free(left_appinfos);
+	list_free(right_appinfos);
+
+	return sjinfo;
+}
+
+/*
+ * Replace parent relids by child relids in the copy of given relid set.
+ */
+Relids
+adjust_child_relids(Relids relids, List *append_rel_infos)
+{
+	ListCell   *lc;
+
+	/* Ensure we have a modifiable copy. */
+	relids = bms_copy(relids);
+	foreach (lc, append_rel_infos)
+	{
+		AppendRelInfo	*appinfo = lfirst(lc);
+
+		/* Remove parent, add child */
+		if (bms_is_member(appinfo->parent_relid, relids))
+		{
+			relids = bms_del_member(relids, appinfo->parent_relid);
+			relids = bms_add_member(relids, appinfo->child_relid);
+		}
+	}
+
+	return relids;
+}
+
+/*
+ * Returns true if there exists an equi-join condition for each pair of
+ * partition key from given relations being joined.
+ */
+static bool
+have_partkey_equi_join(RelOptInfo *rel1, RelOptInfo *rel2,
+					 JoinType jointype, List *restrictlist)
+{
+	PartitionScheme	part_scheme = rel1->part_scheme;
+	ListCell	*lc;
+	int		cnt_pks;
+	int		num_pks;
+	bool   *pk_has_clause;
+
+	/*
+	 * This function should be called when the joining relations have same
+	 * partitioning scheme.
+	 */
+	Assert(rel1->part_scheme == rel2->part_scheme);
+	Assert(part_scheme);
+
+	num_pks = part_scheme->partnatts;
+
+	pk_has_clause = (bool *) palloc0(sizeof(bool) * num_pks);
+
+	foreach (lc, restrictlist)
+	{
+		RestrictInfo *rinfo = lfirst(lc);
+		OpExpr		 *opexpr;
+		Expr		 *expr1;
+		Expr		 *expr2;
+		int		ipk1;
+		int		ipk2;
+
+		/* If processing an outer join, only use its own join clauses. */
+		if (IS_OUTER_JOIN(jointype) && rinfo->is_pushed_down)
+			continue;
+
+		/* Skip clauses which can not be used for a join. */
+		if (!rinfo->can_join)
+			continue;
+
+		/* Skip clauses which are not equality conditions. */
+		if (rinfo->hashjoinoperator == InvalidOid && !rinfo->mergeopfamilies)
+			continue;
+
+		opexpr = (OpExpr *) rinfo->clause;
+		Assert(is_opclause(opexpr));
+
+
+		/* Match the operands to the relation. */
+		if (bms_is_subset(rinfo->left_relids, rel1->relids) &&
+			bms_is_subset(rinfo->right_relids, rel2->relids))
+		{
+			expr1 = linitial(opexpr->args);
+			expr2 = lsecond(opexpr->args);
+		}
+		else if (bms_is_subset(rinfo->left_relids, rel2->relids) &&
+				 bms_is_subset(rinfo->right_relids, rel1->relids))
+		{
+			expr1 = lsecond(opexpr->args);
+			expr2 = linitial(opexpr->args);
+		}
+		else
+			continue;
+
+		/* Associate matching clauses with partition keys. */
+		ipk1 = match_expr_to_partition_keys(expr1, rel1);
+		ipk2 = match_expr_to_partition_keys(expr2, rel2);
+
+		/*
+		 * If the clause refers to different partition keys from
+		 * both relations, it can not be used for partition-wise join.
+		 */
+		if (ipk1 != ipk2)
+			continue;
+
+		/*
+		 * The clause allows partition-wise join if only it uses the same
+		 * operator family as that specified by the partition key.
+		 */
+		if (!list_member_oid(rinfo->mergeopfamilies,
+							 part_scheme->partopfamily[ipk1]))
+			continue;
+
+		/* Mark the partition key as having an equi-join clause. */
+		pk_has_clause[ipk1] = true;
+	}
+
+	/* Check whether every partition key has an equi-join condition. */
+	for (cnt_pks = 0; cnt_pks < num_pks; cnt_pks++)
+	{
+		if (!pk_has_clause[cnt_pks])
+		{
+			pfree(pk_has_clause);
+			return false;
+		}
+	}
+
+	pfree(pk_has_clause);
+	return true;
+}
+
+/*
+ * Find the partition key from the given relation matching the given
+ * expression. If found, return the index of the partition key, else return -1.
+ */
+static int
+match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel)
+{
+	int		cnt_pks;
+	int		num_pks;
+
+	/* This function should be called only for partitioned relations. */
+	Assert(rel->part_scheme);
+
+	num_pks = rel->part_scheme->partnatts;
+
+	/*
+	 * Remove the relabel decoration. We can assume that there is at most one
+	 * RelabelType node; eval_const_expressions() simplifies multiple
+	 * RelabelType nodes into one.
+	 */
+	if (IsA(expr, RelabelType))
+		expr = (Expr *) ((RelabelType *) expr)->arg;
+
+	for (cnt_pks = 0; cnt_pks < num_pks; cnt_pks++)
+	{
+		List	 *pkexprs = rel->partexprs[cnt_pks];
+		ListCell *lc;
+
+		foreach(lc, pkexprs)
+		{
+			Expr *pkexpr = lfirst(lc);
+			if (equal(pkexpr, expr))
+				return cnt_pks;
+		}
+	}
+
+	return -1;
+}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index 4436ac1..def64e3 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -1088,12 +1088,25 @@ select_outer_pathkeys_for_merge(PlannerInfo *root,
 	int			necs;
 	ListCell   *lc;
 	int			j;
+	Relids		relids;
 
 	/* Might have no mergeclauses */
 	if (nClauses == 0)
 		return NIL;
 
 	/*
+	 * For a child join relation, use parent relids to find potential join
+	 * partners (see code below) from equivalence classes. A potential join
+	 * partner of parent also indicates potential join partner of the child. By
+	 * using only parent relids, we avoid scoring an equivalence class multiple
+	 * times once for parent and then for all of its children.
+	 */
+	if (joinrel->reloptkind == RELOPT_OTHER_JOINREL)
+		relids = joinrel->top_parent_relids;
+	else
+		relids = joinrel->relids;
+
+	/*
 	 * Make arrays of the ECs used by the mergeclauses (dropping any
 	 * duplicates) and their "popularity" scores.
 	 */
@@ -1133,7 +1146,7 @@ select_outer_pathkeys_for_merge(PlannerInfo *root,
 
 			/* Potential future join partner? */
 			if (!em->em_is_const && !em->em_is_child &&
-				!bms_overlap(em->em_relids, joinrel->relids))
+				!bms_overlap(em->em_relids, relids))
 				score++;
 		}
 
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index ad49674..b754d90 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -30,6 +30,7 @@
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/paths.h"
+#include "optimizer/pathnode.h"
 #include "optimizer/placeholder.h"
 #include "optimizer/plancat.h"
 #include "optimizer/planmain.h"
@@ -42,6 +43,7 @@
 #include "parser/parse_clause.h"
 #include "parser/parsetree.h"
 #include "utils/lsyscache.h"
+#include "utils/memutils.h"
 
 
 /*
@@ -145,6 +147,9 @@ static CustomScan *create_customscan_plan(PlannerInfo *root,
 static NestLoop *create_nestloop_plan(PlannerInfo *root, NestPath *best_path);
 static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path);
 static HashJoin *create_hashjoin_plan(PlannerInfo *root, HashPath *best_path);
+static Plan *create_partition_plan(PlannerInfo *root, Path *best_path);
+static Plan *create_partition_join_plan(PlannerInfo *root,
+										PartitionJoinPath *best_path);
 static Node *replace_nestloop_params(PlannerInfo *root, Node *expr);
 static Node *replace_nestloop_params_mutator(Node *node, PlannerInfo *root);
 static void process_subquery_nestloop_params(PlannerInfo *root,
@@ -241,7 +246,8 @@ static Plan *prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
 static EquivalenceMember *find_ec_member_for_tle(EquivalenceClass *ec,
 					   TargetEntry *tle,
 					   Relids relids);
-static Sort *make_sort_from_pathkeys(Plan *lefttree, List *pathkeys);
+static Sort *make_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
+									 Relids relids);
 static Sort *make_sort_from_groupcols(List *groupcls,
 						 AttrNumber *grpColIdx,
 						 Plan *lefttree);
@@ -367,12 +373,8 @@ create_plan_recurse(PlannerInfo *root, Path *best_path, int flags)
 									(JoinPath *) best_path);
 			break;
 		case T_Append:
-			plan = create_append_plan(root,
-									  (AppendPath *) best_path);
-			break;
 		case T_MergeAppend:
-			plan = create_merge_append_plan(root,
-											(MergeAppendPath *) best_path);
+			plan = create_partition_plan(root, best_path);
 			break;
 		case T_Result:
 			if (IsA(best_path, ProjectionPath))
@@ -1115,6 +1117,30 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path)
 }
 
 /*
+ * create_partition_plan
+ *		Creates an Merge/Append plan as specified by the "best path".
+ *
+ *		Returns a Plan node.
+ */
+static Plan *
+create_partition_plan(PlannerInfo *root, Path *best_path)
+{
+	Plan   *plan;
+
+	if (IsA(best_path, PartitionJoinPath))
+		plan = create_partition_join_plan(root, (PartitionJoinPath *)best_path);
+	else if (best_path->pathtype == T_Append)
+		plan = create_append_plan(root, (AppendPath *) best_path);
+	else
+	{
+		Assert(best_path->pathtype == T_MergeAppend);
+		plan = create_merge_append_plan(root, (MergeAppendPath *) best_path);
+	}
+
+	return plan;
+}
+
+/*
  * create_result_plan
  *	  Create a Result plan for 'best_path'.
  *	  This is only used for degenerate cases, such as a query with an empty
@@ -1513,7 +1539,7 @@ create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags)
 	subplan = create_plan_recurse(root, best_path->subpath,
 								  flags | CP_SMALL_TLIST);
 
-	plan = make_sort_from_pathkeys(subplan, best_path->path.pathkeys);
+	plan = make_sort_from_pathkeys(subplan, best_path->path.pathkeys, NULL);
 
 	copy_generic_path_info(&plan->plan, (Path *) best_path);
 
@@ -3530,6 +3556,8 @@ create_mergejoin_plan(PlannerInfo *root,
 	ListCell   *lc;
 	ListCell   *lop;
 	ListCell   *lip;
+	Path	   *outer_path = best_path->jpath.outerjoinpath;
+	Path	   *inner_path = best_path->jpath.innerjoinpath;
 
 	/*
 	 * MergeJoin can project, so we don't have to demand exact tlists from the
@@ -3537,10 +3565,10 @@ create_mergejoin_plan(PlannerInfo *root,
 	 * best to request a small tlist so we aren't sorting more data than
 	 * necessary.
 	 */
-	outer_plan = create_plan_recurse(root, best_path->jpath.outerjoinpath,
+	outer_plan = create_plan_recurse(root, outer_path,
 					 (best_path->outersortkeys != NIL) ? CP_SMALL_TLIST : 0);
 
-	inner_plan = create_plan_recurse(root, best_path->jpath.innerjoinpath,
+	inner_plan = create_plan_recurse(root, inner_path,
 					 (best_path->innersortkeys != NIL) ? CP_SMALL_TLIST : 0);
 
 	/* Sort join qual clauses into best execution order */
@@ -3586,34 +3614,38 @@ create_mergejoin_plan(PlannerInfo *root,
 	 * outer_is_left status.
 	 */
 	mergeclauses = get_switched_clauses(best_path->path_mergeclauses,
-							 best_path->jpath.outerjoinpath->parent->relids);
+							 outer_path->parent->relids);
 
 	/*
 	 * Create explicit sort nodes for the outer and inner paths if necessary.
 	 */
 	if (best_path->outersortkeys)
 	{
+		Relids		outer_relids = outer_path->parent->relids;
 		Sort	   *sort = make_sort_from_pathkeys(outer_plan,
-												   best_path->outersortkeys);
+												   best_path->outersortkeys,
+												   outer_relids);
 
 		label_sort_with_costsize(root, sort, -1.0);
 		outer_plan = (Plan *) sort;
 		outerpathkeys = best_path->outersortkeys;
 	}
 	else
-		outerpathkeys = best_path->jpath.outerjoinpath->pathkeys;
+		outerpathkeys = outer_path->pathkeys;
 
 	if (best_path->innersortkeys)
 	{
+		Relids		inner_relids = inner_path->parent->relids;
 		Sort	   *sort = make_sort_from_pathkeys(inner_plan,
-												   best_path->innersortkeys);
+												   best_path->innersortkeys,
+												   inner_relids);
 
 		label_sort_with_costsize(root, sort, -1.0);
 		inner_plan = (Plan *) sort;
 		innerpathkeys = best_path->innersortkeys;
 	}
 	else
-		innerpathkeys = best_path->jpath.innerjoinpath->pathkeys;
+		innerpathkeys = inner_path->pathkeys;
 
 	/*
 	 * If specified, add a materialize node to shield the inner plan from the
@@ -3951,6 +3983,212 @@ create_hashjoin_plan(PlannerInfo *root,
 	return join_plan;
 }
 
+/*
+ * create_partition_join_plan
+ *		Creates Merge/Append plan consisting of join plans for child-join.
+ *
+ *		Returns a Plan node.
+ */
+static Plan *
+create_partition_join_plan(PlannerInfo *root, PartitionJoinPath *best_path)
+{
+	RelOptInfo *joinrel = best_path->path.parent;
+	int		nparts;
+	int		cnt_parts;
+	List	   *child_plans = NIL;
+	List	   *tlist = build_path_tlist(root, &best_path->path);
+	Plan	   *plan;
+	MemoryContext	child_context;
+	MemoryContext	old_context;
+	List	   *pathkeys = best_path->path.pathkeys;
+	StringInfoData	mem_context_name;
+
+	/* The relation should be a partitioned join relation. */
+	Assert(IS_JOIN_REL(joinrel) && joinrel->part_scheme &&
+		   joinrel->partitioned_joins);
+
+	nparts = joinrel->part_scheme->nparts;
+
+	/* Create MergeAppend plan when result is expected to be ordered. */
+	if (pathkeys)
+	{
+		MergeAppend *node = makeNode(MergeAppend);
+		plan = &node->plan;
+
+		plan->targetlist = tlist;
+
+		/* Compute sorting info, and adjust MergeAppend's tlist as needed. */
+		(void) prepare_sort_from_pathkeys(plan, pathkeys,
+										  best_path->path.parent->relids,
+										  NULL,
+										  true,
+										  &node->numCols,
+										  &node->sortColIdx,
+										  &node->sortOperators,
+										  &node->collations,
+										  &node->nullsFirst);
+	}
+	else
+	{
+		Append  *node = makeNode(Append);
+		plan = &node->plan;
+		plan->targetlist = tlist;
+	}
+
+	/* Fill costs, so that we can cost Sort node, if required. */
+	copy_generic_path_info(plan, (Path *) best_path);
+
+	/*
+	 * Create a new memory context for planning child joins. Since this routine
+	 * may be called recursively for tables with subpartitions, we use
+	 * a unique context name for every level of partition by using the lowest
+	 * relid amongst the base relations being joined.
+	 */
+	initStringInfo(&mem_context_name);
+	appendStringInfo(&mem_context_name, "%s_%d", "ChildJoinContext",
+					 bms_next_member(joinrel->relids, -1));
+	child_context = AllocSetContextCreate(CurrentMemoryContext,
+										  pstrdup(mem_context_name.data),
+										  ALLOCSET_DEFAULT_SIZES);
+	pfree(mem_context_name.data);
+	resetStringInfo(&mem_context_name);
+
+	/*
+	 * Create a paths for all child joins, one child join at a time. The paths
+	 * for every child join are independent i.e. one child does not require
+	 * paths created for the other. In order to avoid accumulating memory
+	 * consumed while creating paths for every child join, we use a fresh
+	 * memory context for every child join.
+	 */
+	for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++)
+	{
+		RelOptInfo *child_join;
+		Path	   *child_path;
+		Plan	   *child_plan;
+		int			numsortkeys;
+		AttrNumber *sortColIdx;
+		Oid		   *sortOperators;
+		Oid		   *collations;
+		bool	   *nullsFirst;
+
+		/*
+		 * Create paths for the child join in a separate context, so that we
+		 * can reuse the memory used by those paths.
+		 */
+		old_context = MemoryContextSwitchTo(child_context);
+
+		add_paths_to_child_joinrel(root, joinrel, cnt_parts);
+
+		MemoryContextSwitchTo(old_context);
+
+		child_join = joinrel->part_rels[cnt_parts];
+
+
+		/* Skip empty child. */
+		if (IS_DUMMY_REL(child_join))
+			continue;
+
+#ifdef OPTIMIZER_DEBUG
+		debug_print_rel(root, rel);
+#endif
+
+		/*
+		 * Search for a child path with pathkeys or parameterization
+		 * matching that of the given path.
+		 */
+		child_path = get_cheapest_path_for_pathkeys(child_join->pathlist,
+													best_path->path.pathkeys,
+											 PATH_REQ_OUTER(&best_path->path),
+													TOTAL_COST);
+
+		if (!child_path)
+			elog(ERROR, "Could not find a path with required pathkeys.");
+
+		/* Create plan for the current child. */
+		child_plan = create_plan_recurse(root, child_path, CP_EXACT_TLIST);
+
+		if (pathkeys)
+		{
+			MergeAppend *node = (MergeAppend *) plan;
+
+			Assert(IsA(node, MergeAppend));
+
+			/* Compute sorting info, and adjust subplan's tlist as needed */
+			child_plan = prepare_sort_from_pathkeys(child_plan, pathkeys,
+												 child_path->parent->relids,
+												 node->sortColIdx,
+												 false,
+												 &numsortkeys,
+												 &sortColIdx,
+												 &sortOperators,
+												 &collations,
+												 &nullsFirst);
+
+			/*
+			 * Check that we got the same sort key information.  We just Assert
+			 * that the sortops match, since those depend only on the pathkeys;
+			 * but it seems like a good idea to check the sort column numbers
+			 * explicitly, to ensure the tlists really do match up.
+			 */
+			Assert(numsortkeys == node->numCols);
+			if (memcmp(sortColIdx, node->sortColIdx,
+					   numsortkeys * sizeof(AttrNumber)) != 0)
+				elog(ERROR, "MergeAppend child's targetlist doesn't match MergeAppend");
+			Assert(memcmp(sortOperators, node->sortOperators,
+						  numsortkeys * sizeof(Oid)) == 0);
+			Assert(memcmp(collations, node->collations,
+						  numsortkeys * sizeof(Oid)) == 0);
+			Assert(memcmp(nullsFirst, node->nullsFirst,
+						  numsortkeys * sizeof(bool)) == 0);
+
+			/* Now, insert a Sort node if subplan isn't sufficiently ordered */
+			if (!pathkeys_contained_in(pathkeys, child_path->pathkeys))
+			{
+				Sort	   *sort = make_sort(child_plan, numsortkeys,
+											 sortColIdx, sortOperators,
+											 collations, nullsFirst);
+				label_sort_with_costsize(root, sort, -1.0);
+				child_plan = (Plan *) sort;
+			}
+		}
+
+		child_plans = lappend(child_plans, child_plan);
+
+		/*
+		 * Reset the child_join memory context to reclaim the memory consumed
+		 * while creating paths.
+		 */
+		MemoryContextResetAndDeleteChildren(child_context);
+	}
+
+	/* Destroy the child context as we do not need it anymore. */
+	Assert(CurrentMemoryContext == old_context);
+	MemoryContextDelete(child_context);
+
+	/* Partitioned relation with all empty children gets a dummy path. */
+	Assert(child_plans != NIL);
+
+	if (IsA(plan, MergeAppend))
+	{
+		MergeAppend *node = (MergeAppend *)plan;
+
+		node->mergeplans = child_plans;
+	}
+	else
+	{
+		Append  *node = (Append *)plan;
+
+		Assert(IsA(plan, Append));
+		node->appendplans = child_plans;
+	}
+
+	/* Complete rest of the plan. */
+	plan->qual = NIL;
+	plan->lefttree = NULL;
+	plan->righttree = NULL;
+	return plan;
+}
+
 
 /*****************************************************************************
  *
@@ -4009,6 +4247,7 @@ replace_nestloop_params_mutator(Node *node, PlannerInfo *root)
 		nlp->paramno = param->paramid;
 		nlp->paramval = var;
 		root->curOuterParams = lappend(root->curOuterParams, nlp);
+
 		/* And return the replacement Param */
 		return (Node *) param;
 	}
@@ -4072,6 +4311,7 @@ replace_nestloop_params_mutator(Node *node, PlannerInfo *root)
 		nlp->paramno = param->paramid;
 		nlp->paramval = (Var *) phv;
 		root->curOuterParams = lappend(root->curOuterParams, nlp);
+
 		/* And return the replacement Param */
 		return (Node *) param;
 	}
@@ -5343,11 +5583,11 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
 					continue;
 
 				/*
-				 * Ignore child members unless they match the rel being
+				 * Ignore child members unless they belong to the rel being
 				 * sorted.
 				 */
 				if (em->em_is_child &&
-					!bms_equal(em->em_relids, relids))
+					!bms_is_subset(em->em_relids, relids))
 					continue;
 
 				sortexpr = em->em_expr;
@@ -5458,10 +5698,10 @@ find_ec_member_for_tle(EquivalenceClass *ec,
 			continue;
 
 		/*
-		 * Ignore child members unless they match the rel being sorted.
+		 * Ignore child members unless they belong to the rel being sorted.
 		 */
 		if (em->em_is_child &&
-			!bms_equal(em->em_relids, relids))
+			!bms_is_subset(em->em_relids, relids))
 			continue;
 
 		/* Match if same expression (after stripping relabel) */
@@ -5482,9 +5722,10 @@ find_ec_member_for_tle(EquivalenceClass *ec,
  *
  *	  'lefttree' is the node which yields input tuples
  *	  'pathkeys' is the list of pathkeys by which the result is to be sorted
+ *	  'relids' is the set of relations required by prepare_sort_from_pathkeys()
  */
 static Sort *
-make_sort_from_pathkeys(Plan *lefttree, List *pathkeys)
+make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids)
 {
 	int			numsortkeys;
 	AttrNumber *sortColIdx;
@@ -5494,7 +5735,7 @@ make_sort_from_pathkeys(Plan *lefttree, List *pathkeys)
 
 	/* Compute sort column info, and adjust lefttree as needed */
 	lefttree = prepare_sort_from_pathkeys(lefttree, pathkeys,
-										  NULL,
+										  relids,
 										  NULL,
 										  false,
 										  &numsortkeys,
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 41dde50..fedbb43 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1105,7 +1105,7 @@ inheritance_planner(PlannerInfo *root)
 		subroot->parse = (Query *)
 			adjust_appendrel_attrs(root,
 								   (Node *) parse,
-								   appinfo);
+								   list_make1(appinfo));
 
 		/*
 		 * The rowMarks list might contain references to subquery RTEs, so
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b714783..899e46f 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -55,7 +55,7 @@
 typedef struct
 {
 	PlannerInfo *root;
-	AppendRelInfo *appinfo;
+	List	   *appinfos;
 	int			sublevels_up;
 } adjust_appendrel_attrs_context;
 
@@ -108,7 +108,6 @@ static Bitmapset *translate_col_privs(const Bitmapset *parent_privs,
 					List *translated_vars);
 static Node *adjust_appendrel_attrs_mutator(Node *node,
 							   adjust_appendrel_attrs_context *context);
-static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
 static List *adjust_inherited_tlist(List *tlist,
 					   AppendRelInfo *context);
 
@@ -1712,10 +1711,10 @@ translate_col_privs(const Bitmapset *parent_privs,
 
 /*
  * adjust_appendrel_attrs
- *	  Copy the specified query or expression and translate Vars referring
- *	  to the parent rel of the specified AppendRelInfo to refer to the
- *	  child rel instead.  We also update rtindexes appearing outside Vars,
- *	  such as resultRelation and jointree relids.
+ *	  Copy the specified query or expression and translate Vars referring to
+ *	  the parent rels specified in the given list of AppendRelInfos to refer to
+ *	  the corresponding child rels instead.  We also update rtindexes appearing
+ *	  outside Vars, such as resultRelation and jointree relids.
  *
  * Note: this is applied after conversion of sublinks to subplans in the
  * query jointree, but there may still be sublinks in the security barrier
@@ -1725,15 +1724,18 @@ translate_col_privs(const Bitmapset *parent_privs,
  * maybe we should try to fold the two routines together.
  */
 Node *
-adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
+adjust_appendrel_attrs(PlannerInfo *root, Node *node, List *appinfos)
 {
 	Node	   *result;
 	adjust_appendrel_attrs_context context;
+	ListCell   *lc;
 
 	context.root = root;
-	context.appinfo = appinfo;
+	context.appinfos = appinfos;
 	context.sublevels_up = 0;
 
+	Assert(appinfos && list_length(appinfos) >= 1);
+
 	/*
 	 * Must be prepared to start with a Query or a bare expression tree; if
 	 * it's a Query, go straight to query_tree_walker to make sure that
@@ -1742,11 +1744,20 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
 	if (node && IsA(node, Query))
 	{
 		Query	   *newnode;
+		AppendRelInfo *appinfo;
 
 		newnode = query_tree_mutator((Query *) node,
 									 adjust_appendrel_attrs_mutator,
 									 (void *) &context,
 									 QTW_IGNORE_RC_SUBQUERIES);
+		foreach (lc, appinfos)
+		{
+			appinfo = lfirst(lc);
+
+			if (newnode->resultRelation == appinfo->parent_relid)
+				break;
+		}
+
 		if (newnode->resultRelation == appinfo->parent_relid)
 		{
 			newnode->resultRelation = appinfo->child_relid;
@@ -1764,17 +1775,51 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
 	return result;
 }
 
+/*
+ * find_appinfos_by_relids
+ * 		Find AppendRelInfo structures for all relations specified by relids.
+ */
+List *
+find_appinfos_by_relids(PlannerInfo *root, Relids relids)
+{
+	ListCell	*lc;
+	List		*appinfo_list = NIL;
+
+	foreach (lc, root->append_rel_list)
+	{
+		AppendRelInfo *appinfo = lfirst(lc);
+
+		if (bms_is_member(appinfo->child_relid, relids))
+			appinfo_list = lappend(appinfo_list, appinfo);
+	}
+
+	Assert(list_length(appinfo_list) == bms_num_members(relids));
+	return appinfo_list;
+}
+
 static Node *
 adjust_appendrel_attrs_mutator(Node *node,
 							   adjust_appendrel_attrs_context *context)
 {
-	AppendRelInfo *appinfo = context->appinfo;
+	List   *appinfos = context->appinfos;
+	ListCell   *lc;
+
+	Assert(appinfos && list_length(appinfos) >= 1);
 
 	if (node == NULL)
 		return NULL;
 	if (IsA(node, Var))
 	{
 		Var		   *var = (Var *) copyObject(node);
+		AppendRelInfo *appinfo;
+
+		foreach (lc, appinfos)
+		{
+			appinfo = lfirst(lc);
+
+			if (var->varno == appinfo->parent_relid)
+				break;
+		}
 
 		if (var->varlevelsup == context->sublevels_up &&
 			var->varno == appinfo->parent_relid)
@@ -1865,32 +1910,58 @@ adjust_appendrel_attrs_mutator(Node *node,
 	{
 		CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node);
 
-		if (context->sublevels_up == 0 &&
-			cexpr->cvarno == appinfo->parent_relid)
-			cexpr->cvarno = appinfo->child_relid;
+		foreach (lc, appinfos)
+		{
+			AppendRelInfo *appinfo = lfirst(lc);
+
+			if (context->sublevels_up == 0 &&
+				cexpr->cvarno == appinfo->parent_relid)
+			{
+				cexpr->cvarno = appinfo->child_relid;
+				break;
+			}
+		}
 		return (Node *) cexpr;
 	}
 	if (IsA(node, RangeTblRef))
 	{
 		RangeTblRef *rtr = (RangeTblRef *) copyObject(node);
 
-		if (context->sublevels_up == 0 &&
-			rtr->rtindex == appinfo->parent_relid)
-			rtr->rtindex = appinfo->child_relid;
+		foreach (lc, appinfos)
+		{
+			AppendRelInfo *appinfo = lfirst(lc);
+
+			if (context->sublevels_up == 0 &&
+				rtr->rtindex == appinfo->parent_relid)
+			{
+				rtr->rtindex = appinfo->child_relid;
+				break;
+			}
+		}
 		return (Node *) rtr;
 	}
 	if (IsA(node, JoinExpr))
 	{
 		/* Copy the JoinExpr node with correct mutation of subnodes */
 		JoinExpr   *j;
+		AppendRelInfo *appinfo;
 
 		j = (JoinExpr *) expression_tree_mutator(node,
 											  adjust_appendrel_attrs_mutator,
 												 (void *) context);
+
 		/* now fix JoinExpr's rtindex (probably never happens) */
-		if (context->sublevels_up == 0 &&
-			j->rtindex == appinfo->parent_relid)
-			j->rtindex = appinfo->child_relid;
+		foreach (lc, appinfos)
+		{
+			appinfo = lfirst(lc);
+
+			if (context->sublevels_up == 0 &&
+				j->rtindex == appinfo->parent_relid)
+			{
+				j->rtindex = appinfo->child_relid;
+				break;
+			}
+		}
 		return (Node *) j;
 	}
 	if (IsA(node, PlaceHolderVar))
@@ -1903,9 +1974,8 @@ adjust_appendrel_attrs_mutator(Node *node,
 														 (void *) context);
 		/* now fix PlaceHolderVar's relid sets */
 		if (phv->phlevelsup == context->sublevels_up)
-			phv->phrels = adjust_relid_set(phv->phrels,
-										   appinfo->parent_relid,
-										   appinfo->child_relid);
+			phv->phrels = adjust_child_relids(phv->phrels, context->appinfos);
+
 		return (Node *) phv;
 	}
 	/* Shouldn't need to handle planner auxiliary nodes here */
@@ -1936,24 +2006,18 @@ adjust_appendrel_attrs_mutator(Node *node,
 			adjust_appendrel_attrs_mutator((Node *) oldinfo->orclause, context);
 
 		/* adjust relid sets too */
-		newinfo->clause_relids = adjust_relid_set(oldinfo->clause_relids,
-												  appinfo->parent_relid,
-												  appinfo->child_relid);
-		newinfo->required_relids = adjust_relid_set(oldinfo->required_relids,
-													appinfo->parent_relid,
-													appinfo->child_relid);
-		newinfo->outer_relids = adjust_relid_set(oldinfo->outer_relids,
-												 appinfo->parent_relid,
-												 appinfo->child_relid);
-		newinfo->nullable_relids = adjust_relid_set(oldinfo->nullable_relids,
-													appinfo->parent_relid,
-													appinfo->child_relid);
-		newinfo->left_relids = adjust_relid_set(oldinfo->left_relids,
-												appinfo->parent_relid,
-												appinfo->child_relid);
-		newinfo->right_relids = adjust_relid_set(oldinfo->right_relids,
-												 appinfo->parent_relid,
-												 appinfo->child_relid);
+		newinfo->clause_relids = adjust_child_relids(oldinfo->clause_relids,
+													 context->appinfos);
+		newinfo->required_relids = adjust_child_relids(oldinfo->required_relids,
+													 context->appinfos);
+		newinfo->outer_relids = adjust_child_relids(oldinfo->outer_relids,
+													 context->appinfos);
+		newinfo->nullable_relids = adjust_child_relids(oldinfo->nullable_relids,
+													   context->appinfos);
+		newinfo->left_relids = adjust_child_relids(oldinfo->left_relids,
+												   context->appinfos);
+		newinfo->right_relids = adjust_child_relids(oldinfo->right_relids,
+													context->appinfos);
 
 		/*
 		 * Reset cached derivative fields, since these might need to have
@@ -2002,23 +2066,6 @@ adjust_appendrel_attrs_mutator(Node *node,
 }
 
 /*
- * Substitute newrelid for oldrelid in a Relid set
- */
-static Relids
-adjust_relid_set(Relids relids, Index oldrelid, Index newrelid)
-{
-	if (bms_is_member(oldrelid, relids))
-	{
-		/* Ensure we have a modifiable copy */
-		relids = bms_copy(relids);
-		/* Remove old, add new */
-		relids = bms_del_member(relids, oldrelid);
-		relids = bms_add_member(relids, newrelid);
-	}
-	return relids;
-}
-
-/*
  * Adjust the targetlist entries of an inherited UPDATE operation
  *
  * The expressions have already been fixed, but we have to make sure that
@@ -2135,5 +2182,91 @@ adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
 	else
 		Assert(parent_rel->reloptkind == RELOPT_BASEREL);
 	/* Now translate for this child */
-	return adjust_appendrel_attrs(root, node, appinfo);
+	return adjust_appendrel_attrs(root, node, list_make1(appinfo));
+}
+
+/*
+ * build_child_restrictinfo
+ *		Returns a RestrictInfo which is derived from the given RestrictInfo by
+ *		applying the parent-child translation specified by the list of
+ *		AppendRelInfos.
+ *
+ * The topmost parent's RestrictInfo maintains a list of child RestrictInfos
+ * derived from it. If a suitable RestrictInfo is found in that list, it is
+ * returned as is. If there is no such child RestrictInfo, we translate the given
+ * RestrictInfo using the given list of AppendRelInfos and stick it in the
+ * topmost parent's list before returning it to the caller.
+ */
+RestrictInfo *
+build_child_restrictinfo(PlannerInfo *root, RestrictInfo *rinfo,
+						 List *append_rel_infos)
+{
+	Relids child_required_relids;
+	ListCell   *lc;
+	RestrictInfo   *parent_rinfo;
+	RestrictInfo   *child_rinfo;
+	MemoryContext	old_context;
+
+	child_required_relids = adjust_child_relids(rinfo->required_relids,
+													append_rel_infos);
+	/*
+	 * Check if we already have the RestrictInfo for the given child in the
+	 * topmost parent's RestrictInfo.
+	 */
+	parent_rinfo = rinfo->parent_rinfo ? rinfo->parent_rinfo : rinfo;
+	foreach (lc, parent_rinfo->child_rinfos)
+	{
+		child_rinfo = lfirst(lc);
+
+		if (bms_equal(child_rinfo->required_relids, child_required_relids))
+		{
+			bms_free(child_required_relids);
+			return child_rinfo;
+		}
+	}
+
+	/*
+	 * We didn't find any child restrictinfo for the given child, translate the
+	 * given RestrictInfo and stick it into the parent's list. The clause
+	 * expression may get used in plan, so create the child RestrictInfo in the
+	 * planner's context.
+	 */
+	old_context = MemoryContextSwitchTo(root->planner_cxt);
+	child_rinfo = (RestrictInfo *) adjust_appendrel_attrs(root, (Node *) rinfo,
+														  append_rel_infos);
+	bms_free(child_required_relids);
+	parent_rinfo->child_rinfos = lappend(parent_rinfo->child_rinfos,
+										 child_rinfo);
+	child_rinfo->parent_rinfo = parent_rinfo;
+
+	MemoryContextSwitchTo(old_context);
+
+	return child_rinfo;
+}
+
+/*
+ * build_child_clauses
+ *		Convenience routine to call build_child_restrictinfo on a list of
+ *		clauses.
+ */
+List *
+build_child_clauses(PlannerInfo *root, List *clauses, List *append_rel_infos)
+{
+	List   *child_clauses = NIL;
+	ListCell   *lc;
+
+	foreach (lc, clauses)
+	{
+		RestrictInfo   *parent_rinfo = lfirst(lc);
+		RestrictInfo   *child_rinfo;
+
+		Assert(IsA(parent_rinfo, RestrictInfo));
+
+		child_rinfo = build_child_restrictinfo(root, parent_rinfo,
+											   append_rel_infos);
+
+		child_clauses = lappend(child_clauses, child_rinfo);
+	}
+
+	return child_clauses;
 }
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 6d3ccfd..37e5f38 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -23,7 +23,9 @@
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/planmain.h"
+#include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
+#include "optimizer/tlist.h"
 #include "optimizer/var.h"
 #include "parser/parsetree.h"
 #include "utils/lsyscache.h"
@@ -2154,6 +2156,176 @@ create_hashjoin_path(PlannerInfo *root,
 }
 
 /*
+ * create_partition_join_path
+ * 		Creates a pathnode that represents partition-wise join for given
+ * 		partitioned join relation.
+ *
+ * This function is called when we haven't created paths for all the child
+ * joins. It estimates the number of rows and cost of the PartitionJoinPath
+ * based upon the number of rows and the cost of representative child-joins
+ * paths.
+ */
+PartitionJoinPath *
+create_partition_join_path(RelOptInfo *rel, List *subpaths,
+						   Bitmapset *required_outer)
+{
+	PartitionJoinPath *pathnode = makeNode(PartitionJoinPath);
+	double	subpath_rows = 0;
+	double	subpath_startup_cost = 0;
+	double	subpath_total_cost = 0;
+	double	child_rel_rows = 0;
+	ListCell   *lc;
+
+	Assert(rel->part_scheme);
+
+	pathnode->path.pathtype = T_Append;
+	pathnode->path.parent = rel;
+	pathnode->path.pathtarget = rel->reltarget;
+	pathnode->path.param_info = get_appendrel_parampathinfo(rel,
+															required_outer);
+	pathnode->path.pathkeys = NULL;
+
+	/* No parallel paths here. See more details in add_paths_to_append_rel() */
+	pathnode->path.parallel_aware = false;
+	pathnode->path.parallel_safe = false;
+	pathnode->path.parallel_workers = 0;
+
+	/* Accumulate the number of rows and costs from the given subpaths. */
+	foreach (lc, subpaths)
+	{
+		Path   *subpath = lfirst(lc);
+
+		subpath_rows += subpath->rows;
+		child_rel_rows += subpath->parent->rows;
+		subpath_total_cost += subpath->total_cost;
+
+		/*
+		 * Startup cost of an append relation is the startup cost of the first
+		 * subpath. Assume that the given first child will be the first child
+		 * in the final plan as well.
+		 */
+		if (lc == list_head(subpaths))
+			subpath_startup_cost = subpath->startup_cost;
+	}
+
+	/*
+	 * For a parameterized path, extrapolate the number of rows for the append
+	 * relation by considering the average selectivity of the parameterization
+	 * across the given children.
+	 */
+	if (bms_is_empty(required_outer))
+		pathnode->path.rows = rel->rows;
+	else
+		pathnode->path.rows = rel->rows * (subpath_rows / child_rel_rows);
+
+	pathnode->path.startup_cost = subpath_startup_cost;
+
+	/* Extrapolate the total cost to account for yet-to-be planned children. */
+	pathnode->path.total_cost = (subpath_total_cost * pathnode->path.rows) / subpath_rows;
+
+	/*
+	 * Multiply the costs with scaling factor as specified. Used to encourage
+	 * or discourage use of partition-wise join plans.
+	 */
+	pathnode->path.startup_cost *= partition_wise_plan_weight;
+	pathnode->path.total_cost *= partition_wise_plan_weight;
+
+	return pathnode;
+}
+
+/*
+ * create_partition_join_path_with_pathkeys
+ * 		Creates a pathnode that represents an ordered partition-wise join for
+ * 		given partitioned join relation.
+ *
+ * This function is called when we haven't created paths for all the child
+ * joins. It estimates the number of rows and cost of the PartitionJoinPath
+ * based upon the number of rows and the cost of representative child-joins
+ * paths.
+ */
+PartitionJoinPath *
+create_partition_join_path_with_pathkeys(PlannerInfo *root, RelOptInfo *rel,
+						   List *subpaths, List *pathkeys,
+						   Bitmapset *required_outer)
+{
+	PartitionJoinPath *pathnode = makeNode(PartitionJoinPath);
+	double	subpath_rows = 0;
+	double	subpath_startup_cost = 0;
+	double	subpath_total_cost = 0;
+	double	child_rel_rows = 0;
+	ListCell   *lc;
+
+	Assert(rel->part_scheme);
+
+	pathnode->path.pathtype = T_MergeAppend;
+	pathnode->path.parent = rel;
+	pathnode->path.pathtarget = rel->reltarget;
+	pathnode->path.param_info = get_appendrel_parampathinfo(rel,
+															required_outer);
+	pathnode->path.pathkeys = pathkeys;
+
+	/* No parallel paths here. See more details in add_paths_to_append_rel() */
+	pathnode->path.parallel_aware = false;
+	pathnode->path.parallel_safe = false;
+	pathnode->path.parallel_workers = 0;
+
+	/* Accumulate the number of rows and costs from the given subpaths. */
+	foreach (lc, subpaths)
+	{
+		Path   *subpath = lfirst(lc);
+
+		if (pathkeys_contained_in(pathkeys, subpath->pathkeys))
+		{
+			/* Subpath is adequately ordered, we won't need to sort it */
+			subpath_startup_cost += subpath->startup_cost;
+			subpath_total_cost += subpath->total_cost;
+		}
+		else
+		{
+			/* We'll need to insert a Sort node, so include cost for that */
+			Path		sort_path;		/* dummy for result of cost_sort */
+
+			cost_sort(&sort_path,
+					  root,
+					  pathkeys,
+					  subpath->total_cost,
+					  subpath->parent->tuples,
+					  subpath->pathtarget->width,
+					  0.0,
+					  work_mem,
+					  -1);
+			subpath_startup_cost += sort_path.startup_cost;
+			subpath_total_cost += sort_path.total_cost;
+		}
+
+		subpath_rows += subpath->rows;
+		child_rel_rows += subpath->parent->rows;
+	}
+
+	/*
+	 * For a parameterized path, extrapolate the number of rows for the append
+	 * relation by considering the average selectivity of the parameterization
+	 * across the given children.
+	 */
+	if (bms_is_empty(required_outer))
+		pathnode->path.rows = rel->rows;
+	else
+		pathnode->path.rows = rel->rows * (subpath_rows / child_rel_rows);
+
+	/* Extrapolate the total cost to account for yet-to-be planned children. */
+	pathnode->path.startup_cost = (subpath_startup_cost * pathnode->path.rows) / subpath_rows;
+	pathnode->path.total_cost = (subpath_total_cost * pathnode->path.rows) / subpath_rows;
+
+	/*
+	 * Multiply the costs with scaling factor as specified. Used to encourage
+	 * or discourage use of partition-wise join plans.
+	 */
+	pathnode->path.startup_cost *= partition_wise_plan_weight;
+	pathnode->path.total_cost *= partition_wise_plan_weight;
+
+	return pathnode;
+}
+/*
  * create_projection_path
  *	  Creates a pathnode that represents performing a projection.
  *
@@ -3209,3 +3381,182 @@ reparameterize_path(PlannerInfo *root, Path *path,
 	}
 	return NULL;
 }
+
+/*
+ * reparameterize_path_by_child
+ * 		Given a path parameterized by the parent of the given relation,
+ * 		translate the path to be parameterized by the given child relation.
+ *
+ * The function creates a new path of the same type as the given path, but
+ * parameterized by the given child relation. If it can not reparameterize the
+ * path as required, it returns NULL.
+ *
+ * The cost, number of rows, width and parallel path properties depend upon
+ * path->parent, which does not change during the translation. Hence those
+ * members are copied as they are.
+ */
+
+Path *
+reparameterize_path_by_child(PlannerInfo *root, Path *path,
+							  RelOptInfo *child_rel)
+{
+	Path	   *new_path;
+	ParamPathInfo   *new_ppi;
+	ParamPathInfo   *old_ppi;
+	List	   *child_aris;
+	Relids		required_outer;
+
+	/*
+	 * If the path is not parameterized by parent of the given relation or it it
+	 * doesn't need reparameterization.
+	 */
+	if (!path->param_info ||
+		!bms_overlap(PATH_REQ_OUTER(path), child_rel->top_parent_relids))
+	return path;
+
+	switch (nodeTag(path))
+	{
+		case T_Path:
+			new_path = makeNode(Path);
+			memcpy(new_path, path, sizeof(Path));
+			break;
+
+		case T_HashPath:
+			new_path = (Path *) makeNode(HashPath);
+			memcpy(new_path, path, sizeof(HashPath));
+			break;
+
+		case T_MergePath:
+			new_path = (Path *) makeNode(MergePath);
+			memcpy(new_path, path, sizeof(MergePath));
+			break;
+
+		case T_NestPath:
+			new_path = (Path *) makeNode(NestPath);
+			memcpy(new_path, path, sizeof(NestPath));
+			break;
+
+		case T_IndexPath:
+			new_path = (Path *) makeNode(IndexPath);
+			memcpy(new_path, path, sizeof(IndexPath));
+			break;
+
+		case T_AppendPath:
+			new_path = (Path *) makeNode(AppendPath);
+			memcpy(new_path, path, sizeof(AppendPath));
+			break;
+
+		/*
+		 * TODO:
+		 * If this method of translation is fine add more path types here.
+		 */
+
+		default:
+			/* Path type unsupported by this function. */
+			return NULL;
+	}
+
+	/*
+	 * Gather AppendRelInfos of the base partition relations in the outer child
+	 * relation. We need those for translating parent path to that of child by
+	 * substituting parent Var nodes and relids with those of children.
+	 */
+	child_aris = find_appinfos_by_relids(root, child_rel->relids);
+
+	/* Adjust the parameterization information. */
+	old_ppi = new_path->param_info;
+	required_outer = adjust_child_relids(old_ppi->ppi_req_outer, child_aris);
+
+	/* If we already have a PPI for this parameterization, just return it */
+	new_ppi = find_param_path_info(new_path->parent, required_outer);
+
+	/* If not build a new one and link it to the list of PPIs. */
+	if (!new_ppi)
+	{
+		new_ppi = makeNode(ParamPathInfo);
+		new_ppi->ppi_req_outer = required_outer;
+		new_ppi->ppi_rows = old_ppi->ppi_rows;
+		new_ppi->ppi_clauses = build_child_clauses(root, old_ppi->ppi_clauses,
+												   child_aris);
+		new_path->param_info = new_ppi;
+		new_path->parent->ppilist = lappend(new_path->parent->ppilist, new_ppi);
+	}
+	else
+		bms_free(required_outer);
+
+	/*
+	 * Adjust the path target if the parent of the outer relation is referenced
+	 * in the targetlist. This can happen when only the parent of outer relation is
+	 * laterally referenced in this relation.
+	 */
+	if (bms_overlap(path->parent->lateral_relids, child_rel->top_parent_relids))
+	{
+		MemoryContext	old_context;
+
+		/*
+		 * Allocate the target in planner's context, since they are copies as
+		 * is from path while creating plans.
+		 */
+		old_context = MemoryContextSwitchTo(root->planner_cxt);
+		new_path->pathtarget = copy_pathtarget(new_path->pathtarget);
+		new_path->pathtarget->exprs = (List *) adjust_appendrel_attrs(root,
+											(Node *) new_path->pathtarget->exprs,
+																	child_aris);
+		MemoryContextSwitchTo(old_context);
+	}
+
+	/*
+	 * Change parameterization of subpaths recursively. Also carry out any
+	 * pathtype specific adjustments.
+	 */
+	switch (nodeTag(path))
+	{
+		case T_HashPath:
+		case T_MergePath:
+		case T_NestPath:
+			{
+				JoinPath *jpath = (JoinPath *)new_path;
+
+				jpath->outerjoinpath = reparameterize_path_by_child(root,
+														 jpath->outerjoinpath,
+														 child_rel);
+				jpath->innerjoinpath = reparameterize_path_by_child(root,
+														 jpath->innerjoinpath,
+														 child_rel);
+				jpath->joinrestrictinfo = build_child_clauses(root,
+															  jpath->joinrestrictinfo,
+															  child_aris);
+			}
+			break;
+
+		case T_AppendPath:
+			{
+				AppendPath	*apath = (AppendPath *)new_path;
+				List		*subpaths = NIL;
+				ListCell	*lc;
+
+				foreach (lc, apath->subpaths)
+					subpaths = lappend(subpaths,
+									   reparameterize_path_by_child(root,
+																	lfirst(lc),
+																	child_rel));
+				apath->subpaths = subpaths;
+			}
+
+		case T_IndexPath:
+			{
+				IndexPath *ipath = (IndexPath *)new_path;
+
+				ipath->indexclauses = build_child_clauses(root, ipath->indexclauses,
+														  child_aris);
+				ipath->indexquals = build_child_clauses(root, ipath->indexquals,
+														child_aris);
+			}
+
+		default:
+			/* Nothing to do. */
+			break;
+	}
+
+	return new_path;
+}
diff --git a/src/backend/optimizer/util/placeholder.c b/src/backend/optimizer/util/placeholder.c
index b210914..859e6a6 100644
--- a/src/backend/optimizer/util/placeholder.c
+++ b/src/backend/optimizer/util/placeholder.c
@@ -21,6 +21,7 @@
 #include "optimizer/placeholder.h"
 #include "optimizer/planmain.h"
 #include "optimizer/var.h"
+#include "optimizer/prep.h"
 #include "utils/lsyscache.h"
 
 /* Local functions */
@@ -411,9 +412,15 @@ void
 add_placeholders_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel,
 							RelOptInfo *outer_rel, RelOptInfo *inner_rel)
 {
-	Relids		relids = joinrel->relids;
+	Relids		relids;
 	ListCell   *lc;
 
+	/* PlaceHolderInfo refers to parent relids and not those of a child. */
+	if (joinrel->top_parent_relids)
+		relids = joinrel->top_parent_relids;
+	else
+		relids = joinrel->relids;
+
 	foreach(lc, root->placeholder_list)
 	{
 		PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
@@ -424,9 +431,27 @@ add_placeholders_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel,
 			/* Is it computable here? */
 			if (bms_is_subset(phinfo->ph_eval_at, relids))
 			{
+				PlaceHolderVar *phv = phinfo->ph_var;
+
+				/*
+				 * In case the placeholder Var refers to any of the parent
+				 * relation, translate it to refer to the corresponding child.
+				 */
+				if (bms_overlap(phv->phrels, relids) &&
+					joinrel->reloptkind == RELOPT_OTHER_JOINREL)
+				{
+					List *append_rel_infos;
+
+					append_rel_infos = find_appinfos_by_relids(root,
+															  joinrel->relids);
+					phv = (PlaceHolderVar *) adjust_appendrel_attrs(root,
+																  (Node *) phv,
+															 append_rel_infos);
+				}
+
 				/* Yup, add it to the output */
 				joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs,
-													phinfo->ph_var);
+													phv);
 				joinrel->reltarget->width += phinfo->ph_width;
 
 				/*
@@ -445,7 +470,7 @@ add_placeholders_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel,
 				{
 					QualCost	cost;
 
-					cost_qual_eval_node(&cost, (Node *) phinfo->ph_var->phexpr,
+					cost_qual_eval_node(&cost, (Node *) phv->phexpr,
 										root);
 					joinrel->reltarget->cost.startup += cost.startup;
 					joinrel->reltarget->cost.per_tuple += cost.per_tuple;
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 72272d9..88e66e4 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -412,6 +412,21 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 	/* Collect info about relation's foreign keys, if relevant */
 	get_relation_foreign_keys(root, rel, relation, inhparent);
 
+	/*
+	 * Lookup partition scheme for the given relation. Only parent relations
+	 * can be partitioned.
+	 */
+	if (inhparent)
+		rel->part_scheme = find_partition_scheme(root, relation);
+	else
+		rel->part_scheme = NULL;
+
+	if (rel->part_scheme)
+		rel->partexprs = build_baserel_partition_key_exprs(relation,
+														   rel->relid);
+	else
+		rel->partexprs = NULL;
+
 	heap_close(relation, NoLock);
 
 	/*
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index d5326e6..d1433ec 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -15,15 +15,22 @@
 #include "postgres.h"
 
 #include "miscadmin.h"
+#include "catalog/heap.h"
+#include "catalog/partition.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "nodes/makefuncs.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/placeholder.h"
 #include "optimizer/plancat.h"
+#include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "optimizer/var.h"
+#include "rewrite/rewriteManip.h"
 #include "utils/hsearch.h"
+#include "utils/rel.h"
 
 
 typedef struct JoinHashEntry
@@ -34,10 +41,6 @@ typedef struct JoinHashEntry
 
 static void build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
 					RelOptInfo *input_rel);
-static List *build_joinrel_restrictlist(PlannerInfo *root,
-						   RelOptInfo *joinrel,
-						   RelOptInfo *outer_rel,
-						   RelOptInfo *inner_rel);
 static void build_joinrel_joinlist(RelOptInfo *joinrel,
 					   RelOptInfo *outer_rel,
 					   RelOptInfo *inner_rel);
@@ -47,6 +50,20 @@ static List *subbuild_joinrel_restrictlist(RelOptInfo *joinrel,
 static List *subbuild_joinrel_joinlist(RelOptInfo *joinrel,
 						  List *joininfo_list,
 						  List *new_joininfo);
+static void set_foreign_rel_properties(RelOptInfo *joinrel,
+							RelOptInfo *outer_rel, RelOptInfo *inner_rel);
+static void build_joinrel_partition_info(RelOptInfo *joinrel,
+						  RelOptInfo *outer_rel, RelOptInfo *inner_rel,
+						  JoinType jointype);
+static void build_child_joinrel_joinlist(PlannerInfo *root,
+								 RelOptInfo *joinrel, RelOptInfo *outer_rel,
+								 RelOptInfo *inner_rel);
+static List *subbuild_child_joinrel_joinlist(PlannerInfo *root,
+							RelOptInfo *joinrel, List *joininfo_list,
+							RelOptInfo *other_rel, List *new_joininfo);
+static List *subbuild_child_joinrel_restrictlist(PlannerInfo *root,
+									RelOptInfo *joinrel, List *joininfo_list,
+									RelOptInfo *other_rel, List *new_restrictlist);
 
 
 /*
@@ -137,6 +154,10 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 	rel->baserestrictcost.per_tuple = 0;
 	rel->joininfo = NIL;
 	rel->has_eclass_joins = false;
+	rel->part_scheme = NULL;
+	rel->partexprs = NULL;
+	rel->top_parent_relids = NULL;
+	rel->part_rels = NULL;
 
 	/* Check type of rtable entry */
 	switch (rte->rtekind)
@@ -314,6 +335,56 @@ find_join_rel(PlannerInfo *root, Relids relids)
 }
 
 /*
+ * set_foreign_rel_properties
+ *		Set up foreign-join fields if outer and inner relation are foreign
+ *		tables (or joins) belonging to the same server and assigned to the same
+ *		user to check access permissions as.
+ *
+ * In addition to an exact match of userid, we allow the case where one side
+ * has zero userid (implying current user) and the other side has explicit
+ * userid that happens to equal the current user; but in that case, pushdown of
+ * the join is only valid for the current user.  The useridiscurrent field
+ * records whether we had to make such an assumption for this join or any
+ * sub-join.
+ *
+ * Otherwise these fields are left invalid, so GetForeignJoinPaths will not be
+ * called for the join relation.
+ *
+ */
+static void
+set_foreign_rel_properties(RelOptInfo *joinrel, RelOptInfo *outer_rel,
+						   RelOptInfo *inner_rel)
+{
+	if (OidIsValid(outer_rel->serverid) &&
+		inner_rel->serverid == outer_rel->serverid)
+	{
+		if (inner_rel->userid == outer_rel->userid)
+		{
+			joinrel->serverid = outer_rel->serverid;
+			joinrel->userid = outer_rel->userid;
+			joinrel->useridiscurrent = outer_rel->useridiscurrent || inner_rel->useridiscurrent;
+			joinrel->fdwroutine = outer_rel->fdwroutine;
+		}
+		else if (!OidIsValid(inner_rel->userid) &&
+				 outer_rel->userid == GetUserId())
+		{
+			joinrel->serverid = outer_rel->serverid;
+			joinrel->userid = outer_rel->userid;
+			joinrel->useridiscurrent = true;
+			joinrel->fdwroutine = outer_rel->fdwroutine;
+		}
+		else if (!OidIsValid(outer_rel->userid) &&
+				 inner_rel->userid == GetUserId())
+		{
+			joinrel->serverid = outer_rel->serverid;
+			joinrel->userid = inner_rel->userid;
+			joinrel->useridiscurrent = true;
+			joinrel->fdwroutine = outer_rel->fdwroutine;
+		}
+	}
+}
+
+/*
  * build_join_rel
  *	  Returns relation entry corresponding to the union of two given rels,
  *	  creating a new relation entry if none already exists.
@@ -363,7 +434,11 @@ build_join_rel(PlannerInfo *root,
 	 * Nope, so make one.
 	 */
 	joinrel = makeNode(RelOptInfo);
+
+	Assert(!IS_OTHER_REL(outer_rel) && !IS_OTHER_REL(inner_rel));
+
 	joinrel->reloptkind = RELOPT_JOINREL;
+
 	joinrel->relids = bms_copy(joinrelids);
 	joinrel->rows = 0;
 	/* cheap startup cost is interesting iff not all tuples to be retrieved */
@@ -409,47 +484,13 @@ build_join_rel(PlannerInfo *root,
 	joinrel->baserestrictcost.per_tuple = 0;
 	joinrel->joininfo = NIL;
 	joinrel->has_eclass_joins = false;
+	joinrel->part_scheme = NULL;
+	joinrel->partexprs = NULL;
+	joinrel->top_parent_relids = NULL;
+	joinrel->part_rels = NULL;
 
-	/*
-	 * Set up foreign-join fields if outer and inner relation are foreign
-	 * tables (or joins) belonging to the same server and assigned to the same
-	 * user to check access permissions as.  In addition to an exact match of
-	 * userid, we allow the case where one side has zero userid (implying
-	 * current user) and the other side has explicit userid that happens to
-	 * equal the current user; but in that case, pushdown of the join is only
-	 * valid for the current user.  The useridiscurrent field records whether
-	 * we had to make such an assumption for this join or any sub-join.
-	 *
-	 * Otherwise these fields are left invalid, so GetForeignJoinPaths will
-	 * not be called for the join relation.
-	 */
-	if (OidIsValid(outer_rel->serverid) &&
-		inner_rel->serverid == outer_rel->serverid)
-	{
-		if (inner_rel->userid == outer_rel->userid)
-		{
-			joinrel->serverid = outer_rel->serverid;
-			joinrel->userid = outer_rel->userid;
-			joinrel->useridiscurrent = outer_rel->useridiscurrent || inner_rel->useridiscurrent;
-			joinrel->fdwroutine = outer_rel->fdwroutine;
-		}
-		else if (!OidIsValid(inner_rel->userid) &&
-				 outer_rel->userid == GetUserId())
-		{
-			joinrel->serverid = outer_rel->serverid;
-			joinrel->userid = outer_rel->userid;
-			joinrel->useridiscurrent = true;
-			joinrel->fdwroutine = outer_rel->fdwroutine;
-		}
-		else if (!OidIsValid(outer_rel->userid) &&
-				 inner_rel->userid == GetUserId())
-		{
-			joinrel->serverid = outer_rel->serverid;
-			joinrel->userid = inner_rel->userid;
-			joinrel->useridiscurrent = true;
-			joinrel->fdwroutine = outer_rel->fdwroutine;
-		}
-	}
+	/* Compute information relevant to the foreign relations. */
+	set_foreign_rel_properties(joinrel, outer_rel, inner_rel);
 
 	/*
 	 * Create a new tlist containing just the vars that need to be output from
@@ -475,6 +516,10 @@ build_join_rel(PlannerInfo *root,
 	if (bms_is_empty(joinrel->direct_lateral_relids))
 		joinrel->direct_lateral_relids = NULL;
 
+	/* Store the partition information. */
+	build_joinrel_partition_info(joinrel, outer_rel, inner_rel,
+								 sjinfo->jointype);
+
 	/*
 	 * Construct restrict and join clause lists for the new joinrel. (The
 	 * caller might or might not need the restrictlist, but I need it anyway
@@ -517,25 +562,8 @@ build_join_rel(PlannerInfo *root,
 		is_parallel_safe(root, (Node *) joinrel->reltarget->exprs))
 		joinrel->consider_parallel = true;
 
-	/*
-	 * Add the joinrel to the query's joinrel list, and store it into the
-	 * auxiliary hashtable if there is one.  NB: GEQO requires us to append
-	 * the new joinrel to the end of the list!
-	 */
-	root->join_rel_list = lappend(root->join_rel_list, joinrel);
-
-	if (root->join_rel_hash)
-	{
-		JoinHashEntry *hentry;
-		bool		found;
-
-		hentry = (JoinHashEntry *) hash_search(root->join_rel_hash,
-											   &(joinrel->relids),
-											   HASH_ENTER,
-											   &found);
-		Assert(!found);
-		hentry->join_rel = joinrel;
-	}
+	/* Add the joinrel to the query's PlannerInfo. */
+	add_join_rel(root, joinrel);
 
 	/*
 	 * Also, if dynamic-programming join search is active, add the new joinrel
@@ -555,6 +583,125 @@ build_join_rel(PlannerInfo *root,
 }
 
 /*
+ * build_child_join_rel
+ *		Builds RelOptInfo for joining given two child relations from RelOptInfo
+ *		representing the join between their parents.
+ *
+ * 'outer_rel' and 'inner_rel' are the RelOptInfos of child relations being
+ *		joined.
+ * 'parent_joinrel' is the RelOptInfo representing the join between parent
+ *		relations. Most of the members of new RelOptInfo are produced by
+ *		translating corresponding members of this RelOptInfo.
+ * 'sjinfo': context info for child join
+ * 'restrictlist': list of RestrictInfo nodes that apply to this particular
+ *		pair of joinable relations.
+ * 'join_appinfos': list of AppendRelInfo nodes for base child relations involved
+ *		in this join.
+ */
+RelOptInfo *
+build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
+						 RelOptInfo *inner_rel, RelOptInfo *parent_joinrel,
+						 JoinType jointype)
+{
+	RelOptInfo *joinrel = makeNode(RelOptInfo);
+
+	joinrel->reloptkind = RELOPT_OTHER_JOINREL;
+	joinrel->relids = bms_union(outer_rel->relids, inner_rel->relids);
+	joinrel->rows = 0;
+	/* cheap startup cost is interesting iff not all tuples to be retrieved */
+	joinrel->consider_startup = (root->tuple_fraction > 0);
+	joinrel->consider_param_startup = false;
+	joinrel->consider_parallel = false;
+	joinrel->reltarget = create_empty_pathtarget();
+	joinrel->pathlist = NIL;
+	joinrel->ppilist = NIL;
+	joinrel->partial_pathlist = NIL;
+	joinrel->cheapest_startup_path = NULL;
+	joinrel->cheapest_total_path = NULL;
+	joinrel->cheapest_unique_path = NULL;
+	joinrel->cheapest_parameterized_paths = NIL;
+	joinrel->direct_lateral_relids = NULL;
+	joinrel->lateral_relids = NULL;
+	joinrel->relid = 0;			/* indicates not a baserel */
+	joinrel->rtekind = RTE_JOIN;
+	joinrel->min_attr = 0;
+	joinrel->max_attr = 0;
+	joinrel->attr_needed = NULL;
+	joinrel->attr_widths = NULL;
+	joinrel->lateral_vars = NIL;
+	joinrel->lateral_referencers = NULL;
+	joinrel->indexlist = NIL;
+	joinrel->pages = 0;
+	joinrel->tuples = 0;
+	joinrel->allvisfrac = 0;
+	joinrel->subroot = NULL;
+	joinrel->subplan_params = NIL;
+	joinrel->serverid = InvalidOid;
+	joinrel->userid = InvalidOid;
+	joinrel->useridiscurrent = false;
+	joinrel->fdwroutine = NULL;
+	joinrel->fdw_private = NULL;
+	joinrel->baserestrictinfo = NIL;
+	joinrel->baserestrictcost.startup = 0;
+	joinrel->baserestrictcost.per_tuple = 0;
+	joinrel->joininfo = NIL;
+	joinrel->has_eclass_joins = false;
+	joinrel->part_scheme = NULL;
+	joinrel->partexprs = NULL;
+	joinrel->top_parent_relids = NULL;
+	joinrel->part_rels = NULL;
+
+
+	/* Only joins between other relations land here. */
+	Assert(IS_OTHER_REL(outer_rel) && IS_OTHER_REL(inner_rel));
+
+	joinrel->top_parent_relids = bms_union(outer_rel->top_parent_relids,
+										   inner_rel->top_parent_relids);
+
+	/* Compute information relevant to foreign relations. */
+	set_foreign_rel_properties(joinrel, outer_rel, inner_rel);
+
+	/* Build targetlist */
+	build_joinrel_tlist(root, joinrel, outer_rel);
+	build_joinrel_tlist(root, joinrel, inner_rel);
+	/* Add placeholder variables. */
+	add_placeholders_to_joinrel(root, joinrel, outer_rel, inner_rel);
+
+	/* Construct joininfo list. */
+	build_child_joinrel_joinlist(root, joinrel, outer_rel, inner_rel);
+
+	/*
+	 * Lateral relids referred in child join will be same as that referred in
+	 * the parent relation. Throw any partial result computed while building
+	 * the targetlist.
+	 */
+	bms_free(joinrel->direct_lateral_relids);
+	bms_free(joinrel->lateral_relids);
+	joinrel->direct_lateral_relids = (Relids) bms_copy(parent_joinrel->direct_lateral_relids);
+	joinrel->lateral_relids = (Relids) bms_copy(parent_joinrel->lateral_relids);
+
+	/*
+	 * If the parent joinrel has pending equivalence classes, so does the
+	 * child.
+	 */
+	joinrel->has_eclass_joins = parent_joinrel->has_eclass_joins;
+
+	/* Is the join between partitions itself partitioned? */
+	build_joinrel_partition_info(joinrel, outer_rel, inner_rel, jointype);
+
+	/* Child joinrel is parallel safe if parent is parallel safe. */
+	joinrel->consider_parallel = parent_joinrel->consider_parallel;
+
+	/* We build the join only once. */
+	Assert(!find_join_rel(root, joinrel->relids));
+
+	/* Add the relation to the PlannerInfo. */
+	add_join_rel(root, joinrel);
+
+	return joinrel;
+}
+
+/*
  * min_join_parameterization
  *
  * Determine the minimum possible parameterization of a joinrel, that is, the
@@ -609,9 +756,15 @@ static void
 build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
 					RelOptInfo *input_rel)
 {
-	Relids		relids = joinrel->relids;
+	Relids		relids;
 	ListCell   *vars;
 
+	/* attrs_needed refers to parent relids and not those of a child. */
+	if (joinrel->top_parent_relids)
+		relids = joinrel->top_parent_relids;
+	else
+		relids = joinrel->relids;
+
 	foreach(vars, input_rel->reltarget->exprs)
 	{
 		Var		   *var = (Var *) lfirst(vars);
@@ -627,23 +780,47 @@ build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
 
 		/*
 		 * Otherwise, anything in a baserel or joinrel targetlist ought to be
-		 * a Var.  (More general cases can only appear in appendrel child
-		 * rels, which will never be seen here.)
+		 * a Var or ConvertRowtypeExpr introduced while translating parent
+		 * targetlist to that of the child.
 		 */
-		if (!IsA(var, Var))
+		if (IsA(var, Var))
+		{
+			/* Get the Var's original base rel */
+			baserel = find_base_rel(root, var->varno);
+
+			/* Is it still needed above this joinrel? */
+			ndx = var->varattno - baserel->min_attr;
+		}
+		else if (IsA(var, ConvertRowtypeExpr))
+		{
+			ConvertRowtypeExpr *child_expr = (ConvertRowtypeExpr *) var;
+			Var	 *childvar = (Var *) child_expr->arg;
+
+			/*
+			 * Child's whole-row references are converted to that of parent
+			 * using ConvertRowtypeExpr. In this case, the argument to
+			 * ConvertRowtypeExpr is expected to be a whole-row reference of
+			 * the child.
+			 */
+			Assert(IsA(childvar, Var) && childvar->varattno == 0);
+
+			baserel = find_base_rel(root, childvar->varno);
+			ndx = 0 - baserel->min_attr;
+		}
+		else
 			elog(ERROR, "unexpected node type in rel targetlist: %d",
 				 (int) nodeTag(var));
 
-		/* Get the Var's original base rel */
-		baserel = find_base_rel(root, var->varno);
-
-		/* Is it still needed above this joinrel? */
-		ndx = var->varattno - baserel->min_attr;
 		if (bms_nonempty_difference(baserel->attr_needed[ndx], relids))
 		{
 			/* Yup, add it to the output */
 			joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs, var);
-			/* Vars have cost zero, so no need to adjust reltarget->cost */
+
+			/*
+			 * Vars have cost zero, so no need to adjust reltarget->cost. Even
+			 * if, it's a ConvertRowtypeExpr, it will be computed only for the
+			 * base relation, costing nothing for a join.
+			 */
 			joinrel->reltarget->width += baserel->attr_widths[ndx];
 		}
 	}
@@ -691,7 +868,7 @@ build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
  * RestrictInfo nodes are no longer context-dependent.  Instead, just include
  * the original nodes in the lists made for the join relation.
  */
-static List *
+List *
 build_joinrel_restrictlist(PlannerInfo *root,
 						   RelOptInfo *joinrel,
 						   RelOptInfo *outer_rel,
@@ -780,6 +957,8 @@ subbuild_joinrel_joinlist(RelOptInfo *joinrel,
 {
 	ListCell   *l;
 
+	Assert(joinrel->reloptkind == RELOPT_JOINREL);
+
 	foreach(l, joininfo_list)
 	{
 		RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
@@ -808,6 +987,154 @@ subbuild_joinrel_joinlist(RelOptInfo *joinrel,
 	return new_joininfo;
 }
 
+/* Similar to build_joinrel_joinlist, but used for child-join relations. */
+static void
+build_child_joinrel_joinlist(PlannerInfo *root, RelOptInfo *joinrel,
+									RelOptInfo *outer_rel, RelOptInfo *inner_rel)
+{
+	List   *result;
+
+	Assert(joinrel->reloptkind == RELOPT_OTHER_JOINREL);
+
+	result = subbuild_child_joinrel_joinlist(root, joinrel,
+											 outer_rel->joininfo, inner_rel,
+											 NIL);
+	result = subbuild_child_joinrel_joinlist(root, joinrel,
+											 inner_rel->joininfo, outer_rel,
+											 result);
+	joinrel->joininfo = result;
+}
+
+/* Similar to subbuild_joinrel_restrictlist(), but used for child-joins. */
+List *
+build_child_joinrel_restrictlist(PlannerInfo *root, RelOptInfo *joinrel,
+								 RelOptInfo *outer_rel, RelOptInfo *inner_rel)
+{
+	List   *result;
+
+	Assert(joinrel->reloptkind == RELOPT_OTHER_JOINREL);
+
+	result = subbuild_child_joinrel_restrictlist(root, joinrel,
+												 outer_rel->joininfo,
+												 inner_rel, NIL);
+	result = subbuild_child_joinrel_restrictlist(root, joinrel,
+												 inner_rel->joininfo,
+												 outer_rel, result);
+
+	/*
+	 * Add on any clauses derived from EquivalenceClasses.  These cannot be
+	 * redundant with the clauses in the joininfo lists, so don't bother
+	 * checking.
+	 */
+	result = list_concat(result,
+						 generate_join_implied_equalities(root,
+														  joinrel->relids,
+														  outer_rel->relids,
+														  inner_rel));
+
+	return result;
+}
+
+/*
+ * Similar to subbuild_joinrel_joinlist() but used for child-joins.
+ */
+static List *
+subbuild_child_joinrel_joinlist(PlannerInfo *root, RelOptInfo *joinrel,
+								List *joininfo_list, RelOptInfo *other_rel,
+								List *new_joininfo)
+{
+	ListCell   *l;
+	List	   *append_rel_infos = find_appinfos_by_relids(root,
+														   other_rel->relids);
+
+	Assert(joinrel->reloptkind == RELOPT_OTHER_JOINREL);
+
+	foreach(l, joininfo_list)
+	{
+		RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
+		RestrictInfo *parent_rinfo;
+
+		parent_rinfo = rinfo->parent_rinfo ? rinfo->parent_rinfo : rinfo;
+
+		if (bms_is_subset(parent_rinfo->required_relids, joinrel->top_parent_relids))
+		{
+			/*
+			 * The child clause derived from this clause becomes a restriction
+			 * clause for the joinrel, since it refers to no outside rels.  So
+			 * we can ignore it in this routine.
+			 */
+		}
+		else
+		{
+			/*
+			 * If the clause references parent of other joining relation, we
+			 * need to translate those references to the child relation.
+			 */
+			if (bms_overlap(rinfo->required_relids, other_rel->top_parent_relids))
+				rinfo = build_child_restrictinfo(root, rinfo, append_rel_infos);
+
+			/*
+			 * This clause is still a join clause at this level, so add it to
+			 * the new joininfo list, being careful to eliminate duplicates.
+			 * (Since RestrictInfo nodes in different joinlists will have been
+			 * multiply-linked rather than copied, pointer equality should be
+			 * a sufficient test.)
+			 */
+			new_joininfo = list_append_unique_ptr(new_joininfo, rinfo);
+		}
+	}
+
+	list_free(append_rel_infos);
+
+	return new_joininfo;
+}
+
+static List *
+subbuild_child_joinrel_restrictlist(PlannerInfo *root, RelOptInfo *joinrel,
+									List *joininfo_list, RelOptInfo *other_rel,
+									List *new_restrictlist)
+{
+	ListCell   *l;
+	List	   *append_rel_infos = find_appinfos_by_relids(root,
+														   other_rel->relids);
+
+	Assert(joinrel->reloptkind == RELOPT_OTHER_JOINREL);
+
+	foreach(l, joininfo_list)
+	{
+		RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
+		RestrictInfo *parent_rinfo;
+
+		parent_rinfo = rinfo->parent_rinfo ? rinfo->parent_rinfo : rinfo;
+
+		if (bms_is_subset(parent_rinfo->required_relids, joinrel->top_parent_relids))
+		{
+			/*
+			 * The child clause derived from this clause becomes a restriction
+			 * clause for the joinrel, since it refers to no outside rels.  Add
+			 * it to the list, being careful to eliminate duplicates. (Since
+			 * RestrictInfo nodes in different joinlists will have been
+			 * multiply-linked rather than copied, pointer equality should be a
+			 * sufficient test.)
+			 */
+			Assert(bms_overlap(rinfo->required_relids,
+							   other_rel->top_parent_relids));
+
+			rinfo = build_child_restrictinfo(root, rinfo, append_rel_infos);
+			new_restrictlist = list_append_unique_ptr(new_restrictlist, rinfo);
+		}
+		else
+		{
+			/*
+			 * This clause is still a join clause at this level, so we ignore
+			 * it in this routine.
+			 */
+		}
+	}
+
+	return new_restrictlist;
+}
+
 
 /*
  * build_empty_join_rel
@@ -933,9 +1260,17 @@ find_childrel_appendrelinfo(PlannerInfo *root, RelOptInfo *rel)
  * appendrel ancestors.  This function locates the topmost ancestor,
  * which will be a regular baserel not an otherrel.
  */
-RelOptInfo *
+Relids
 find_childrel_top_parent(PlannerInfo *root, RelOptInfo *rel)
 {
+	Assert(IS_OTHER_REL(rel));
+
+	/* Child-join relations cache this in their RelOptInfo. */
+	if (rel->reloptkind == RELOPT_OTHER_JOINREL)
+		return rel->top_parent_relids;
+
+	Assert(rel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+
 	do
 	{
 		AppendRelInfo *appinfo = find_childrel_appendrelinfo(root, rel);
@@ -947,7 +1282,7 @@ find_childrel_top_parent(PlannerInfo *root, RelOptInfo *rel)
 
 	Assert(rel->reloptkind == RELOPT_BASEREL);
 
-	return rel;
+	return rel->relids;
 }
 
 
@@ -1009,12 +1344,8 @@ get_baserel_parampathinfo(PlannerInfo *root, RelOptInfo *baserel,
 	Assert(!bms_overlap(baserel->relids, required_outer));
 
 	/* If we already have a PPI for this parameterization, just return it */
-	foreach(lc, baserel->ppilist)
-	{
-		ppi = (ParamPathInfo *) lfirst(lc);
-		if (bms_equal(ppi->ppi_req_outer, required_outer))
-			return ppi;
-	}
+	if ((ppi = find_param_path_info(baserel, required_outer)))
+		return ppi;
 
 	/*
 	 * Identify all joinclauses that are movable to this base rel given this
@@ -1251,12 +1582,8 @@ get_joinrel_parampathinfo(PlannerInfo *root, RelOptInfo *joinrel,
 	*restrict_clauses = list_concat(pclauses, *restrict_clauses);
 
 	/* If we already have a PPI for this parameterization, just return it */
-	foreach(lc, joinrel->ppilist)
-	{
-		ppi = (ParamPathInfo *) lfirst(lc);
-		if (bms_equal(ppi->ppi_req_outer, required_outer))
-			return ppi;
-	}
+	if ((ppi = find_param_path_info(joinrel, required_outer)))
+		return ppi;
 
 	/* Estimate the number of rows returned by the parameterized join */
 	rows = get_parameterized_joinrel_size(root, joinrel,
@@ -1295,7 +1622,6 @@ ParamPathInfo *
 get_appendrel_parampathinfo(RelOptInfo *appendrel, Relids required_outer)
 {
 	ParamPathInfo *ppi;
-	ListCell   *lc;
 
 	/* Unparameterized paths have no ParamPathInfo */
 	if (bms_is_empty(required_outer))
@@ -1304,12 +1630,8 @@ get_appendrel_parampathinfo(RelOptInfo *appendrel, Relids required_outer)
 	Assert(!bms_overlap(appendrel->relids, required_outer));
 
 	/* If we already have a PPI for this parameterization, just return it */
-	foreach(lc, appendrel->ppilist)
-	{
-		ppi = (ParamPathInfo *) lfirst(lc);
-		if (bms_equal(ppi->ppi_req_outer, required_outer))
-			return ppi;
-	}
+	if ((ppi = find_param_path_info(appendrel, required_outer)))
+		return ppi;
 
 	/* Else build the ParamPathInfo */
 	ppi = makeNode(ParamPathInfo);
@@ -1320,3 +1642,130 @@ get_appendrel_parampathinfo(RelOptInfo *appendrel, Relids required_outer)
 
 	return ppi;
 }
+
+/*
+ * add_join_rel
+ *		Add given join relation to the list of join relations in the given
+ *		PlannerInfo. Also add it to the auxiliary hashtable if there is one.
+ */
+void
+add_join_rel(PlannerInfo *root, RelOptInfo *joinrel)
+{
+	/* GEQO requires us to append the new joinrel to the end of the list! */
+	root->join_rel_list = lappend(root->join_rel_list, joinrel);
+
+	/* store it into the auxiliary hashtable if there is one. */
+	if (root->join_rel_hash)
+	{
+		JoinHashEntry *hentry;
+		bool		found;
+
+		hentry = (JoinHashEntry *) hash_search(root->join_rel_hash,
+											   &(joinrel->relids),
+											   HASH_ENTER,
+											   &found);
+		Assert(!found);
+		hentry->join_rel = joinrel;
+	}
+}
+
+/*
+ * build_joinrel_partition_info
+ *		If the join between given partitioned relations is possibly partitioned
+ *		set the partitioning scheme and partition keys expressions for the
+ *		join.
+ *
+ * If the two relations have same partitioning scheme, their join may be
+ * partitioned and will follow the same partitioning scheme as the joining
+ * relations.
+ */
+static void
+build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel,
+							 RelOptInfo *inner_rel, JoinType jointype)
+{
+	int		num_pks;
+	int		cnt;
+
+	/* Nothing to do if partition-wise join technique is disabled. */
+	if (!enable_partition_wise_join)
+	{
+		joinrel->part_scheme = NULL;
+		return;
+	}
+
+	/*
+	 * The join is not partitioned, if any of the relations being joined are
+	 * not partitioned or they do not have same partitioning scheme.
+	 */
+	if (!outer_rel->part_scheme || !inner_rel->part_scheme ||
+		outer_rel->part_scheme != inner_rel->part_scheme)
+	{
+		joinrel->part_scheme = NULL;
+		return;
+	}
+
+	/*
+	 * This function will be called only once for each joinrel, hence it should
+	 * not have partition scheme, partition key expressions and array for
+	 * storing child relations set.
+	 */
+	Assert(!joinrel->part_scheme && !joinrel->partexprs &&
+		   !joinrel->part_rels);
+
+	/*
+	 * Join relation is partitioned using same partitioning scheme as the
+	 * joining relations.
+	 */
+	joinrel->part_scheme = outer_rel->part_scheme;
+	num_pks = joinrel->part_scheme->partnatts;
+
+	/*
+	 * Construct partition keys for the join.
+	 *
+	 * An INNER join between two partitioned relations is partition by key
+	 * expressions from both the relations. For tables A and B partitioned by a and b
+	 * respectively, (A INNER JOIN B ON A.a = B.b) is partitioned by both A.a
+	 * and B.b.
+	 *
+	 * An OUTER join like (A LEFT JOIN B ON A.a = B.b) may produce rows with
+	 * B.b NULL. These rows may not fit the partitioning conditions imposed on
+	 * B.b. Hence, strictly speaking, the join is not partitioned by B.b.
+	 * Strictly speaking, partition keys of an OUTER join should include
+	 * partition key expressions from the OUTER side only. Consider a join like
+	 * (A LEFT JOIN B on (A.a = B.b) LEFT JOIN C ON B.b = C.c. If we do not
+	 * include B.b as partition key expression for (AB), it prohibits us from
+	 * using partition-wise join when joining (AB) with C as there is no
+	 * equi-join between partition keys of joining relations. But two NULL
+	 * values are never equal and no two rows from mis-matching partitions can
+	 * join. Hence it's safe to include B.b as partition key expression for
+	 * (AB), even though rows in (AB) are not strictly partitioned by B.b.
+	 */
+	joinrel->partexprs = (List **) palloc0(sizeof(List *) * num_pks);
+	for (cnt = 0; cnt < num_pks; cnt++)
+	{
+		List *pkexpr = list_copy(outer_rel->partexprs[cnt]);
+
+		pkexpr = list_concat(pkexpr,
+							 list_copy(inner_rel->partexprs[cnt]));
+		joinrel->partexprs[cnt] = pkexpr;
+	}
+}
+
+/*
+ * Returns a ParamPathInfo for outer relations specified by required_outer, if
+ * already available in the given rel. Returns NULL otherwise.
+ */
+ParamPathInfo *
+find_param_path_info(RelOptInfo *rel, Relids required_outer)
+{
+	ListCell   *lc;
+
+	foreach(lc, rel->ppilist)
+	{
+		ParamPathInfo  *ppi = (ParamPathInfo *) lfirst(lc);
+		if (bms_equal(ppi->ppi_req_outer, required_outer))
+			return ppi;
+	}
+
+	return NULL;
+}
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index a7ae7e3..6787df6 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -169,7 +169,6 @@ HandleStartupProcInterrupts(void)
 		exit(1);
 }
 
-
 /* ----------------------------------
  *	Startup Process main entry point
  * ----------------------------------
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 4973396..713e361 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -3426,7 +3426,9 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows,
 		/*
 		 * Sanity check --- don't divide by zero if empty relation.
 		 */
-		Assert(rel->reloptkind == RELOPT_BASEREL);
+		Assert(rel->reloptkind == RELOPT_BASEREL ||
+			   rel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+
 		if (rel->tuples > 0)
 		{
 			/*
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index a025117..ee0c4f9 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -894,6 +894,15 @@ static struct config_bool ConfigureNamesBool[] =
 		true,
 		NULL, NULL, NULL
 	},
+	{
+		{"enable_partition_wise_join", PGC_USERSET, QUERY_TUNING_METHOD,
+			gettext_noop("Enables partition-wise join."),
+			NULL
+		},
+		&enable_partition_wise_join,
+		true,
+		NULL, NULL, NULL
+	},
 
 	{
 		{"geqo", PGC_USERSET, QUERY_TUNING_GEQO,
@@ -2937,6 +2946,16 @@ static struct config_real ConfigureNamesReal[] =
 	},
 
 	{
+		{"partition_wise_plan_weight", PGC_USERSET, QUERY_TUNING_OTHER,
+			gettext_noop("Multiplication factor for partition-wise plan costs."),
+			NULL
+		},
+		&partition_wise_plan_weight,
+		DEFAULT_PARTITION_WISE_PLAN_WEIGHT, 0, DBL_MAX,
+		NULL, NULL, NULL
+	},
+
+	{
 		{"bgwriter_lru_multiplier", PGC_SIGHUP, RESOURCES_BGWRITER,
 			gettext_noop("Multiple of the average buffer usage to free per round."),
 			NULL
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 21effbf..e174923 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -63,6 +63,42 @@ typedef struct PartitionDispatchData
 
 typedef struct PartitionDispatchData *PartitionDispatch;
 
+/*
+ * Partitioning scheme
+ *		Structure to hold partitioning scheme for a given relation.
+ *
+ * Multiple relations may be partitioned in the same way. The relations
+ * resulting from joining such relations may be partitioned in the same way as
+ * the joining relations. Similarly, relations derived from such relations by
+ * grouping, sorting may be partitioned in the same way as the underlying
+ * scan relations. All such relations partitioned in the same way share the
+ * partitioning scheme.
+ *
+ * PlannerInfo stores a list of distinct "canonical" partitioning schemes.
+ * RelOptInfo of a partitioned relation holds the pointer to "canonical"
+ * partitioning scheme.
+ */
+typedef struct PartitionSchemeData
+{
+	/* Information about partitions */
+	int			nparts;			/* number of partitions */
+	PartitionBoundInfo boundinfo;	/* Partition bounds/lists */
+
+	/* Information about partition keys */
+	char		strategy;		/* partition strategy */
+	int16		partnatts;		/* number of partition attributes */
+	Oid		   *partopfamily;	/* OIDs of operator families */
+	Oid		   *partopcintype;	/* OIDs of opclass declared input data types */
+	Oid		   *key_types;		/* OIDs of partition key data types. */
+	int32	   *key_typmods;	/* typmods of partition keys. */
+	Oid		   *key_collations;	/* OIDs of collations of partition keys. */
+} PartitionSchemeData;
+
+typedef struct PartitionSchemeData *PartitionScheme;
+
+/* Include here to avoid circular dependency with relation.h. */
+struct PlannerInfo;
+
 extern void RelationBuildPartitionDesc(Relation relation);
 extern bool partition_bounds_equal(PartitionKey key,
 					   PartitionBoundInfo p1, PartitionBoundInfo p2);
@@ -80,4 +116,9 @@ extern int get_partition_for_tuple(PartitionDispatch *pd,
 					TupleTableSlot *slot,
 					EState *estate,
 					Oid *failed_at);
+extern List **build_baserel_partition_key_exprs(Relation relation,
+												Index varno);
+extern PartitionScheme find_partition_scheme(struct PlannerInfo *root,
+											 Relation rel);
+
 #endif   /* PARTITION_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index c514d3f..297ec92 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -238,6 +238,7 @@ typedef enum NodeTag
 	T_NestPath,
 	T_MergePath,
 	T_HashPath,
+	T_PartitionJoinPath,
 	T_AppendPath,
 	T_MergeAppendPath,
 	T_ResultPath,
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 3a1255a..0a7f21a 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -15,6 +15,7 @@
 #define RELATION_H
 
 #include "access/sdir.h"
+#include "catalog/partition.h"
 #include "lib/stringinfo.h"
 #include "nodes/params.h"
 #include "nodes/parsenodes.h"
@@ -263,6 +264,9 @@ typedef struct PlannerInfo
 
 	List	   *initial_rels;	/* RelOptInfos we are now trying to join */
 
+	List	   *part_schemes;	/* Canonicalised partition schemes
+								 * used in the query. */
+
 	/* Use fetch_upper_rel() to get any particular upper rel */
 	List	   *upper_rels[UPPERREL_FINAL + 1]; /* upper-rel RelOptInfos */
 
@@ -352,6 +356,11 @@ typedef struct PlannerInfo
  * handling join alias Vars.  Currently this is not needed because all join
  * alias Vars are expanded to non-aliased form during preprocess_expression.
  *
+ * We also have relations representing joins between child relations of
+ * different partitioned tables. These relations are not added to
+ * join_rel_level lists as they are not joined directly by the dynamic
+ * programming algorithm.
+ *
  * There is also a RelOptKind for "upper" relations, which are RelOptInfos
  * that describe post-scan/join processing steps, such as aggregation.
  * Many of the fields in these RelOptInfos are meaningless, but their Path
@@ -471,10 +480,19 @@ typedef enum RelOptKind
 	RELOPT_BASEREL,
 	RELOPT_JOINREL,
 	RELOPT_OTHER_MEMBER_REL,
+	RELOPT_OTHER_JOINREL,
 	RELOPT_UPPER_REL,
 	RELOPT_DEADREL
 } RelOptKind;
 
+#define IS_OTHER_REL(rel) \
+	((rel)->reloptkind == RELOPT_OTHER_MEMBER_REL || \
+	 (rel)->reloptkind == RELOPT_OTHER_JOINREL)
+
+#define IS_JOIN_REL(rel)	\
+	((rel)->reloptkind == RELOPT_JOINREL || \
+	 (rel)->reloptkind == RELOPT_OTHER_JOINREL)
+
 typedef struct RelOptInfo
 {
 	NodeTag		type;
@@ -542,6 +560,27 @@ typedef struct RelOptInfo
 	List	   *joininfo;		/* RestrictInfo structures for join clauses
 								 * involving this rel */
 	bool		has_eclass_joins;		/* T means joininfo is incomplete */
+
+	/* For partitioned relations. */
+	PartitionScheme	    part_scheme;	/* Partitioning scheme. */
+	struct RelOptInfo **part_rels;		/* Array of RelOptInfos of partitions,
+										 * stored in the same order as bounds
+										 * or lists in PartitionScheme.
+										 */
+	List			  **partexprs;		/* Array of list of partition key
+										 * expressions. For base relations
+										 * these are one element lists. For
+										 * join there may be as many elements
+										 * as the number of joining
+										 * relations.
+										 */
+	List			   *partitioned_joins;	/* List of join orders which yield
+											 * relations partitioned by above
+											 * partition scheme.
+											 */
+
+	/* Set only for "other" base or join relations. */
+	Relids		top_parent_relids;		/* Relids of topmost parents. */
 } RelOptInfo;
 
 /*
@@ -1469,6 +1508,14 @@ typedef struct LimitPath
 	Node	   *limitCount;		/* COUNT parameter, or NULL if none */
 } LimitPath;
 
+/*
+ * PartitionJoinPath represents partition-wise join between two partitioned
+ * tables.
+ */
+typedef struct PartitionJoinPath
+{
+	Path		path;
+} PartitionJoinPath;
 
 /*
  * Restriction clause info.
@@ -1663,6 +1710,12 @@ typedef struct RestrictInfo
 	/* cache space for hashclause processing; -1 if not yet set */
 	Selectivity left_bucketsize;	/* avg bucketsize of left side */
 	Selectivity right_bucketsize;		/* avg bucketsize of right side */
+
+	/* Only one of these two can be set. */
+	List	   *child_rinfos;		/* RestrictInfos derived for children. */
+	struct RestrictInfo *parent_rinfo;		/* Parent restrictinfo this
+											 * RestrictInf is derived from.
+											 */
 } RestrictInfo;
 
 /*
@@ -1785,6 +1838,19 @@ typedef struct SpecialJoinInfo
 } SpecialJoinInfo;
 
 /*
+ * Partitioned join information
+ *
+ * Saves information about relations which can be joined partition-wise and
+ * thus produce result partitioned by the partition scheme of the relation.
+ */
+typedef struct PartitionedJoin
+{
+	RelOptInfo	   *rel1;
+	RelOptInfo	   *rel2;
+	SpecialJoinInfo	   *sjinfo;
+} PartitionedJoin;
+
+/*
  * Append-relation info.
  *
  * When we expand an inheritable table or a UNION-ALL subselect into an
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index 2a4df2f..aff7ab7 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -30,6 +30,7 @@
 #define DEFAULT_PARALLEL_SETUP_COST  1000.0
 
 #define DEFAULT_EFFECTIVE_CACHE_SIZE  524288	/* measured in pages */
+#define DEFAULT_PARTITION_WISE_PLAN_WEIGHT 1
 
 typedef enum
 {
@@ -66,7 +67,9 @@ extern bool enable_nestloop;
 extern bool enable_material;
 extern bool enable_mergejoin;
 extern bool enable_hashjoin;
+extern bool enable_partition_wise_join;
 extern int	constraint_exclusion;
+extern double partition_wise_plan_weight;
 
 extern double clamp_row_est(double nrows);
 extern double index_pages_fetched(double tuples_fetched, BlockNumber pages,
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 71d9154..f36277f 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -225,10 +225,17 @@ extern LimitPath *create_limit_path(PlannerInfo *root, RelOptInfo *rel,
 				  Path *subpath,
 				  Node *limitOffset, Node *limitCount,
 				  int64 offset_est, int64 count_est);
+extern PartitionJoinPath *create_partition_join_path(RelOptInfo *rel,
+								 List *subpaths, Bitmapset *required_outer);
+extern PartitionJoinPath *create_partition_join_path_with_pathkeys(PlannerInfo *root,
+								 RelOptInfo *rel, List *subpaths,
+								 List *pathkeys, Bitmapset *required_outer);
 
 extern Path *reparameterize_path(PlannerInfo *root, Path *path,
 					Relids required_outer,
 					double loop_count);
+extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path,
+					RelOptInfo *child_rel);
 
 /*
  * prototypes for relnode.c
@@ -253,7 +260,7 @@ extern RelOptInfo *fetch_upper_rel(PlannerInfo *root, UpperRelationKind kind,
 				Relids relids);
 extern AppendRelInfo *find_childrel_appendrelinfo(PlannerInfo *root,
 							RelOptInfo *rel);
-extern RelOptInfo *find_childrel_top_parent(PlannerInfo *root, RelOptInfo *rel);
+extern Relids find_childrel_top_parent(PlannerInfo *root, RelOptInfo *rel);
 extern Relids find_childrel_parents(PlannerInfo *root, RelOptInfo *rel);
 extern ParamPathInfo *get_baserel_parampathinfo(PlannerInfo *root,
 						  RelOptInfo *baserel,
@@ -267,5 +274,18 @@ extern ParamPathInfo *get_joinrel_parampathinfo(PlannerInfo *root,
 						  List **restrict_clauses);
 extern ParamPathInfo *get_appendrel_parampathinfo(RelOptInfo *appendrel,
 							Relids required_outer);
+extern RelOptInfo *build_child_join_rel(PlannerInfo *root,
+						 RelOptInfo *outer_rel, RelOptInfo *inner_rel,
+						 RelOptInfo *parent_joinrel, JoinType jointype);
+extern List *build_joinrel_restrictlist(PlannerInfo *root,
+						   RelOptInfo *joinrel,
+						   RelOptInfo *outer_rel,
+						   RelOptInfo *inner_rel);
+extern void add_join_rel(PlannerInfo *root, RelOptInfo *joinrel);
+extern List *build_child_joinrel_restrictlist(PlannerInfo *root,
+								 RelOptInfo *joinrel, RelOptInfo *outer_rel,
+								 RelOptInfo *inner_rel);
+extern ParamPathInfo *find_param_path_info(RelOptInfo *rel,
+									  Relids required_outer);
 
 #endif   /* PATHNODE_H */
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 44abe83..5d7bcd9 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -53,6 +53,8 @@ extern RelOptInfo *standard_join_search(PlannerInfo *root, int levels_needed,
 					 List *initial_rels);
 
 extern void generate_gather_paths(PlannerInfo *root, RelOptInfo *rel);
+extern void generate_partition_wise_join_paths(PlannerInfo *root,
+											   RelOptInfo *rel);
 
 #ifdef OPTIMIZER_DEBUG
 extern void debug_print_rel(PlannerInfo *root, RelOptInfo *rel);
@@ -104,6 +106,9 @@ extern bool have_join_order_restriction(PlannerInfo *root,
 							RelOptInfo *rel1, RelOptInfo *rel2);
 extern bool have_dangerous_phv(PlannerInfo *root,
 				   Relids outer_relids, Relids inner_params);
+extern void add_paths_to_child_joinrel(PlannerInfo *root,
+									   RelOptInfo *parent_joinrel,
+									   int child_id);
 
 /*
  * equivclass.c
@@ -219,4 +224,6 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
 					   EquivalenceClass *eclass, Oid opfamily,
 					   int strategy, bool nulls_first);
 
+extern Relids adjust_child_relids(Relids relids, List *append_rel_infos);
+
 #endif   /* PATHS_H */
diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h
index fb35b68..2483303 100644
--- a/src/include/optimizer/prep.h
+++ b/src/include/optimizer/prep.h
@@ -28,6 +28,9 @@ extern void flatten_simple_union_all(PlannerInfo *root);
 extern void reduce_outer_joins(PlannerInfo *root);
 extern Relids get_relids_in_jointree(Node *jtnode, bool include_joins);
 extern Relids get_relids_for_join(PlannerInfo *root, int joinrelid);
+extern Node *adjust_join_appendrel_attrs(PlannerInfo *root, Node *node,
+										 List *append_rel_infos);
+extern List *find_appinfos_by_relids(PlannerInfo *root, Relids relids);
 
 /*
  * prototypes for prepqual.c
@@ -58,9 +61,13 @@ extern RelOptInfo *plan_set_operations(PlannerInfo *root);
 extern void expand_inherited_tables(PlannerInfo *root);
 
 extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node,
-					   AppendRelInfo *appinfo);
+					   List *appinfos);
 
 extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
 								  RelOptInfo *child_rel);
+extern RestrictInfo *build_child_restrictinfo(PlannerInfo *root,
+								 RestrictInfo *rinfo, List *append_rel_infos);
+extern List *build_child_clauses(PlannerInfo *root, List *clauses,
+								 List *append_rel_infos);
 
 #endif   /* PREP_H */
diff --git a/src/test/regress/expected/multi_level_partition_join.out b/src/test/regress/expected/multi_level_partition_join.out
new file mode 100644
index 0000000..d40ae55
--- /dev/null
+++ b/src/test/regress/expected/multi_level_partition_join.out
@@ -0,0 +1,458 @@
+--
+-- multi-leveled partitions
+--
+CREATE TABLE prt1_l (a int, b int, c varchar) PARTITION BY RANGE(a);
+CREATE TABLE prt1_l_p1 PARTITION OF prt1_l FOR VALUES FROM (0) TO (250) PARTITION BY RANGE (b);
+CREATE TABLE prt1_l_p1_p1 PARTITION OF prt1_l_p1 FOR VALUES FROM (0) TO (100);
+CREATE TABLE prt1_l_p1_p2 PARTITION OF prt1_l_p1 FOR VALUES FROM (100) TO (250);
+CREATE TABLE prt1_l_p2 PARTITION OF prt1_l FOR VALUES FROM (250) TO (500) PARTITION BY RANGE (c);
+CREATE TABLE prt1_l_p2_p1 PARTITION OF prt1_l_p2 FOR VALUES FROM ('0250') TO ('0400');
+CREATE TABLE prt1_l_p2_p2 PARTITION OF prt1_l_p2 FOR VALUES FROM ('0400') TO ('0500');
+CREATE TABLE prt1_l_p3 PARTITION OF prt1_l FOR VALUES FROM (500) TO (600) PARTITION BY RANGE ((b + a));
+CREATE TABLE prt1_l_p3_p1 PARTITION OF prt1_l_p3 FOR VALUES FROM (1000) TO (1100);
+CREATE TABLE prt1_l_p3_p2 PARTITION OF prt1_l_p3 FOR VALUES FROM (1100) TO (1200);
+INSERT INTO prt1_l SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 2) i;
+ANALYZE prt1_l;
+ANALYZE prt1_l_p1;
+ANALYZE prt1_l_p1_p1;
+ANALYZE prt1_l_p1_p2;
+ANALYZE prt1_l_p2;
+ANALYZE prt1_l_p2_p1;
+ANALYZE prt1_l_p2_p2;
+ANALYZE prt1_l_p3;
+ANALYZE prt1_l_p3_p1;
+ANALYZE prt1_l_p3_p2;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE uprt1_l AS SELECT * FROM prt1_l;
+CREATE TABLE prt2_l (a int, b int, c varchar) PARTITION BY RANGE(b);
+CREATE TABLE prt2_l_p1 PARTITION OF prt2_l FOR VALUES FROM (0) TO (250) PARTITION BY RANGE (a);
+CREATE TABLE prt2_l_p1_p1 PARTITION OF prt2_l_p1 FOR VALUES FROM (0) TO (100);
+CREATE TABLE prt2_l_p1_p2 PARTITION OF prt2_l_p1 FOR VALUES FROM (100) TO (250);
+CREATE TABLE prt2_l_p2 PARTITION OF prt2_l FOR VALUES FROM (250) TO (500) PARTITION BY RANGE (c);
+CREATE TABLE prt2_l_p2_p1 PARTITION OF prt2_l_p2 FOR VALUES FROM ('0250') TO ('0400');
+CREATE TABLE prt2_l_p2_p2 PARTITION OF prt2_l_p2 FOR VALUES FROM ('0400') TO ('0500');
+CREATE TABLE prt2_l_p3 PARTITION OF prt2_l FOR VALUES FROM (500) TO (600) PARTITION BY RANGE ((a + b));
+CREATE TABLE prt2_l_p3_p1 PARTITION OF prt2_l_p3 FOR VALUES FROM (1000) TO (1100);
+CREATE TABLE prt2_l_p3_p2 PARTITION OF prt2_l_p3 FOR VALUES FROM (1100) TO (1200);
+INSERT INTO prt2_l SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 3) i;
+ANALYZE prt2_l;
+ANALYZE prt2_l_p1;
+ANALYZE prt2_l_p1_p1;
+ANALYZE prt2_l_p1_p2;
+ANALYZE prt2_l_p2;
+ANALYZE prt2_l_p2_p1;
+ANALYZE prt2_l_p2_p2;
+ANALYZE prt2_l_p3;
+ANALYZE prt2_l_p3_p1;
+ANALYZE prt2_l_p3_p2;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE uprt2_l AS SELECT * FROM prt2_l;
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1.b = t2.a AND t1.c = t2.c AND t1.b + t1.a = t2.a + t2.b AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+                                                                      QUERY PLAN                                                                      
+------------------------------------------------------------------------------------------------------------------------------------------------------
+ Sort
+   Output: t1.a, t1.c, t2.b, t2.c
+   Sort Key: t1.a
+   ->  Append
+         ->  Hash Join
+               Output: t1.a, t1.c, t2.b, t2.c
+               Hash Cond: ((t2.b = t1.a) AND (t2.a = t1.b) AND ((t2.c)::text = (t1.c)::text) AND ((t2.a + t2.b) = (t1.b + t1.a)))
+               ->  Seq Scan on public.prt2_l_p1_p1 t2
+                     Output: t2.b, t2.c, t2.a
+               ->  Hash
+                     Output: t1.a, t1.c, t1.b
+                     ->  Seq Scan on public.prt1_l_p1_p1 t1
+                           Output: t1.a, t1.c, t1.b
+                           Filter: ((t1.a % 25) = 0)
+         ->  Hash Join
+               Output: t1_1.a, t1_1.c, t2_1.b, t2_1.c
+               Hash Cond: ((t2_1.b = t1_1.a) AND (t2_1.a = t1_1.b) AND ((t2_1.c)::text = (t1_1.c)::text) AND ((t2_1.a + t2_1.b) = (t1_1.b + t1_1.a)))
+               ->  Seq Scan on public.prt2_l_p1_p2 t2_1
+                     Output: t2_1.b, t2_1.c, t2_1.a
+               ->  Hash
+                     Output: t1_1.a, t1_1.c, t1_1.b
+                     ->  Seq Scan on public.prt1_l_p1_p2 t1_1
+                           Output: t1_1.a, t1_1.c, t1_1.b
+                           Filter: ((t1_1.a % 25) = 0)
+         ->  Hash Join
+               Output: t1_2.a, t1_2.c, t2_2.b, t2_2.c
+               Hash Cond: ((t2_2.b = t1_2.a) AND (t2_2.a = t1_2.b) AND ((t2_2.c)::text = (t1_2.c)::text) AND ((t2_2.a + t2_2.b) = (t1_2.b + t1_2.a)))
+               ->  Seq Scan on public.prt2_l_p2_p1 t2_2
+                     Output: t2_2.b, t2_2.c, t2_2.a
+               ->  Hash
+                     Output: t1_2.a, t1_2.c, t1_2.b
+                     ->  Seq Scan on public.prt1_l_p2_p1 t1_2
+                           Output: t1_2.a, t1_2.c, t1_2.b
+                           Filter: ((t1_2.a % 25) = 0)
+         ->  Hash Join
+               Output: t1_3.a, t1_3.c, t2_3.b, t2_3.c
+               Hash Cond: ((t2_3.b = t1_3.a) AND (t2_3.a = t1_3.b) AND ((t2_3.c)::text = (t1_3.c)::text) AND ((t2_3.a + t2_3.b) = (t1_3.b + t1_3.a)))
+               ->  Seq Scan on public.prt2_l_p2_p2 t2_3
+                     Output: t2_3.b, t2_3.c, t2_3.a
+               ->  Hash
+                     Output: t1_3.a, t1_3.c, t1_3.b
+                     ->  Seq Scan on public.prt1_l_p2_p2 t1_3
+                           Output: t1_3.a, t1_3.c, t1_3.b
+                           Filter: ((t1_3.a % 25) = 0)
+         ->  Hash Join
+               Output: t1_4.a, t1_4.c, t2_4.b, t2_4.c
+               Hash Cond: ((t2_4.b = t1_4.a) AND (t2_4.a = t1_4.b) AND ((t2_4.c)::text = (t1_4.c)::text) AND ((t2_4.a + t2_4.b) = (t1_4.b + t1_4.a)))
+               ->  Seq Scan on public.prt2_l_p3_p1 t2_4
+                     Output: t2_4.b, t2_4.c, t2_4.a
+               ->  Hash
+                     Output: t1_4.a, t1_4.c, t1_4.b
+                     ->  Seq Scan on public.prt1_l_p3_p1 t1_4
+                           Output: t1_4.a, t1_4.c, t1_4.b
+                           Filter: ((t1_4.a % 25) = 0)
+         ->  Hash Join
+               Output: t1_5.a, t1_5.c, t2_5.b, t2_5.c
+               Hash Cond: ((t2_5.b = t1_5.a) AND (t2_5.a = t1_5.b) AND ((t2_5.c)::text = (t1_5.c)::text) AND ((t2_5.a + t2_5.b) = (t1_5.b + t1_5.a)))
+               ->  Seq Scan on public.prt2_l_p3_p2 t2_5
+                     Output: t2_5.b, t2_5.c, t2_5.a
+               ->  Hash
+                     Output: t1_5.a, t1_5.c, t1_5.b
+                     ->  Seq Scan on public.prt1_l_p3_p2 t1_5
+                           Output: t1_5.a, t1_5.c, t1_5.b
+                           Filter: ((t1_5.a % 25) = 0)
+(64 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1.b = t2.a AND t1.c = t2.c AND t1.b + t1.a = t2.a + t2.b AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+ 150 | 0150 | 150 | 0150
+ 300 | 0300 | 300 | 0300
+ 450 | 0450 | 450 | 0450
+(4 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM uprt1_l t1, uprt2_l t2 WHERE t1.a = t2.b AND t1.b = t2.a AND t1.c = t2.c AND t1.b + t1.a = t2.a + t2.b AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+ 150 | 0150 | 150 | 0150
+ 300 | 0300 | 300 | 0300
+ 450 | 0450 | 450 | 0450
+(4 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 LEFT JOIN prt2_l t2 ON t1.a = t2.b AND t1.b = t2.a AND t1.c = t2.c AND t1.b + t1.a = t2.a + t2.b WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+                                                                      QUERY PLAN                                                                      
+------------------------------------------------------------------------------------------------------------------------------------------------------
+ Sort
+   Output: t1.a, t1.c, t2.b, t2.c
+   Sort Key: t1.a, t2.b
+   ->  Append
+         ->  Hash Right Join
+               Output: t1.a, t1.c, t2.b, t2.c
+               Hash Cond: ((t2.b = t1.a) AND (t2.a = t1.b) AND ((t2.c)::text = (t1.c)::text) AND ((t2.a + t2.b) = (t1.b + t1.a)))
+               ->  Seq Scan on public.prt2_l_p1_p1 t2
+                     Output: t2.b, t2.c, t2.a
+               ->  Hash
+                     Output: t1.a, t1.c, t1.b
+                     ->  Seq Scan on public.prt1_l_p1_p1 t1
+                           Output: t1.a, t1.c, t1.b
+                           Filter: ((t1.a % 25) = 0)
+         ->  Hash Right Join
+               Output: t1_1.a, t1_1.c, t2_1.b, t2_1.c
+               Hash Cond: ((t2_1.b = t1_1.a) AND (t2_1.a = t1_1.b) AND ((t2_1.c)::text = (t1_1.c)::text) AND ((t2_1.a + t2_1.b) = (t1_1.b + t1_1.a)))
+               ->  Seq Scan on public.prt2_l_p1_p2 t2_1
+                     Output: t2_1.b, t2_1.c, t2_1.a
+               ->  Hash
+                     Output: t1_1.a, t1_1.c, t1_1.b
+                     ->  Seq Scan on public.prt1_l_p1_p2 t1_1
+                           Output: t1_1.a, t1_1.c, t1_1.b
+                           Filter: ((t1_1.a % 25) = 0)
+         ->  Hash Right Join
+               Output: t1_2.a, t1_2.c, t2_2.b, t2_2.c
+               Hash Cond: ((t2_2.b = t1_2.a) AND (t2_2.a = t1_2.b) AND ((t2_2.c)::text = (t1_2.c)::text) AND ((t2_2.a + t2_2.b) = (t1_2.b + t1_2.a)))
+               ->  Seq Scan on public.prt2_l_p2_p1 t2_2
+                     Output: t2_2.b, t2_2.c, t2_2.a
+               ->  Hash
+                     Output: t1_2.a, t1_2.c, t1_2.b
+                     ->  Seq Scan on public.prt1_l_p2_p1 t1_2
+                           Output: t1_2.a, t1_2.c, t1_2.b
+                           Filter: ((t1_2.a % 25) = 0)
+         ->  Hash Right Join
+               Output: t1_3.a, t1_3.c, t2_3.b, t2_3.c
+               Hash Cond: ((t2_3.b = t1_3.a) AND (t2_3.a = t1_3.b) AND ((t2_3.c)::text = (t1_3.c)::text) AND ((t2_3.a + t2_3.b) = (t1_3.b + t1_3.a)))
+               ->  Seq Scan on public.prt2_l_p2_p2 t2_3
+                     Output: t2_3.b, t2_3.c, t2_3.a
+               ->  Hash
+                     Output: t1_3.a, t1_3.c, t1_3.b
+                     ->  Seq Scan on public.prt1_l_p2_p2 t1_3
+                           Output: t1_3.a, t1_3.c, t1_3.b
+                           Filter: ((t1_3.a % 25) = 0)
+         ->  Hash Right Join
+               Output: t1_4.a, t1_4.c, t2_4.b, t2_4.c
+               Hash Cond: ((t2_4.b = t1_4.a) AND (t2_4.a = t1_4.b) AND ((t2_4.c)::text = (t1_4.c)::text) AND ((t2_4.a + t2_4.b) = (t1_4.b + t1_4.a)))
+               ->  Seq Scan on public.prt2_l_p3_p1 t2_4
+                     Output: t2_4.b, t2_4.c, t2_4.a
+               ->  Hash
+                     Output: t1_4.a, t1_4.c, t1_4.b
+                     ->  Seq Scan on public.prt1_l_p3_p1 t1_4
+                           Output: t1_4.a, t1_4.c, t1_4.b
+                           Filter: ((t1_4.a % 25) = 0)
+         ->  Hash Right Join
+               Output: t1_5.a, t1_5.c, t2_5.b, t2_5.c
+               Hash Cond: ((t2_5.b = t1_5.a) AND (t2_5.a = t1_5.b) AND ((t2_5.c)::text = (t1_5.c)::text) AND ((t2_5.a + t2_5.b) = (t1_5.b + t1_5.a)))
+               ->  Seq Scan on public.prt2_l_p3_p2 t2_5
+                     Output: t2_5.b, t2_5.c, t2_5.a
+               ->  Hash
+                     Output: t1_5.a, t1_5.c, t1_5.b
+                     ->  Seq Scan on public.prt1_l_p3_p2 t1_5
+                           Output: t1_5.a, t1_5.c, t1_5.b
+                           Filter: ((t1_5.a % 25) = 0)
+(64 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 LEFT JOIN prt2_l t2 ON t1.a = t2.b AND t1.b = t2.a AND t1.c = t2.c AND t1.b + t1.a = t2.a + t2.b WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+  50 | 0050 |     | 
+ 100 | 0100 |     | 
+ 150 | 0150 | 150 | 0150
+ 200 | 0200 |     | 
+ 250 | 0250 |     | 
+ 300 | 0300 | 300 | 0300
+ 350 | 0350 |     | 
+ 400 | 0400 |     | 
+ 450 | 0450 | 450 | 0450
+ 500 | 0500 |     | 
+ 550 | 0550 |     | 
+(12 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM uprt1_l t1 LEFT JOIN uprt2_l t2 ON t1.a = t2.b AND t1.b = t2.a AND t1.c = t2.c AND t1.b + t1.a = t2.a + t2.b WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+  50 | 0050 |     | 
+ 100 | 0100 |     | 
+ 150 | 0150 | 150 | 0150
+ 200 | 0200 |     | 
+ 250 | 0250 |     | 
+ 300 | 0300 | 300 | 0300
+ 350 | 0350 |     | 
+ 400 | 0400 |     | 
+ 450 | 0450 | 450 | 0450
+ 500 | 0500 |     | 
+ 550 | 0550 |     | 
+(12 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 RIGHT JOIN prt2_l t2 ON t1.a = t2.b AND t1.b = t2.a AND t1.c = t2.c AND t1.b + t1.a = t2.a + t2.b WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+                                                                         QUERY PLAN                                                                         
+------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Sort
+   Output: t1.a, t1.c, t2.b, t2.c
+   Sort Key: t1.a, t2.b
+   ->  Result
+         Output: t1.a, t1.c, t2.b, t2.c
+         ->  Append
+               ->  Hash Right Join
+                     Output: t2.b, t2.c, t1.a, t1.c
+                     Hash Cond: ((t1.a = t2.b) AND (t1.b = t2.a) AND ((t1.c)::text = (t2.c)::text) AND ((t1.b + t1.a) = (t2.a + t2.b)))
+                     ->  Seq Scan on public.prt1_l_p1_p1 t1
+                           Output: t1.a, t1.c, t1.b
+                     ->  Hash
+                           Output: t2.b, t2.c, t2.a
+                           ->  Seq Scan on public.prt2_l_p1_p1 t2
+                                 Output: t2.b, t2.c, t2.a
+                                 Filter: ((t2.b % 25) = 0)
+               ->  Hash Right Join
+                     Output: t2_1.b, t2_1.c, t1_1.a, t1_1.c
+                     Hash Cond: ((t1_1.a = t2_1.b) AND (t1_1.b = t2_1.a) AND ((t1_1.c)::text = (t2_1.c)::text) AND ((t1_1.b + t1_1.a) = (t2_1.a + t2_1.b)))
+                     ->  Seq Scan on public.prt1_l_p1_p2 t1_1
+                           Output: t1_1.a, t1_1.c, t1_1.b
+                     ->  Hash
+                           Output: t2_1.b, t2_1.c, t2_1.a
+                           ->  Seq Scan on public.prt2_l_p1_p2 t2_1
+                                 Output: t2_1.b, t2_1.c, t2_1.a
+                                 Filter: ((t2_1.b % 25) = 0)
+               ->  Hash Right Join
+                     Output: t2_2.b, t2_2.c, t1_2.a, t1_2.c
+                     Hash Cond: ((t1_2.a = t2_2.b) AND (t1_2.b = t2_2.a) AND ((t1_2.c)::text = (t2_2.c)::text) AND ((t1_2.b + t1_2.a) = (t2_2.a + t2_2.b)))
+                     ->  Seq Scan on public.prt1_l_p2_p1 t1_2
+                           Output: t1_2.a, t1_2.c, t1_2.b
+                     ->  Hash
+                           Output: t2_2.b, t2_2.c, t2_2.a
+                           ->  Seq Scan on public.prt2_l_p2_p1 t2_2
+                                 Output: t2_2.b, t2_2.c, t2_2.a
+                                 Filter: ((t2_2.b % 25) = 0)
+               ->  Hash Right Join
+                     Output: t2_3.b, t2_3.c, t1_3.a, t1_3.c
+                     Hash Cond: ((t1_3.a = t2_3.b) AND (t1_3.b = t2_3.a) AND ((t1_3.c)::text = (t2_3.c)::text) AND ((t1_3.b + t1_3.a) = (t2_3.a + t2_3.b)))
+                     ->  Seq Scan on public.prt1_l_p2_p2 t1_3
+                           Output: t1_3.a, t1_3.c, t1_3.b
+                     ->  Hash
+                           Output: t2_3.b, t2_3.c, t2_3.a
+                           ->  Seq Scan on public.prt2_l_p2_p2 t2_3
+                                 Output: t2_3.b, t2_3.c, t2_3.a
+                                 Filter: ((t2_3.b % 25) = 0)
+               ->  Hash Right Join
+                     Output: t2_4.b, t2_4.c, t1_4.a, t1_4.c
+                     Hash Cond: ((t1_4.a = t2_4.b) AND (t1_4.b = t2_4.a) AND ((t1_4.c)::text = (t2_4.c)::text) AND ((t1_4.b + t1_4.a) = (t2_4.a + t2_4.b)))
+                     ->  Seq Scan on public.prt1_l_p3_p1 t1_4
+                           Output: t1_4.a, t1_4.c, t1_4.b
+                     ->  Hash
+                           Output: t2_4.b, t2_4.c, t2_4.a
+                           ->  Seq Scan on public.prt2_l_p3_p1 t2_4
+                                 Output: t2_4.b, t2_4.c, t2_4.a
+                                 Filter: ((t2_4.b % 25) = 0)
+               ->  Hash Right Join
+                     Output: t2_5.b, t2_5.c, t1_5.a, t1_5.c
+                     Hash Cond: ((t1_5.a = t2_5.b) AND (t1_5.b = t2_5.a) AND ((t1_5.c)::text = (t2_5.c)::text) AND ((t1_5.b + t1_5.a) = (t2_5.a + t2_5.b)))
+                     ->  Seq Scan on public.prt1_l_p3_p2 t1_5
+                           Output: t1_5.a, t1_5.c, t1_5.b
+                     ->  Hash
+                           Output: t2_5.b, t2_5.c, t2_5.a
+                           ->  Seq Scan on public.prt2_l_p3_p2 t2_5
+                                 Output: t2_5.b, t2_5.c, t2_5.a
+                                 Filter: ((t2_5.b % 25) = 0)
+(66 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 RIGHT JOIN prt2_l t2 ON t1.a = t2.b AND t1.b = t2.a AND t1.c = t2.c AND t1.b + t1.a = t2.a + t2.b WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+ 150 | 0150 | 150 | 0150
+ 300 | 0300 | 300 | 0300
+ 450 | 0450 | 450 | 0450
+     |      |  75 | 0075
+     |      | 225 | 0225
+     |      | 375 | 0375
+     |      | 525 | 0525
+(8 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM uprt1_l t1 RIGHT JOIN uprt2_l t2 ON t1.a = t2.b AND t1.b = t2.a AND t1.c = t2.c AND t1.b + t1.a = t2.a + t2.b WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+ 150 | 0150 | 150 | 0150
+ 300 | 0300 | 300 | 0300
+ 450 | 0450 | 450 | 0450
+     |      |  75 | 0075
+     |      | 225 | 0225
+     |      | 375 | 0375
+     |      | 525 | 0525
+(8 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_l WHERE prt1_l.a % 25 = 0) t1 FULL JOIN (SELECT * FROM prt2_l WHERE prt2_l.b % 25 = 0) t2 ON (t1.a = t2.b AND t1.b = t2.a AND t1.c = t2.c AND t1.b + t1.a = t2.a + t2.b) ORDER BY t1.a, t2.b;
+                                                                                                              QUERY PLAN                                                                                                              
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Sort
+   Output: prt1_l_p1_p1.a, prt1_l_p1_p1.c, prt2_l_p1_p1.b, prt2_l_p1_p1.c
+   Sort Key: prt1_l_p1_p1.a, prt2_l_p1_p1.b
+   ->  Append
+         ->  Hash Full Join
+               Output: prt1_l_p1_p1.a, prt1_l_p1_p1.c, prt2_l_p1_p1.b, prt2_l_p1_p1.c
+               Hash Cond: ((prt1_l_p1_p1.a = prt2_l_p1_p1.b) AND (prt1_l_p1_p1.b = prt2_l_p1_p1.a) AND ((prt1_l_p1_p1.c)::text = (prt2_l_p1_p1.c)::text) AND ((prt1_l_p1_p1.b + prt1_l_p1_p1.a) = (prt2_l_p1_p1.a + prt2_l_p1_p1.b)))
+               ->  Seq Scan on public.prt1_l_p1_p1
+                     Output: prt1_l_p1_p1.a, prt1_l_p1_p1.c, prt1_l_p1_p1.b
+                     Filter: ((prt1_l_p1_p1.a % 25) = 0)
+               ->  Hash
+                     Output: prt2_l_p1_p1.b, prt2_l_p1_p1.c, prt2_l_p1_p1.a
+                     ->  Seq Scan on public.prt2_l_p1_p1
+                           Output: prt2_l_p1_p1.b, prt2_l_p1_p1.c, prt2_l_p1_p1.a
+                           Filter: ((prt2_l_p1_p1.b % 25) = 0)
+         ->  Hash Full Join
+               Output: prt1_l_p1_p2.a, prt1_l_p1_p2.c, prt2_l_p1_p2.b, prt2_l_p1_p2.c
+               Hash Cond: ((prt1_l_p1_p2.a = prt2_l_p1_p2.b) AND (prt1_l_p1_p2.b = prt2_l_p1_p2.a) AND ((prt1_l_p1_p2.c)::text = (prt2_l_p1_p2.c)::text) AND ((prt1_l_p1_p2.b + prt1_l_p1_p2.a) = (prt2_l_p1_p2.a + prt2_l_p1_p2.b)))
+               ->  Seq Scan on public.prt1_l_p1_p2
+                     Output: prt1_l_p1_p2.a, prt1_l_p1_p2.c, prt1_l_p1_p2.b
+                     Filter: ((prt1_l_p1_p2.a % 25) = 0)
+               ->  Hash
+                     Output: prt2_l_p1_p2.b, prt2_l_p1_p2.c, prt2_l_p1_p2.a
+                     ->  Seq Scan on public.prt2_l_p1_p2
+                           Output: prt2_l_p1_p2.b, prt2_l_p1_p2.c, prt2_l_p1_p2.a
+                           Filter: ((prt2_l_p1_p2.b % 25) = 0)
+         ->  Hash Full Join
+               Output: prt1_l_p2_p1.a, prt1_l_p2_p1.c, prt2_l_p2_p1.b, prt2_l_p2_p1.c
+               Hash Cond: ((prt1_l_p2_p1.a = prt2_l_p2_p1.b) AND (prt1_l_p2_p1.b = prt2_l_p2_p1.a) AND ((prt1_l_p2_p1.c)::text = (prt2_l_p2_p1.c)::text) AND ((prt1_l_p2_p1.b + prt1_l_p2_p1.a) = (prt2_l_p2_p1.a + prt2_l_p2_p1.b)))
+               ->  Seq Scan on public.prt1_l_p2_p1
+                     Output: prt1_l_p2_p1.a, prt1_l_p2_p1.c, prt1_l_p2_p1.b
+                     Filter: ((prt1_l_p2_p1.a % 25) = 0)
+               ->  Hash
+                     Output: prt2_l_p2_p1.b, prt2_l_p2_p1.c, prt2_l_p2_p1.a
+                     ->  Seq Scan on public.prt2_l_p2_p1
+                           Output: prt2_l_p2_p1.b, prt2_l_p2_p1.c, prt2_l_p2_p1.a
+                           Filter: ((prt2_l_p2_p1.b % 25) = 0)
+         ->  Hash Full Join
+               Output: prt1_l_p2_p2.a, prt1_l_p2_p2.c, prt2_l_p2_p2.b, prt2_l_p2_p2.c
+               Hash Cond: ((prt1_l_p2_p2.a = prt2_l_p2_p2.b) AND (prt1_l_p2_p2.b = prt2_l_p2_p2.a) AND ((prt1_l_p2_p2.c)::text = (prt2_l_p2_p2.c)::text) AND ((prt1_l_p2_p2.b + prt1_l_p2_p2.a) = (prt2_l_p2_p2.a + prt2_l_p2_p2.b)))
+               ->  Seq Scan on public.prt1_l_p2_p2
+                     Output: prt1_l_p2_p2.a, prt1_l_p2_p2.c, prt1_l_p2_p2.b
+                     Filter: ((prt1_l_p2_p2.a % 25) = 0)
+               ->  Hash
+                     Output: prt2_l_p2_p2.b, prt2_l_p2_p2.c, prt2_l_p2_p2.a
+                     ->  Seq Scan on public.prt2_l_p2_p2
+                           Output: prt2_l_p2_p2.b, prt2_l_p2_p2.c, prt2_l_p2_p2.a
+                           Filter: ((prt2_l_p2_p2.b % 25) = 0)
+         ->  Hash Full Join
+               Output: prt1_l_p3_p1.a, prt1_l_p3_p1.c, prt2_l_p3_p1.b, prt2_l_p3_p1.c
+               Hash Cond: ((prt1_l_p3_p1.a = prt2_l_p3_p1.b) AND (prt1_l_p3_p1.b = prt2_l_p3_p1.a) AND ((prt1_l_p3_p1.c)::text = (prt2_l_p3_p1.c)::text) AND ((prt1_l_p3_p1.b + prt1_l_p3_p1.a) = (prt2_l_p3_p1.a + prt2_l_p3_p1.b)))
+               ->  Seq Scan on public.prt1_l_p3_p1
+                     Output: prt1_l_p3_p1.a, prt1_l_p3_p1.c, prt1_l_p3_p1.b
+                     Filter: ((prt1_l_p3_p1.a % 25) = 0)
+               ->  Hash
+                     Output: prt2_l_p3_p1.b, prt2_l_p3_p1.c, prt2_l_p3_p1.a
+                     ->  Seq Scan on public.prt2_l_p3_p1
+                           Output: prt2_l_p3_p1.b, prt2_l_p3_p1.c, prt2_l_p3_p1.a
+                           Filter: ((prt2_l_p3_p1.b % 25) = 0)
+         ->  Hash Full Join
+               Output: prt1_l_p3_p2.a, prt1_l_p3_p2.c, prt2_l_p3_p2.b, prt2_l_p3_p2.c
+               Hash Cond: ((prt1_l_p3_p2.a = prt2_l_p3_p2.b) AND (prt1_l_p3_p2.b = prt2_l_p3_p2.a) AND ((prt1_l_p3_p2.c)::text = (prt2_l_p3_p2.c)::text) AND ((prt1_l_p3_p2.b + prt1_l_p3_p2.a) = (prt2_l_p3_p2.a + prt2_l_p3_p2.b)))
+               ->  Seq Scan on public.prt1_l_p3_p2
+                     Output: prt1_l_p3_p2.a, prt1_l_p3_p2.c, prt1_l_p3_p2.b
+                     Filter: ((prt1_l_p3_p2.a % 25) = 0)
+               ->  Hash
+                     Output: prt2_l_p3_p2.b, prt2_l_p3_p2.c, prt2_l_p3_p2.a
+                     ->  Seq Scan on public.prt2_l_p3_p2
+                           Output: prt2_l_p3_p2.b, prt2_l_p3_p2.c, prt2_l_p3_p2.a
+                           Filter: ((prt2_l_p3_p2.b % 25) = 0)
+(70 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_l WHERE prt1_l.a % 25 = 0) t1 FULL JOIN (SELECT * FROM prt2_l WHERE prt2_l.b % 25 = 0) t2 ON (t1.a = t2.b AND t1.b = t2.a AND t1.c = t2.c AND t1.b + t1.a = t2.a + t2.b) ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+  50 | 0050 |     | 
+ 100 | 0100 |     | 
+ 150 | 0150 | 150 | 0150
+ 200 | 0200 |     | 
+ 250 | 0250 |     | 
+ 300 | 0300 | 300 | 0300
+ 350 | 0350 |     | 
+ 400 | 0400 |     | 
+ 450 | 0450 | 450 | 0450
+ 500 | 0500 |     | 
+ 550 | 0550 |     | 
+     |      |  75 | 0075
+     |      | 225 | 0225
+     |      | 375 | 0375
+     |      | 525 | 0525
+(16 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM uprt1_l t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM uprt2_l t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b AND t1.b = t2.a AND t1.c = t2.c AND t1.b + t1.a = t2.a + t2.b) ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+  50 | 0050 |     | 
+ 100 | 0100 |     | 
+ 150 | 0150 | 150 | 0150
+ 200 | 0200 |     | 
+ 250 | 0250 |     | 
+ 300 | 0300 | 300 | 0300
+ 350 | 0350 |     | 
+ 400 | 0400 |     | 
+ 450 | 0450 | 450 | 0450
+ 500 | 0500 |     | 
+ 550 | 0550 |     | 
+     |      |  75 | 0075
+     |      | 225 | 0225
+     |      | 375 | 0375
+     |      | 525 | 0525
+(16 rows)
diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out
new file mode 100644
index 0000000..79779d6
--- /dev/null
+++ b/src/test/regress/expected/partition_join.out
@@ -0,0 +1,4118 @@
+--
+-- PARTITION_JOIN
+-- Test partition-wise join between partitioned tables
+--
+-- Usually partition-wise join paths are chosen when data is large, which would
+-- take regression tests to run longer. So, weigh partition-wise joins cheaper
+-- to force those even for smaller data.
+SET partition_wise_plan_weight to 0.2;
+--
+-- partitioned by a single column
+--
+CREATE TABLE prt1 (a int, b int, c varchar) PARTITION BY RANGE(a);
+CREATE TABLE prt1_p1 PARTITION OF prt1 FOR VALUES FROM (0) TO (250);
+CREATE TABLE prt1_p3 PARTITION OF prt1 FOR VALUES FROM (500) TO (600);
+CREATE TABLE prt1_p2 PARTITION OF prt1 FOR VALUES FROM (250) TO (500);
+INSERT INTO prt1 SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 2) i;
+ANALYZE prt1;
+ANALYZE prt1_p1;
+ANALYZE prt1_p2;
+ANALYZE prt1_p3;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE uprt1 AS SELECT * FROM prt1;
+CREATE TABLE prt2 (a int, b int, c varchar) PARTITION BY RANGE(b);
+CREATE TABLE prt2_p1 PARTITION OF prt2 FOR VALUES FROM (0) TO (250);
+CREATE TABLE prt2_p2 PARTITION OF prt2 FOR VALUES FROM (250) TO (500);
+CREATE TABLE prt2_p3 PARTITION OF prt2 FOR VALUES FROM (500) TO (600);
+INSERT INTO prt2 SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 3) i;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+ANALYZE prt2;
+ANALYZE prt2_p1;
+ANALYZE prt2_p2;
+ANALYZE prt2_p3;
+CREATE TABLE uprt2 AS SELECT * FROM prt2;
+-- inner join
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+                       QUERY PLAN                        
+---------------------------------------------------------
+ Sort
+   Output: t1.a, t1.c, t2.b, t2.c
+   Sort Key: t1.a
+   ->  Append
+         ->  Hash Join
+               Output: t1.a, t1.c, t2.b, t2.c
+               Hash Cond: (t2.b = t1.a)
+               ->  Seq Scan on public.prt2_p1 t2
+                     Output: t2.b, t2.c
+               ->  Hash
+                     Output: t1.a, t1.c
+                     ->  Seq Scan on public.prt1_p1 t1
+                           Output: t1.a, t1.c
+                           Filter: ((t1.a % 25) = 0)
+         ->  Hash Join
+               Output: t1_2.a, t1_2.c, t2_1.b, t2_1.c
+               Hash Cond: (t2_1.b = t1_2.a)
+               ->  Seq Scan on public.prt2_p2 t2_1
+                     Output: t2_1.b, t2_1.c
+               ->  Hash
+                     Output: t1_2.a, t1_2.c
+                     ->  Seq Scan on public.prt1_p2 t1_2
+                           Output: t1_2.a, t1_2.c
+                           Filter: ((t1_2.a % 25) = 0)
+         ->  Hash Join
+               Output: t1_1.a, t1_1.c, t2_2.b, t2_2.c
+               Hash Cond: (t2_2.b = t1_1.a)
+               ->  Seq Scan on public.prt2_p3 t2_2
+                     Output: t2_2.b, t2_2.c
+               ->  Hash
+                     Output: t1_1.a, t1_1.c
+                     ->  Seq Scan on public.prt1_p3 t1_1
+                           Output: t1_1.a, t1_1.c
+                           Filter: ((t1_1.a % 25) = 0)
+(34 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+ 150 | 0150 | 150 | 0150
+ 300 | 0300 | 300 | 0300
+ 450 | 0450 | 450 | 0450
+(4 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM uprt1 t1, uprt2 t2 WHERE t1.a = t2.b AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+ 150 | 0150 | 150 | 0150
+ 300 | 0300 | 300 | 0300
+ 450 | 0450 | 450 | 0450
+(4 rows)
+
+-- left outer join
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+                       QUERY PLAN                        
+---------------------------------------------------------
+ Sort
+   Output: t1.a, t1.c, t2.b, t2.c
+   Sort Key: t1.a, t2.b
+   ->  Append
+         ->  Hash Right Join
+               Output: t1.a, t1.c, t2.b, t2.c
+               Hash Cond: (t2.b = t1.a)
+               ->  Seq Scan on public.prt2_p1 t2
+                     Output: t2.b, t2.c
+               ->  Hash
+                     Output: t1.a, t1.c
+                     ->  Seq Scan on public.prt1_p1 t1
+                           Output: t1.a, t1.c
+                           Filter: ((t1.a % 25) = 0)
+         ->  Hash Right Join
+               Output: t1_2.a, t1_2.c, t2_1.b, t2_1.c
+               Hash Cond: (t2_1.b = t1_2.a)
+               ->  Seq Scan on public.prt2_p2 t2_1
+                     Output: t2_1.b, t2_1.c
+               ->  Hash
+                     Output: t1_2.a, t1_2.c
+                     ->  Seq Scan on public.prt1_p2 t1_2
+                           Output: t1_2.a, t1_2.c
+                           Filter: ((t1_2.a % 25) = 0)
+         ->  Hash Right Join
+               Output: t1_1.a, t1_1.c, t2_2.b, t2_2.c
+               Hash Cond: (t2_2.b = t1_1.a)
+               ->  Seq Scan on public.prt2_p3 t2_2
+                     Output: t2_2.b, t2_2.c
+               ->  Hash
+                     Output: t1_1.a, t1_1.c
+                     ->  Seq Scan on public.prt1_p3 t1_1
+                           Output: t1_1.a, t1_1.c
+                           Filter: ((t1_1.a % 25) = 0)
+(34 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+  50 | 0050 |     | 
+ 100 | 0100 |     | 
+ 150 | 0150 | 150 | 0150
+ 200 | 0200 |     | 
+ 250 | 0250 |     | 
+ 300 | 0300 | 300 | 0300
+ 350 | 0350 |     | 
+ 400 | 0400 |     | 
+ 450 | 0450 | 450 | 0450
+ 500 | 0500 |     | 
+ 550 | 0550 |     | 
+(12 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM uprt1 t1 LEFT JOIN uprt2 t2 ON t1.a = t2.b WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+  50 | 0050 |     | 
+ 100 | 0100 |     | 
+ 150 | 0150 | 150 | 0150
+ 200 | 0200 |     | 
+ 250 | 0250 |     | 
+ 300 | 0300 | 300 | 0300
+ 350 | 0350 |     | 
+ 400 | 0400 |     | 
+ 450 | 0450 | 450 | 0450
+ 500 | 0500 |     | 
+ 550 | 0550 |     | 
+(12 rows)
+
+-- right outer join
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Sort
+   Output: t1.a, t1.c, t2.b, t2.c
+   Sort Key: t1.a, t2.b
+   ->  Result
+         Output: t1.a, t1.c, t2.b, t2.c
+         ->  Append
+               ->  Hash Right Join
+                     Output: t2.b, t2.c, t1.a, t1.c
+                     Hash Cond: (t1.a = t2.b)
+                     ->  Seq Scan on public.prt1_p1 t1
+                           Output: t1.a, t1.c
+                     ->  Hash
+                           Output: t2.b, t2.c
+                           ->  Seq Scan on public.prt2_p1 t2
+                                 Output: t2.b, t2.c
+                                 Filter: ((t2.b % 25) = 0)
+               ->  Hash Right Join
+                     Output: t2_1.b, t2_1.c, t1_2.a, t1_2.c
+                     Hash Cond: (t1_2.a = t2_1.b)
+                     ->  Seq Scan on public.prt1_p2 t1_2
+                           Output: t1_2.a, t1_2.c
+                     ->  Hash
+                           Output: t2_1.b, t2_1.c
+                           ->  Seq Scan on public.prt2_p2 t2_1
+                                 Output: t2_1.b, t2_1.c
+                                 Filter: ((t2_1.b % 25) = 0)
+               ->  Hash Right Join
+                     Output: t2_2.b, t2_2.c, t1_1.a, t1_1.c
+                     Hash Cond: (t1_1.a = t2_2.b)
+                     ->  Seq Scan on public.prt1_p3 t1_1
+                           Output: t1_1.a, t1_1.c
+                     ->  Hash
+                           Output: t2_2.b, t2_2.c
+                           ->  Seq Scan on public.prt2_p3 t2_2
+                                 Output: t2_2.b, t2_2.c
+                                 Filter: ((t2_2.b % 25) = 0)
+(36 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+ 150 | 0150 | 150 | 0150
+ 300 | 0300 | 300 | 0300
+ 450 | 0450 | 450 | 0450
+     |      |  75 | 0075
+     |      | 225 | 0225
+     |      | 375 | 0375
+     |      | 525 | 0525
+(8 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM uprt1 t1 RIGHT JOIN uprt2 t2 ON t1.a = t2.b WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+ 150 | 0150 | 150 | 0150
+ 300 | 0300 | 300 | 0300
+ 450 | 0450 | 450 | 0450
+     |      |  75 | 0075
+     |      | 225 | 0225
+     |      | 375 | 0375
+     |      | 525 | 0525
+(8 rows)
+
+-- full outer join
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE prt1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM prt2 WHERE prt2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b;
+                            QUERY PLAN                            
+------------------------------------------------------------------
+ Sort
+   Output: prt1_p1.a, prt1_p1.c, prt2_p1.b, prt2_p1.c
+   Sort Key: prt1_p1.a, prt2_p1.b
+   ->  Append
+         ->  Hash Full Join
+               Output: prt1_p1.a, prt1_p1.c, prt2_p1.b, prt2_p1.c
+               Hash Cond: (prt1_p1.a = prt2_p1.b)
+               ->  Seq Scan on public.prt1_p1
+                     Output: prt1_p1.a, prt1_p1.c
+                     Filter: ((prt1_p1.a % 25) = 0)
+               ->  Hash
+                     Output: prt2_p1.b, prt2_p1.c
+                     ->  Seq Scan on public.prt2_p1
+                           Output: prt2_p1.b, prt2_p1.c
+                           Filter: ((prt2_p1.b % 25) = 0)
+         ->  Hash Full Join
+               Output: prt1_p2.a, prt1_p2.c, prt2_p2.b, prt2_p2.c
+               Hash Cond: (prt1_p2.a = prt2_p2.b)
+               ->  Seq Scan on public.prt1_p2
+                     Output: prt1_p2.a, prt1_p2.c
+                     Filter: ((prt1_p2.a % 25) = 0)
+               ->  Hash
+                     Output: prt2_p2.b, prt2_p2.c
+                     ->  Seq Scan on public.prt2_p2
+                           Output: prt2_p2.b, prt2_p2.c
+                           Filter: ((prt2_p2.b % 25) = 0)
+         ->  Hash Full Join
+               Output: prt1_p3.a, prt1_p3.c, prt2_p3.b, prt2_p3.c
+               Hash Cond: (prt1_p3.a = prt2_p3.b)
+               ->  Seq Scan on public.prt1_p3
+                     Output: prt1_p3.a, prt1_p3.c
+                     Filter: ((prt1_p3.a % 25) = 0)
+               ->  Hash
+                     Output: prt2_p3.b, prt2_p3.c
+                     ->  Seq Scan on public.prt2_p3
+                           Output: prt2_p3.b, prt2_p3.c
+                           Filter: ((prt2_p3.b % 25) = 0)
+(37 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE prt1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM prt2 WHERE prt2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+  50 | 0050 |     | 
+ 100 | 0100 |     | 
+ 150 | 0150 | 150 | 0150
+ 200 | 0200 |     | 
+ 250 | 0250 |     | 
+ 300 | 0300 | 300 | 0300
+ 350 | 0350 |     | 
+ 400 | 0400 |     | 
+ 450 | 0450 | 450 | 0450
+ 500 | 0500 |     | 
+ 550 | 0550 |     | 
+     |      |  75 | 0075
+     |      | 225 | 0225
+     |      | 375 | 0375
+     |      | 525 | 0525
+(16 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM uprt1 t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM uprt2 t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+  50 | 0050 |     | 
+ 100 | 0100 |     | 
+ 150 | 0150 | 150 | 0150
+ 200 | 0200 |     | 
+ 250 | 0250 |     | 
+ 300 | 0300 | 300 | 0300
+ 350 | 0350 |     | 
+ 400 | 0400 |     | 
+ 450 | 0450 | 450 | 0450
+ 500 | 0500 |     | 
+ 550 | 0550 |     | 
+     |      |  75 | 0075
+     |      | 225 | 0225
+     |      | 375 | 0375
+     |      | 525 | 0525
+(16 rows)
+
+-- Cases with non-nullable expressions in subquery results;
+-- make sure these go to null as expected
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 50 phv, * FROM prt1 WHERE prt1.a % 25 = 0) t1 FULL JOIN (SELECT 75 phv, * FROM prt2 WHERE prt2.b % 25 = 0) t2 ON (t1.a = t2.b) WHERE t1.phv = t1.b OR t2.phv = t2.b ORDER BY t1.a, t2.b;
+                            QUERY PLAN                            
+------------------------------------------------------------------
+ Sort
+   Output: prt1_p1.a, prt1_p1.c, prt2_p1.b, prt2_p1.c
+   Sort Key: prt1_p1.a, prt2_p1.b
+   ->  Append
+         ->  Hash Full Join
+               Output: prt1_p1.a, prt1_p1.c, prt2_p1.b, prt2_p1.c
+               Hash Cond: (prt1_p1.a = prt2_p1.b)
+               Filter: (((50) = prt1_p1.b) OR ((75) = prt2_p1.b))
+               ->  Seq Scan on public.prt1_p1
+                     Output: prt1_p1.a, prt1_p1.c, prt1_p1.b, 50
+                     Filter: ((prt1_p1.a % 25) = 0)
+               ->  Hash
+                     Output: prt2_p1.b, prt2_p1.c, (75)
+                     ->  Seq Scan on public.prt2_p1
+                           Output: prt2_p1.b, prt2_p1.c, 75
+                           Filter: ((prt2_p1.b % 25) = 0)
+         ->  Hash Full Join
+               Output: prt1_p2.a, prt1_p2.c, prt2_p2.b, prt2_p2.c
+               Hash Cond: (prt1_p2.a = prt2_p2.b)
+               Filter: (((50) = prt1_p2.b) OR ((75) = prt2_p2.b))
+               ->  Seq Scan on public.prt1_p2
+                     Output: prt1_p2.a, prt1_p2.c, prt1_p2.b, 50
+                     Filter: ((prt1_p2.a % 25) = 0)
+               ->  Hash
+                     Output: prt2_p2.b, prt2_p2.c, (75)
+                     ->  Seq Scan on public.prt2_p2
+                           Output: prt2_p2.b, prt2_p2.c, 75
+                           Filter: ((prt2_p2.b % 25) = 0)
+         ->  Hash Full Join
+               Output: prt1_p3.a, prt1_p3.c, prt2_p3.b, prt2_p3.c
+               Hash Cond: (prt1_p3.a = prt2_p3.b)
+               Filter: (((50) = prt1_p3.b) OR ((75) = prt2_p3.b))
+               ->  Seq Scan on public.prt1_p3
+                     Output: prt1_p3.a, prt1_p3.c, prt1_p3.b, 50
+                     Filter: ((prt1_p3.a % 25) = 0)
+               ->  Hash
+                     Output: prt2_p3.b, prt2_p3.c, (75)
+                     ->  Seq Scan on public.prt2_p3
+                           Output: prt2_p3.b, prt2_p3.c, 75
+                           Filter: ((prt2_p3.b % 25) = 0)
+(40 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 50 phv, * FROM prt1 WHERE prt1.a % 25 = 0) t1 FULL JOIN (SELECT 75 phv, * FROM prt2 WHERE prt2.b % 25 = 0) t2 ON (t1.a = t2.b) WHERE t1.phv = t1.b OR t2.phv = t2.b ORDER BY t1.a, t2.b;
+ a  |  c   | b  |  c   
+----+------+----+------
+ 50 | 0050 |    | 
+    |      | 75 | 0075
+(2 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 50 phv, * FROM uprt1 WHERE uprt1.a % 25 = 0) t1 FULL JOIN (SELECT 75 phv, * FROM uprt2 WHERE uprt2.b % 25 = 0) t2 ON (t1.a = t2.b) WHERE t1.phv = t1.b OR t2.phv = t2.b ORDER BY t1.a, t2.b;
+ a  |  c   | b  |  c   
+----+------+----+------
+ 50 | 0050 |    | 
+    |      | 75 | 0075
+(2 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t1.phv, t2.b, t2.c, t2.phv FROM (SELECT 25 phv, * FROM prt1 WHERE prt1.a % 25 = 0) t1 FULL JOIN (SELECT 50 phv, * FROM prt2 WHERE prt2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b;
+                                     QUERY PLAN                                     
+------------------------------------------------------------------------------------
+ Sort
+   Output: prt1_p1.a, prt1_p1.c, (25), prt2_p1.b, prt2_p1.c, (50)
+   Sort Key: prt1_p1.a, prt2_p1.b
+   ->  Result
+         Output: prt1_p1.a, prt1_p1.c, (25), prt2_p1.b, prt2_p1.c, (50)
+         ->  Append
+               ->  Hash Full Join
+                     Output: prt1_p1.a, prt1_p1.c, prt2_p1.b, prt2_p1.c, (25), (50)
+                     Hash Cond: (prt1_p1.a = prt2_p1.b)
+                     ->  Seq Scan on public.prt1_p1
+                           Output: prt1_p1.a, prt1_p1.c, 25
+                           Filter: ((prt1_p1.a % 25) = 0)
+                     ->  Hash
+                           Output: prt2_p1.b, prt2_p1.c, (50)
+                           ->  Seq Scan on public.prt2_p1
+                                 Output: prt2_p1.b, prt2_p1.c, 50
+                                 Filter: ((prt2_p1.b % 25) = 0)
+               ->  Hash Full Join
+                     Output: prt1_p2.a, prt1_p2.c, prt2_p2.b, prt2_p2.c, (25), (50)
+                     Hash Cond: (prt1_p2.a = prt2_p2.b)
+                     ->  Seq Scan on public.prt1_p2
+                           Output: prt1_p2.a, prt1_p2.c, 25
+                           Filter: ((prt1_p2.a % 25) = 0)
+                     ->  Hash
+                           Output: prt2_p2.b, prt2_p2.c, (50)
+                           ->  Seq Scan on public.prt2_p2
+                                 Output: prt2_p2.b, prt2_p2.c, 50
+                                 Filter: ((prt2_p2.b % 25) = 0)
+               ->  Hash Full Join
+                     Output: prt1_p3.a, prt1_p3.c, prt2_p3.b, prt2_p3.c, (25), (50)
+                     Hash Cond: (prt1_p3.a = prt2_p3.b)
+                     ->  Seq Scan on public.prt1_p3
+                           Output: prt1_p3.a, prt1_p3.c, 25
+                           Filter: ((prt1_p3.a % 25) = 0)
+                     ->  Hash
+                           Output: prt2_p3.b, prt2_p3.c, (50)
+                           ->  Seq Scan on public.prt2_p3
+                                 Output: prt2_p3.b, prt2_p3.c, 50
+                                 Filter: ((prt2_p3.b % 25) = 0)
+(39 rows)
+
+SELECT t1.a, t1.c, t1.phv, t2.b, t2.c, t2.phv FROM (SELECT 25 phv, * FROM prt1 WHERE prt1.a % 25 = 0) t1 FULL JOIN (SELECT 50 phv, * FROM prt2 WHERE prt2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b;
+  a  |  c   | phv |  b  |  c   | phv 
+-----+------+-----+-----+------+-----
+   0 | 0000 |  25 |   0 | 0000 |  50
+  50 | 0050 |  25 |     |      |    
+ 100 | 0100 |  25 |     |      |    
+ 150 | 0150 |  25 | 150 | 0150 |  50
+ 200 | 0200 |  25 |     |      |    
+ 250 | 0250 |  25 |     |      |    
+ 300 | 0300 |  25 | 300 | 0300 |  50
+ 350 | 0350 |  25 |     |      |    
+ 400 | 0400 |  25 |     |      |    
+ 450 | 0450 |  25 | 450 | 0450 |  50
+ 500 | 0500 |  25 |     |      |    
+ 550 | 0550 |  25 |     |      |    
+     |      |     |  75 | 0075 |  50
+     |      |     | 225 | 0225 |  50
+     |      |     | 375 | 0375 |  50
+     |      |     | 525 | 0525 |  50
+(16 rows)
+
+SELECT t1.a, t1.c, t1.phv, t2.b, t2.c, t2.phv FROM (SELECT 25 phv, * FROM uprt1 WHERE uprt1.a % 25 = 0) t1 FULL JOIN (SELECT 50 phv, * FROM uprt2 WHERE uprt2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b;
+  a  |  c   | phv |  b  |  c   | phv 
+-----+------+-----+-----+------+-----
+   0 | 0000 |  25 |   0 | 0000 |  50
+  50 | 0050 |  25 |     |      |    
+ 100 | 0100 |  25 |     |      |    
+ 150 | 0150 |  25 | 150 | 0150 |  50
+ 200 | 0200 |  25 |     |      |    
+ 250 | 0250 |  25 |     |      |    
+ 300 | 0300 |  25 | 300 | 0300 |  50
+ 350 | 0350 |  25 |     |      |    
+ 400 | 0400 |  25 |     |      |    
+ 450 | 0450 |  25 | 450 | 0450 |  50
+ 500 | 0500 |  25 |     |      |    
+ 550 | 0550 |  25 |     |      |    
+     |      |     |  75 | 0075 |  50
+     |      |     | 225 | 0225 |  50
+     |      |     | 375 | 0375 |  50
+     |      |     | 525 | 0525 |  50
+(16 rows)
+
+-- Join with pruned partitions from joining relations
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+                               QUERY PLAN                               
+------------------------------------------------------------------------
+ Sort
+   Output: t1.a, t1.c, t2.b, t2.c
+   Sort Key: t1.a
+   ->  Append
+         ->  Hash Join
+               Output: t1.a, t1.c, t2.b, t2.c
+               Hash Cond: (t2.b = t1.a)
+               ->  Seq Scan on public.prt2_p2 t2
+                     Output: t2.b, t2.c
+                     Filter: (t2.b > 250)
+               ->  Hash
+                     Output: t1.a, t1.c
+                     ->  Seq Scan on public.prt1_p2 t1
+                           Output: t1.a, t1.c
+                           Filter: ((t1.a < 450) AND ((t1.a % 25) = 0))
+(15 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+ 300 | 0300 | 300 | 0300
+(1 row)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM uprt1 t1, uprt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+ 300 | 0300 | 300 | 0300
+(1 row)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+                                    QUERY PLAN                                    
+----------------------------------------------------------------------------------
+ Sort
+   Output: prt1_p1.a, prt1_p1.c, b, c
+   Sort Key: prt1_p1.a, b
+   ->  Append
+         ->  Nested Loop Left Join
+               Output: prt1_p1.a, prt1_p1.c, b, c
+               Join Filter: (prt1_p1.a = b)
+               ->  Seq Scan on public.prt1_p1
+                     Output: prt1_p1.a, prt1_p1.c
+                     Filter: ((prt1_p1.a < 450) AND ((prt1_p1.a % 25) = 0))
+               ->  Result
+                     Output: b, c
+                     One-Time Filter: false
+         ->  Hash Right Join
+               Output: prt1_p2.a, prt1_p2.c, prt2_p2.b, prt2_p2.c
+               Hash Cond: (prt2_p2.b = prt1_p2.a)
+               ->  Seq Scan on public.prt2_p2
+                     Output: prt2_p2.b, prt2_p2.c
+                     Filter: (prt2_p2.b > 250)
+               ->  Hash
+                     Output: prt1_p2.a, prt1_p2.c
+                     ->  Seq Scan on public.prt1_p2
+                           Output: prt1_p2.a, prt1_p2.c
+                           Filter: ((prt1_p2.a < 450) AND ((prt1_p2.a % 25) = 0))
+(24 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |     | 
+  50 | 0050 |     | 
+ 100 | 0100 |     | 
+ 150 | 0150 |     | 
+ 200 | 0200 |     | 
+ 250 | 0250 |     | 
+ 300 | 0300 | 300 | 0300
+ 350 | 0350 |     | 
+ 400 | 0400 |     | 
+(9 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM uprt1 WHERE a < 450) t1 LEFT JOIN (SELECT * FROM uprt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |     | 
+  50 | 0050 |     | 
+ 100 | 0100 |     | 
+ 150 | 0150 |     | 
+ 200 | 0200 |     | 
+ 250 | 0250 |     | 
+ 300 | 0300 | 300 | 0300
+ 350 | 0350 |     | 
+ 400 | 0400 |     | 
+(9 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450) t1 RIGHT JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t2.a % 25 = 0 ORDER BY t1.a, t2.b;
+                                       QUERY PLAN                                       
+----------------------------------------------------------------------------------------
+ Sort
+   Output: prt1_p2.a, prt1_p2.c, prt2_p2.b, prt2_p2.c
+   Sort Key: prt1_p2.a, prt2_p2.b
+   ->  Result
+         Output: prt1_p2.a, prt1_p2.c, prt2_p2.b, prt2_p2.c
+         ->  Append
+               ->  Hash Right Join
+                     Output: prt2_p2.b, prt2_p2.c, prt1_p2.a, prt1_p2.c
+                     Hash Cond: (prt1_p2.a = prt2_p2.b)
+                     ->  Seq Scan on public.prt1_p2
+                           Output: prt1_p2.a, prt1_p2.c
+                           Filter: (prt1_p2.a < 450)
+                     ->  Hash
+                           Output: prt2_p2.b, prt2_p2.c
+                           ->  Seq Scan on public.prt2_p2
+                                 Output: prt2_p2.b, prt2_p2.c
+                                 Filter: ((prt2_p2.b > 250) AND ((prt2_p2.a % 25) = 0))
+               ->  Nested Loop Left Join
+                     Output: prt2_p3.b, prt2_p3.c, a, c
+                     Join Filter: (a = prt2_p3.b)
+                     ->  Seq Scan on public.prt2_p3
+                           Output: prt2_p3.b, prt2_p3.c
+                           Filter: ((prt2_p3.b > 250) AND ((prt2_p3.a % 25) = 0))
+                     ->  Result
+                           Output: a, c
+                           One-Time Filter: false
+(26 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450) t1 RIGHT JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t2.a % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+ 300 | 0300 | 300 | 0300
+     |      | 375 | 0375
+     |      | 450 | 0450
+     |      | 525 | 0525
+(4 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM uprt1 WHERE a < 450) t1 RIGHT JOIN (SELECT * FROM uprt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t2.a % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+ 300 | 0300 | 300 | 0300
+     |      | 375 | 0375
+     |      | 450 | 0450
+     |      | 525 | 0525
+(4 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450 AND a % 25 = 0) t1 FULL JOIN (SELECT * FROM prt2 WHERE b > 250 AND b % 25 = 0) t2 ON t1.a = t2.b ORDER BY t1.a, t2.b;
+                                    QUERY PLAN                                    
+----------------------------------------------------------------------------------
+ Sort
+   Output: prt1_p1.a, prt1_p1.c, b, c
+   Sort Key: prt1_p1.a, b
+   ->  Append
+         ->  Hash Full Join
+               Output: prt1_p1.a, prt1_p1.c, b, c
+               Hash Cond: (prt1_p1.a = b)
+               ->  Seq Scan on public.prt1_p1
+                     Output: prt1_p1.a, prt1_p1.c
+                     Filter: ((prt1_p1.a < 450) AND ((prt1_p1.a % 25) = 0))
+               ->  Hash
+                     Output: b, c
+                     ->  Result
+                           Output: b, c
+                           One-Time Filter: false
+         ->  Hash Full Join
+               Output: prt1_p2.a, prt1_p2.c, prt2_p2.b, prt2_p2.c
+               Hash Cond: (prt1_p2.a = prt2_p2.b)
+               ->  Seq Scan on public.prt1_p2
+                     Output: prt1_p2.a, prt1_p2.c
+                     Filter: ((prt1_p2.a < 450) AND ((prt1_p2.a % 25) = 0))
+               ->  Hash
+                     Output: prt2_p2.b, prt2_p2.c
+                     ->  Seq Scan on public.prt2_p2
+                           Output: prt2_p2.b, prt2_p2.c
+                           Filter: ((prt2_p2.b > 250) AND ((prt2_p2.b % 25) = 0))
+         ->  Hash Full Join
+               Output: a, c, prt2_p3.b, prt2_p3.c
+               Hash Cond: (prt2_p3.b = a)
+               ->  Seq Scan on public.prt2_p3
+                     Output: prt2_p3.b, prt2_p3.c
+                     Filter: ((prt2_p3.b > 250) AND ((prt2_p3.b % 25) = 0))
+               ->  Hash
+                     Output: a, c
+                     ->  Result
+                           Output: a, c
+                           One-Time Filter: false
+(37 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450 AND a % 25 = 0) t1 FULL JOIN (SELECT * FROM prt2 WHERE b > 250 AND b % 25 = 0) t2 ON t1.a = t2.b ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |     | 
+  50 | 0050 |     | 
+ 100 | 0100 |     | 
+ 150 | 0150 |     | 
+ 200 | 0200 |     | 
+ 250 | 0250 |     | 
+ 300 | 0300 | 300 | 0300
+ 350 | 0350 |     | 
+ 400 | 0400 |     | 
+     |      | 375 | 0375
+     |      | 450 | 0450
+     |      | 525 | 0525
+(12 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM uprt1 WHERE a < 450 AND a % 25 = 0) t1 FULL JOIN (SELECT * FROM uprt2 WHERE b > 250 AND b % 25 = 0) t2 ON t1.a = t2.b ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |     | 
+  50 | 0050 |     | 
+ 100 | 0100 |     | 
+ 150 | 0150 |     | 
+ 200 | 0200 |     | 
+ 250 | 0250 |     | 
+ 300 | 0300 | 300 | 0300
+ 350 | 0350 |     | 
+ 400 | 0400 |     | 
+     |      | 375 | 0375
+     |      | 450 | 0450
+     |      | 525 | 0525
+(12 rows)
+
+-- Semi-join
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b % 25 = 0) AND t1.a % 25 = 0 ORDER BY t1.a;
+                     QUERY PLAN                     
+----------------------------------------------------
+ Merge Append
+   Sort Key: t1.a
+   ->  Merge Semi Join
+         Output: t1.a, t1.b, t1.c
+         Merge Cond: (t1.a = t1_3.b)
+         ->  Sort
+               Output: t1.a, t1.b, t1.c
+               Sort Key: t1.a
+               ->  Seq Scan on public.prt1_p1 t1
+                     Output: t1.a, t1.b, t1.c
+                     Filter: ((t1.a % 25) = 0)
+         ->  Sort
+               Output: t1_3.b
+               Sort Key: t1_3.b
+               ->  Seq Scan on public.prt2_p1 t1_3
+                     Output: t1_3.b
+                     Filter: ((t1_3.b % 25) = 0)
+   ->  Merge Semi Join
+         Output: t1_2.a, t1_2.b, t1_2.c
+         Merge Cond: (t1_2.a = t1_4.b)
+         ->  Sort
+               Output: t1_2.a, t1_2.b, t1_2.c
+               Sort Key: t1_2.a
+               ->  Seq Scan on public.prt1_p2 t1_2
+                     Output: t1_2.a, t1_2.b, t1_2.c
+                     Filter: ((t1_2.a % 25) = 0)
+         ->  Sort
+               Output: t1_4.b
+               Sort Key: t1_4.b
+               ->  Seq Scan on public.prt2_p2 t1_4
+                     Output: t1_4.b
+                     Filter: ((t1_4.b % 25) = 0)
+   ->  Merge Semi Join
+         Output: t1_1.a, t1_1.b, t1_1.c
+         Merge Cond: (t1_1.a = t1_5.b)
+         ->  Sort
+               Output: t1_1.a, t1_1.b, t1_1.c
+               Sort Key: t1_1.a
+               ->  Seq Scan on public.prt1_p3 t1_1
+                     Output: t1_1.a, t1_1.b, t1_1.c
+                     Filter: ((t1_1.a % 25) = 0)
+         ->  Sort
+               Output: t1_5.b
+               Sort Key: t1_5.b
+               ->  Seq Scan on public.prt2_p3 t1_5
+                     Output: t1_5.b
+                     Filter: ((t1_5.b % 25) = 0)
+(47 rows)
+
+SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b % 25 = 0) AND t1.a % 25 = 0 ORDER BY t1.a;
+  a  |  b  |  c   
+-----+-----+------
+   0 |   0 | 0000
+ 150 | 150 | 0150
+ 300 | 300 | 0300
+ 450 | 450 | 0450
+(4 rows)
+
+SELECT t1.* FROM uprt1 t1 WHERE t1.a IN (SELECT t1.b FROM uprt2 t1 WHERE t1.b % 25 = 0) AND t1.a % 25 = 0 ORDER BY t1.a;
+  a  |  b  |  c   
+-----+-----+------
+   0 |   0 | 0000
+ 150 | 150 | 0150
+ 300 | 300 | 0300
+ 450 | 450 | 0450
+(4 rows)
+
+-- lateral reference
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT * FROM prt1 t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss
+			  ON t1.a = ss.t2a WHERE t1.a % 25 = 0 ORDER BY t1.a;
+                                             QUERY PLAN                                              
+-----------------------------------------------------------------------------------------------------
+ Sort
+   Output: t1.a, t1.b, t1.c, t2.a, t3.a, (LEAST(t1.a, t2.a, t3.a)), t1.a
+   Sort Key: t1.a
+   ->  Result
+         Output: t1.a, t1.b, t1.c, t2.a, t3.a, (LEAST(t1.a, t2.a, t3.a)), t1.a
+         ->  Append
+               ->  Nested Loop Left Join
+                     Output: t1.a, t1.b, t1.c, t2.a, t3.a, (LEAST(t1.a, t2.a, t3.a))
+                     ->  Seq Scan on public.prt1_p1 t1
+                           Output: t1.a, t1.b, t1.c
+                           Filter: ((t1.a % 25) = 0)
+                     ->  Hash Join
+                           Output: t2.a, t3.a, LEAST(t1.a, t2.a, t3.a)
+                           Hash Cond: (t3.b = t2.a)
+                           ->  Seq Scan on public.prt2_p1 t3
+                                 Output: t3.a, t3.b
+                           ->  Hash
+                                 Output: t2.a
+                                 ->  Seq Scan on public.prt1_p1 t2
+                                       Output: t2.a
+                                       Filter: (t1.a = t2.a)
+               ->  Nested Loop Left Join
+                     Output: t1_2.a, t1_2.b, t1_2.c, t2_2.a, t3_1.a, (LEAST(t1_2.a, t2_2.a, t3_1.a))
+                     ->  Seq Scan on public.prt1_p2 t1_2
+                           Output: t1_2.a, t1_2.b, t1_2.c
+                           Filter: ((t1_2.a % 25) = 0)
+                     ->  Hash Join
+                           Output: t2_2.a, t3_1.a, LEAST(t1_2.a, t2_2.a, t3_1.a)
+                           Hash Cond: (t3_1.b = t2_2.a)
+                           ->  Seq Scan on public.prt2_p2 t3_1
+                                 Output: t3_1.a, t3_1.b
+                           ->  Hash
+                                 Output: t2_2.a
+                                 ->  Seq Scan on public.prt1_p2 t2_2
+                                       Output: t2_2.a
+                                       Filter: (t1_2.a = t2_2.a)
+               ->  Nested Loop Left Join
+                     Output: t1_1.a, t1_1.b, t1_1.c, t2_1.a, t3_2.a, (LEAST(t1_1.a, t2_1.a, t3_2.a))
+                     ->  Seq Scan on public.prt1_p3 t1_1
+                           Output: t1_1.a, t1_1.b, t1_1.c
+                           Filter: ((t1_1.a % 25) = 0)
+                     ->  Hash Join
+                           Output: t2_1.a, t3_2.a, LEAST(t1_1.a, t2_1.a, t3_2.a)
+                           Hash Cond: (t3_2.b = t2_1.a)
+                           ->  Seq Scan on public.prt2_p3 t3_2
+                                 Output: t3_2.a, t3_2.b
+                           ->  Hash
+                                 Output: t2_1.a
+                                 ->  Seq Scan on public.prt1_p3 t2_1
+                                       Output: t2_1.a
+                                       Filter: (t1_1.a = t2_1.a)
+(51 rows)
+
+SELECT * FROM prt1 t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss
+			  ON t1.a = ss.t2a WHERE t1.a % 25 = 0 ORDER BY t1.a;
+  a  |  b  |  c   | t2a | t3a | least 
+-----+-----+------+-----+-----+-------
+   0 |   0 | 0000 |   0 |   0 |     0
+  50 |  50 | 0050 |     |     |      
+ 100 | 100 | 0100 |     |     |      
+ 150 | 150 | 0150 | 150 | 150 |   150
+ 200 | 200 | 0200 |     |     |      
+ 250 | 250 | 0250 |     |     |      
+ 300 | 300 | 0300 | 300 | 300 |   300
+ 350 | 350 | 0350 |     |     |      
+ 400 | 400 | 0400 |     |     |      
+ 450 | 450 | 0450 | 450 | 450 |   450
+ 500 | 500 | 0500 |     |     |      
+ 550 | 550 | 0550 |     |     |      
+(12 rows)
+
+SELECT * FROM uprt1 t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM uprt1 t2 JOIN uprt2 t3 ON (t2.a = t3.b)) ss
+			  ON t1.a = ss.t2a WHERE t1.a % 25 = 0 ORDER BY t1.a;
+  a  |  b  |  c   | t2a | t3a | least 
+-----+-----+------+-----+-----+-------
+   0 |   0 | 0000 |   0 |   0 |     0
+  50 |  50 | 0050 |     |     |      
+ 100 | 100 | 0100 |     |     |      
+ 150 | 150 | 0150 | 150 | 150 |   150
+ 200 | 200 | 0200 |     |     |      
+ 250 | 250 | 0250 |     |     |      
+ 300 | 300 | 0300 | 300 | 300 |   300
+ 350 | 350 | 0350 |     |     |      
+ 400 | 400 | 0400 |     |     |      
+ 450 | 450 | 0450 | 450 | 450 |   450
+ 500 | 500 | 0500 |     |     |      
+ 550 | 550 | 0550 |     |     |      
+(12 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT * FROM prt1 t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss
+			  ON t1.b = ss.t2a WHERE t1.a % 25 = 0 ORDER BY t1.a;
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ Sort
+   Output: t1.a, t1.b, t1.c, t2.a, t3.a, (LEAST(t1.a, t2.a, t3.a)), t1.a
+   Sort Key: t1.a
+   ->  Nested Loop Left Join
+         Output: t1.a, t1.b, t1.c, t2.a, t3.a, (LEAST(t1.a, t2.a, t3.a)), t1.a
+         ->  Append
+               ->  Seq Scan on public.prt1 t1
+                     Output: t1.a, t1.b, t1.c
+                     Filter: ((t1.a % 25) = 0)
+               ->  Seq Scan on public.prt1_p1 t1_1
+                     Output: t1_1.a, t1_1.b, t1_1.c
+                     Filter: ((t1_1.a % 25) = 0)
+               ->  Seq Scan on public.prt1_p3 t1_2
+                     Output: t1_2.a, t1_2.b, t1_2.c
+                     Filter: ((t1_2.a % 25) = 0)
+               ->  Seq Scan on public.prt1_p2 t1_3
+                     Output: t1_3.a, t1_3.b, t1_3.c
+                     Filter: ((t1_3.a % 25) = 0)
+         ->  Append
+               ->  Hash Join
+                     Output: t2.a, t3.a, LEAST(t1.a, t2.a, t3.a)
+                     Hash Cond: (t3.b = t2.a)
+                     ->  Seq Scan on public.prt2_p1 t3
+                           Output: t3.a, t3.b
+                     ->  Hash
+                           Output: t2.a
+                           ->  Seq Scan on public.prt1_p1 t2
+                                 Output: t2.a
+                                 Filter: (t1.b = t2.a)
+               ->  Hash Join
+                     Output: t2_2.a, t3_1.a, LEAST(t1.a, t2_2.a, t3_1.a)
+                     Hash Cond: (t3_1.b = t2_2.a)
+                     ->  Seq Scan on public.prt2_p2 t3_1
+                           Output: t3_1.a, t3_1.b
+                     ->  Hash
+                           Output: t2_2.a
+                           ->  Seq Scan on public.prt1_p2 t2_2
+                                 Output: t2_2.a
+                                 Filter: (t1.b = t2_2.a)
+               ->  Hash Join
+                     Output: t2_1.a, t3_2.a, LEAST(t1.a, t2_1.a, t3_2.a)
+                     Hash Cond: (t3_2.b = t2_1.a)
+                     ->  Seq Scan on public.prt2_p3 t3_2
+                           Output: t3_2.a, t3_2.b
+                     ->  Hash
+                           Output: t2_1.a
+                           ->  Seq Scan on public.prt1_p3 t2_1
+                                 Output: t2_1.a
+                                 Filter: (t1.b = t2_1.a)
+(49 rows)
+
+SELECT * FROM prt1 t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss
+			  ON t1.b = ss.t2a WHERE t1.a % 25 = 0 ORDER BY t1.a;
+  a  |  b  |  c   | t2a | t3a | least 
+-----+-----+------+-----+-----+-------
+   0 |   0 | 0000 |   0 |   0 |     0
+  50 |  50 | 0050 |     |     |      
+ 100 | 100 | 0100 |     |     |      
+ 150 | 150 | 0150 | 150 | 150 |   150
+ 200 | 200 | 0200 |     |     |      
+ 250 | 250 | 0250 |     |     |      
+ 300 | 300 | 0300 | 300 | 300 |   300
+ 350 | 350 | 0350 |     |     |      
+ 400 | 400 | 0400 |     |     |      
+ 450 | 450 | 0450 | 450 | 450 |   450
+ 500 | 500 | 0500 |     |     |      
+ 550 | 550 | 0550 |     |     |      
+(12 rows)
+
+SELECT * FROM uprt1 t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM uprt1 t2 JOIN uprt2 t3 ON (t2.a = t3.b)) ss
+			  ON t1.b = ss.t2a WHERE t1.a % 25 = 0 ORDER BY t1.a;
+  a  |  b  |  c   | t2a | t3a | least 
+-----+-----+------+-----+-----+-------
+   0 |   0 | 0000 |   0 |   0 |     0
+  50 |  50 | 0050 |     |     |      
+ 100 | 100 | 0100 |     |     |      
+ 150 | 150 | 0150 | 150 | 150 |   150
+ 200 | 200 | 0200 |     |     |      
+ 250 | 250 | 0250 |     |     |      
+ 300 | 300 | 0300 | 300 | 300 |   300
+ 350 | 350 | 0350 |     |     |      
+ 400 | 400 | 0400 |     |     |      
+ 450 | 450 | 0450 | 450 | 450 |   450
+ 500 | 500 | 0500 |     |     |      
+ 550 | 550 | 0550 |     |     |      
+(12 rows)
+
+--
+-- partitioned by expression
+--
+CREATE TABLE prt1_e (a int, b int, c varchar) PARTITION BY RANGE(((a + b)/2));
+CREATE TABLE prt1_e_p1 PARTITION OF prt1_e FOR VALUES FROM (0) TO (250);
+CREATE TABLE prt1_e_p2 PARTITION OF prt1_e FOR VALUES FROM (250) TO (500);
+CREATE TABLE prt1_e_p3 PARTITION OF prt1_e FOR VALUES FROM (500) TO (600);
+INSERT INTO prt1_e SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 2) i;
+ANALYZE prt1_e;
+ANALYZE prt1_e_p1;
+ANALYZE prt1_e_p2;
+ANALYZE prt1_e_p3;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE uprt1_e AS SELECT * FROM prt1_e;
+CREATE TABLE prt2_e (a int, b int, c varchar) PARTITION BY RANGE(((b + a)/2));
+CREATE TABLE prt2_e_p1 PARTITION OF prt2_e FOR VALUES FROM (0) TO (250);
+CREATE TABLE prt2_e_p2 PARTITION OF prt2_e FOR VALUES FROM (250) TO (500);
+CREATE TABLE prt2_e_p3 PARTITION OF prt2_e FOR VALUES FROM (500) TO (600);
+INSERT INTO prt2_e SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 3) i;
+ANALYZE prt2_e;
+ANALYZE prt2_e_p1;
+ANALYZE prt2_e_p2;
+ANALYZE prt2_e_p3;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE uprt2_e AS SELECT * FROM prt2_e;
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_e t1, prt2_e t2 WHERE (t1.a + t1.b)/2 = (t2.b + t2.a)/2 AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+                                  QUERY PLAN                                  
+------------------------------------------------------------------------------
+ Sort
+   Output: t1.a, t1.c, t2.b, t2.c
+   Sort Key: t1.a, t2.b
+   ->  Append
+         ->  Hash Join
+               Output: t1.a, t1.c, t2.b, t2.c
+               Hash Cond: (((t2.b + t2.a) / 2) = ((t1.a + t1.b) / 2))
+               ->  Seq Scan on public.prt2_e_p1 t2
+                     Output: t2.b, t2.c, t2.a
+               ->  Hash
+                     Output: t1.a, t1.c, t1.b
+                     ->  Seq Scan on public.prt1_e_p1 t1
+                           Output: t1.a, t1.c, t1.b
+                           Filter: ((t1.a % 25) = 0)
+         ->  Hash Join
+               Output: t1_1.a, t1_1.c, t2_1.b, t2_1.c
+               Hash Cond: (((t2_1.b + t2_1.a) / 2) = ((t1_1.a + t1_1.b) / 2))
+               ->  Seq Scan on public.prt2_e_p2 t2_1
+                     Output: t2_1.b, t2_1.c, t2_1.a
+               ->  Hash
+                     Output: t1_1.a, t1_1.c, t1_1.b
+                     ->  Seq Scan on public.prt1_e_p2 t1_1
+                           Output: t1_1.a, t1_1.c, t1_1.b
+                           Filter: ((t1_1.a % 25) = 0)
+         ->  Hash Join
+               Output: t1_2.a, t1_2.c, t2_2.b, t2_2.c
+               Hash Cond: (((t2_2.b + t2_2.a) / 2) = ((t1_2.a + t1_2.b) / 2))
+               ->  Seq Scan on public.prt2_e_p3 t2_2
+                     Output: t2_2.b, t2_2.c, t2_2.a
+               ->  Hash
+                     Output: t1_2.a, t1_2.c, t1_2.b
+                     ->  Seq Scan on public.prt1_e_p3 t1_2
+                           Output: t1_2.a, t1_2.c, t1_2.b
+                           Filter: ((t1_2.a % 25) = 0)
+(34 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_e t1, prt2_e t2 WHERE (t1.a + t1.b)/2 = (t2.b + t2.a)/2 AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+ 150 | 0150 | 150 | 0150
+ 300 | 0300 | 300 | 0300
+ 450 | 0450 | 450 | 0450
+(4 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM uprt1_e t1, uprt2_e t2 WHERE (t1.a + t1.b)/2 = (t2.b + t2.a)/2 AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+ 150 | 0150 | 150 | 0150
+ 300 | 0300 | 300 | 0300
+ 450 | 0450 | 450 | 0450
+(4 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_e t1 LEFT JOIN prt2_e t2 ON (t1.a + t1.b)/2 = (t2.b + t2.a)/2 WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+                                  QUERY PLAN                                  
+------------------------------------------------------------------------------
+ Sort
+   Output: t1.a, t1.c, t2.b, t2.c
+   Sort Key: t1.a, t2.b
+   ->  Append
+         ->  Hash Right Join
+               Output: t1.a, t1.c, t2.b, t2.c
+               Hash Cond: (((t2.b + t2.a) / 2) = ((t1.a + t1.b) / 2))
+               ->  Seq Scan on public.prt2_e_p1 t2
+                     Output: t2.b, t2.c, t2.a
+               ->  Hash
+                     Output: t1.a, t1.c, t1.b
+                     ->  Seq Scan on public.prt1_e_p1 t1
+                           Output: t1.a, t1.c, t1.b
+                           Filter: ((t1.a % 25) = 0)
+         ->  Hash Right Join
+               Output: t1_1.a, t1_1.c, t2_1.b, t2_1.c
+               Hash Cond: (((t2_1.b + t2_1.a) / 2) = ((t1_1.a + t1_1.b) / 2))
+               ->  Seq Scan on public.prt2_e_p2 t2_1
+                     Output: t2_1.b, t2_1.c, t2_1.a
+               ->  Hash
+                     Output: t1_1.a, t1_1.c, t1_1.b
+                     ->  Seq Scan on public.prt1_e_p2 t1_1
+                           Output: t1_1.a, t1_1.c, t1_1.b
+                           Filter: ((t1_1.a % 25) = 0)
+         ->  Hash Right Join
+               Output: t1_2.a, t1_2.c, t2_2.b, t2_2.c
+               Hash Cond: (((t2_2.b + t2_2.a) / 2) = ((t1_2.a + t1_2.b) / 2))
+               ->  Seq Scan on public.prt2_e_p3 t2_2
+                     Output: t2_2.b, t2_2.c, t2_2.a
+               ->  Hash
+                     Output: t1_2.a, t1_2.c, t1_2.b
+                     ->  Seq Scan on public.prt1_e_p3 t1_2
+                           Output: t1_2.a, t1_2.c, t1_2.b
+                           Filter: ((t1_2.a % 25) = 0)
+(34 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_e t1 LEFT JOIN prt2_e t2 ON (t1.a + t1.b)/2 = (t2.b + t2.a)/2 WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+  50 | 0050 |     | 
+ 100 | 0100 |     | 
+ 150 | 0150 | 150 | 0150
+ 200 | 0200 |     | 
+ 250 | 0250 |     | 
+ 300 | 0300 | 300 | 0300
+ 350 | 0350 |     | 
+ 400 | 0400 |     | 
+ 450 | 0450 | 450 | 0450
+ 500 | 0500 |     | 
+ 550 | 0550 |     | 
+(12 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM uprt1_e t1 LEFT JOIN uprt2_e t2 ON (t1.a + t1.b)/2 = (t2.b + t2.a)/2 WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+  50 | 0050 |     | 
+ 100 | 0100 |     | 
+ 150 | 0150 | 150 | 0150
+ 200 | 0200 |     | 
+ 250 | 0250 |     | 
+ 300 | 0300 | 300 | 0300
+ 350 | 0350 |     | 
+ 400 | 0400 |     | 
+ 450 | 0450 | 450 | 0450
+ 500 | 0500 |     | 
+ 550 | 0550 |     | 
+(12 rows)
+
+--
+-- N-way join
+--
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t3 WHERE t1.a = t2.b AND t1.a = (t3.a + t3.b)/2 AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+                                     QUERY PLAN                                     
+------------------------------------------------------------------------------------
+ Sort
+   Output: t1.a, t1.c, t2.b, t2.c, ((t3.a + t3.b)), t3.c
+   Sort Key: t1.a
+   ->  Result
+         Output: t1.a, t1.c, t2.b, t2.c, (t3.a + t3.b), t3.c
+         ->  Append
+               ->  Hash Join
+                     Output: t1.a, t1.c, t2.b, t2.c, t3.a, t3.b, t3.c
+                     Hash Cond: (((t3.a + t3.b) / 2) = t1.a)
+                     ->  Seq Scan on public.prt1_e_p1 t3
+                           Output: t3.a, t3.b, t3.c
+                     ->  Hash
+                           Output: t1.a, t1.c, t2.b, t2.c
+                           ->  Hash Join
+                                 Output: t1.a, t1.c, t2.b, t2.c
+                                 Hash Cond: (t2.b = t1.a)
+                                 ->  Seq Scan on public.prt2_p1 t2
+                                       Output: t2.b, t2.c
+                                 ->  Hash
+                                       Output: t1.a, t1.c
+                                       ->  Seq Scan on public.prt1_p1 t1
+                                             Output: t1.a, t1.c
+                                             Filter: ((t1.a % 25) = 0)
+               ->  Hash Join
+                     Output: t1_2.a, t1_2.c, t2_1.b, t2_1.c, t3_1.a, t3_1.b, t3_1.c
+                     Hash Cond: (((t3_1.a + t3_1.b) / 2) = t1_2.a)
+                     ->  Seq Scan on public.prt1_e_p2 t3_1
+                           Output: t3_1.a, t3_1.b, t3_1.c
+                     ->  Hash
+                           Output: t1_2.a, t1_2.c, t2_1.b, t2_1.c
+                           ->  Hash Join
+                                 Output: t1_2.a, t1_2.c, t2_1.b, t2_1.c
+                                 Hash Cond: (t2_1.b = t1_2.a)
+                                 ->  Seq Scan on public.prt2_p2 t2_1
+                                       Output: t2_1.b, t2_1.c
+                                 ->  Hash
+                                       Output: t1_2.a, t1_2.c
+                                       ->  Seq Scan on public.prt1_p2 t1_2
+                                             Output: t1_2.a, t1_2.c
+                                             Filter: ((t1_2.a % 25) = 0)
+               ->  Hash Join
+                     Output: t1_1.a, t1_1.c, t2_2.b, t2_2.c, t3_2.a, t3_2.b, t3_2.c
+                     Hash Cond: (((t3_2.a + t3_2.b) / 2) = t1_1.a)
+                     ->  Seq Scan on public.prt1_e_p3 t3_2
+                           Output: t3_2.a, t3_2.b, t3_2.c
+                     ->  Hash
+                           Output: t1_1.a, t1_1.c, t2_2.b, t2_2.c
+                           ->  Hash Join
+                                 Output: t1_1.a, t1_1.c, t2_2.b, t2_2.c
+                                 Hash Cond: (t2_2.b = t1_1.a)
+                                 ->  Seq Scan on public.prt2_p3 t2_2
+                                       Output: t2_2.b, t2_2.c
+                                 ->  Hash
+                                       Output: t1_1.a, t1_1.c
+                                       ->  Seq Scan on public.prt1_p3 t1_1
+                                             Output: t1_1.a, t1_1.c
+                                             Filter: ((t1_1.a % 25) = 0)
+(57 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t3 WHERE t1.a = t2.b AND t1.a = (t3.a + t3.b)/2 AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   | ?column? |  c   
+-----+------+-----+------+----------+------
+   0 | 0000 |   0 | 0000 |        0 | 0000
+ 150 | 0150 | 150 | 0150 |      300 | 0150
+ 300 | 0300 | 300 | 0300 |      600 | 0300
+ 450 | 0450 | 450 | 0450 |      900 | 0450
+(4 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM uprt1 t1, uprt2 t2, uprt1_e t3 WHERE t1.a = t2.b AND t1.a = (t3.a + t3.b)/2 AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   | ?column? |  c   
+-----+------+-----+------+----------+------
+   0 | 0000 |   0 | 0000 |        0 | 0000
+ 150 | 0150 | 150 | 0150 |      300 | 0150
+ 300 | 0300 | 300 | 0300 |      600 | 0300
+ 450 | 0450 | 450 | 0450 |      900 | 0450
+(4 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) LEFT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+                                     QUERY PLAN                                     
+------------------------------------------------------------------------------------
+ Sort
+   Output: t1.a, t1.c, t2.b, t2.c, ((t3.a + t3.b)), t3.c
+   Sort Key: t1.a, t2.b, ((t3.a + t3.b))
+   ->  Result
+         Output: t1.a, t1.c, t2.b, t2.c, (t3.a + t3.b), t3.c
+         ->  Append
+               ->  Hash Right Join
+                     Output: t1.a, t1.c, t2.b, t2.c, t3.a, t3.b, t3.c
+                     Hash Cond: (((t3.a + t3.b) / 2) = t1.a)
+                     ->  Seq Scan on public.prt1_e_p1 t3
+                           Output: t3.a, t3.b, t3.c
+                     ->  Hash
+                           Output: t1.a, t1.c, t2.b, t2.c
+                           ->  Hash Right Join
+                                 Output: t1.a, t1.c, t2.b, t2.c
+                                 Hash Cond: (t2.b = t1.a)
+                                 ->  Seq Scan on public.prt2_p1 t2
+                                       Output: t2.b, t2.c
+                                 ->  Hash
+                                       Output: t1.a, t1.c
+                                       ->  Seq Scan on public.prt1_p1 t1
+                                             Output: t1.a, t1.c
+                                             Filter: ((t1.a % 25) = 0)
+               ->  Hash Right Join
+                     Output: t1_2.a, t1_2.c, t2_1.b, t2_1.c, t3_1.a, t3_1.b, t3_1.c
+                     Hash Cond: (((t3_1.a + t3_1.b) / 2) = t1_2.a)
+                     ->  Seq Scan on public.prt1_e_p2 t3_1
+                           Output: t3_1.a, t3_1.b, t3_1.c
+                     ->  Hash
+                           Output: t1_2.a, t1_2.c, t2_1.b, t2_1.c
+                           ->  Hash Right Join
+                                 Output: t1_2.a, t1_2.c, t2_1.b, t2_1.c
+                                 Hash Cond: (t2_1.b = t1_2.a)
+                                 ->  Seq Scan on public.prt2_p2 t2_1
+                                       Output: t2_1.b, t2_1.c
+                                 ->  Hash
+                                       Output: t1_2.a, t1_2.c
+                                       ->  Seq Scan on public.prt1_p2 t1_2
+                                             Output: t1_2.a, t1_2.c
+                                             Filter: ((t1_2.a % 25) = 0)
+               ->  Hash Right Join
+                     Output: t1_1.a, t1_1.c, t2_2.b, t2_2.c, t3_2.a, t3_2.b, t3_2.c
+                     Hash Cond: (((t3_2.a + t3_2.b) / 2) = t1_1.a)
+                     ->  Seq Scan on public.prt1_e_p3 t3_2
+                           Output: t3_2.a, t3_2.b, t3_2.c
+                     ->  Hash
+                           Output: t1_1.a, t1_1.c, t2_2.b, t2_2.c
+                           ->  Hash Right Join
+                                 Output: t1_1.a, t1_1.c, t2_2.b, t2_2.c
+                                 Hash Cond: (t2_2.b = t1_1.a)
+                                 ->  Seq Scan on public.prt2_p3 t2_2
+                                       Output: t2_2.b, t2_2.c
+                                 ->  Hash
+                                       Output: t1_1.a, t1_1.c
+                                       ->  Seq Scan on public.prt1_p3 t1_1
+                                             Output: t1_1.a, t1_1.c
+                                             Filter: ((t1_1.a % 25) = 0)
+(57 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) LEFT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+  a  |  c   |  b  |  c   | ?column? |  c   
+-----+------+-----+------+----------+------
+   0 | 0000 |   0 | 0000 |        0 | 0000
+  50 | 0050 |     |      |      100 | 0050
+ 100 | 0100 |     |      |      200 | 0100
+ 150 | 0150 | 150 | 0150 |      300 | 0150
+ 200 | 0200 |     |      |      400 | 0200
+ 250 | 0250 |     |      |      500 | 0250
+ 300 | 0300 | 300 | 0300 |      600 | 0300
+ 350 | 0350 |     |      |      700 | 0350
+ 400 | 0400 |     |      |      800 | 0400
+ 450 | 0450 | 450 | 0450 |      900 | 0450
+ 500 | 0500 |     |      |     1000 | 0500
+ 550 | 0550 |     |      |     1100 | 0550
+(12 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (uprt1 t1 LEFT JOIN uprt2 t2 ON t1.a = t2.b) LEFT JOIN uprt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+  a  |  c   |  b  |  c   | ?column? |  c   
+-----+------+-----+------+----------+------
+   0 | 0000 |   0 | 0000 |        0 | 0000
+  50 | 0050 |     |      |      100 | 0050
+ 100 | 0100 |     |      |      200 | 0100
+ 150 | 0150 | 150 | 0150 |      300 | 0150
+ 200 | 0200 |     |      |      400 | 0200
+ 250 | 0250 |     |      |      500 | 0250
+ 300 | 0300 | 300 | 0300 |      600 | 0300
+ 350 | 0350 |     |      |      700 | 0350
+ 400 | 0400 |     |      |      800 | 0400
+ 450 | 0450 | 450 | 0450 |      900 | 0450
+ 500 | 0500 |     |      |     1000 | 0500
+ 550 | 0550 |     |      |     1100 | 0550
+(12 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) LEFT JOIN prt1_e t3 ON (t2.b = (t3.a + t3.b)/2) WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+                                     QUERY PLAN                                     
+------------------------------------------------------------------------------------
+ Sort
+   Output: t1.a, t1.c, t2.b, t2.c, ((t3.a + t3.b)), t3.c
+   Sort Key: t1.a, t2.b, ((t3.a + t3.b))
+   ->  Result
+         Output: t1.a, t1.c, t2.b, t2.c, (t3.a + t3.b), t3.c
+         ->  Append
+               ->  Hash Right Join
+                     Output: t1.a, t1.c, t2.b, t2.c, t3.a, t3.b, t3.c
+                     Hash Cond: (((t3.a + t3.b) / 2) = t2.b)
+                     ->  Seq Scan on public.prt1_e_p1 t3
+                           Output: t3.a, t3.b, t3.c
+                     ->  Hash
+                           Output: t1.a, t1.c, t2.b, t2.c
+                           ->  Hash Right Join
+                                 Output: t1.a, t1.c, t2.b, t2.c
+                                 Hash Cond: (t2.b = t1.a)
+                                 ->  Seq Scan on public.prt2_p1 t2
+                                       Output: t2.b, t2.c
+                                 ->  Hash
+                                       Output: t1.a, t1.c
+                                       ->  Seq Scan on public.prt1_p1 t1
+                                             Output: t1.a, t1.c
+                                             Filter: ((t1.a % 25) = 0)
+               ->  Hash Right Join
+                     Output: t1_2.a, t1_2.c, t2_1.b, t2_1.c, t3_1.a, t3_1.b, t3_1.c
+                     Hash Cond: (((t3_1.a + t3_1.b) / 2) = t2_1.b)
+                     ->  Seq Scan on public.prt1_e_p2 t3_1
+                           Output: t3_1.a, t3_1.b, t3_1.c
+                     ->  Hash
+                           Output: t1_2.a, t1_2.c, t2_1.b, t2_1.c
+                           ->  Hash Right Join
+                                 Output: t1_2.a, t1_2.c, t2_1.b, t2_1.c
+                                 Hash Cond: (t2_1.b = t1_2.a)
+                                 ->  Seq Scan on public.prt2_p2 t2_1
+                                       Output: t2_1.b, t2_1.c
+                                 ->  Hash
+                                       Output: t1_2.a, t1_2.c
+                                       ->  Seq Scan on public.prt1_p2 t1_2
+                                             Output: t1_2.a, t1_2.c
+                                             Filter: ((t1_2.a % 25) = 0)
+               ->  Hash Right Join
+                     Output: t1_1.a, t1_1.c, t2_2.b, t2_2.c, t3_2.a, t3_2.b, t3_2.c
+                     Hash Cond: (((t3_2.a + t3_2.b) / 2) = t2_2.b)
+                     ->  Seq Scan on public.prt1_e_p3 t3_2
+                           Output: t3_2.a, t3_2.b, t3_2.c
+                     ->  Hash
+                           Output: t1_1.a, t1_1.c, t2_2.b, t2_2.c
+                           ->  Hash Right Join
+                                 Output: t1_1.a, t1_1.c, t2_2.b, t2_2.c
+                                 Hash Cond: (t2_2.b = t1_1.a)
+                                 ->  Seq Scan on public.prt2_p3 t2_2
+                                       Output: t2_2.b, t2_2.c
+                                 ->  Hash
+                                       Output: t1_1.a, t1_1.c
+                                       ->  Seq Scan on public.prt1_p3 t1_1
+                                             Output: t1_1.a, t1_1.c
+                                             Filter: ((t1_1.a % 25) = 0)
+(57 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) LEFT JOIN prt1_e t3 ON (t2.b = (t3.a + t3.b)/2) WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+  a  |  c   |  b  |  c   | ?column? |  c   
+-----+------+-----+------+----------+------
+   0 | 0000 |   0 | 0000 |        0 | 0000
+  50 | 0050 |     |      |          | 
+ 100 | 0100 |     |      |          | 
+ 150 | 0150 | 150 | 0150 |      300 | 0150
+ 200 | 0200 |     |      |          | 
+ 250 | 0250 |     |      |          | 
+ 300 | 0300 | 300 | 0300 |      600 | 0300
+ 350 | 0350 |     |      |          | 
+ 400 | 0400 |     |      |          | 
+ 450 | 0450 | 450 | 0450 |      900 | 0450
+ 500 | 0500 |     |      |          | 
+ 550 | 0550 |     |      |          | 
+(12 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (uprt1 t1 LEFT JOIN uprt2 t2 ON t1.a = t2.b) LEFT JOIN uprt1_e t3 ON (t2.b = (t3.a + t3.b)/2) WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+  a  |  c   |  b  |  c   | ?column? |  c   
+-----+------+-----+------+----------+------
+   0 | 0000 |   0 | 0000 |        0 | 0000
+  50 | 0050 |     |      |          | 
+ 100 | 0100 |     |      |          | 
+ 150 | 0150 | 150 | 0150 |      300 | 0150
+ 200 | 0200 |     |      |          | 
+ 250 | 0250 |     |      |          | 
+ 300 | 0300 | 300 | 0300 |      600 | 0300
+ 350 | 0350 |     |      |          | 
+ 400 | 0400 |     |      |          | 
+ 450 | 0450 | 450 | 0450 |      900 | 0450
+ 500 | 0500 |     |      |          | 
+ 550 | 0550 |     |      |          | 
+(12 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+                                     QUERY PLAN                                     
+------------------------------------------------------------------------------------
+ Sort
+   Output: t1.a, t1.c, t2.b, t2.c, ((t3.a + t3.b)), t3.c
+   Sort Key: t1.a, t2.b, ((t3.a + t3.b))
+   ->  Result
+         Output: t1.a, t1.c, t2.b, t2.c, (t3.a + t3.b), t3.c
+         ->  Append
+               ->  Hash Right Join
+                     Output: t3.a, t3.b, t3.c, t1.a, t1.c, t2.b, t2.c
+                     Hash Cond: (t2.b = t1.a)
+                     ->  Seq Scan on public.prt2_p1 t2
+                           Output: t2.b, t2.c
+                     ->  Hash
+                           Output: t3.a, t3.b, t3.c, t1.a, t1.c
+                           ->  Hash Right Join
+                                 Output: t3.a, t3.b, t3.c, t1.a, t1.c
+                                 Hash Cond: (t1.a = ((t3.a + t3.b) / 2))
+                                 ->  Seq Scan on public.prt1_p1 t1
+                                       Output: t1.a, t1.c
+                                 ->  Hash
+                                       Output: t3.a, t3.b, t3.c
+                                       ->  Seq Scan on public.prt1_e_p1 t3
+                                             Output: t3.a, t3.b, t3.c
+                                             Filter: ((t3.a % 25) = 0)
+               ->  Hash Right Join
+                     Output: t3_1.a, t3_1.b, t3_1.c, t1_2.a, t1_2.c, t2_1.b, t2_1.c
+                     Hash Cond: (t2_1.b = t1_2.a)
+                     ->  Seq Scan on public.prt2_p2 t2_1
+                           Output: t2_1.b, t2_1.c
+                     ->  Hash
+                           Output: t3_1.a, t3_1.b, t3_1.c, t1_2.a, t1_2.c
+                           ->  Hash Right Join
+                                 Output: t3_1.a, t3_1.b, t3_1.c, t1_2.a, t1_2.c
+                                 Hash Cond: (t1_2.a = ((t3_1.a + t3_1.b) / 2))
+                                 ->  Seq Scan on public.prt1_p2 t1_2
+                                       Output: t1_2.a, t1_2.c
+                                 ->  Hash
+                                       Output: t3_1.a, t3_1.b, t3_1.c
+                                       ->  Seq Scan on public.prt1_e_p2 t3_1
+                                             Output: t3_1.a, t3_1.b, t3_1.c
+                                             Filter: ((t3_1.a % 25) = 0)
+               ->  Hash Right Join
+                     Output: t3_2.a, t3_2.b, t3_2.c, t1_1.a, t1_1.c, t2_2.b, t2_2.c
+                     Hash Cond: (t2_2.b = t1_1.a)
+                     ->  Seq Scan on public.prt2_p3 t2_2
+                           Output: t2_2.b, t2_2.c
+                     ->  Hash
+                           Output: t3_2.a, t3_2.b, t3_2.c, t1_1.a, t1_1.c
+                           ->  Hash Right Join
+                                 Output: t3_2.a, t3_2.b, t3_2.c, t1_1.a, t1_1.c
+                                 Hash Cond: (t1_1.a = ((t3_2.a + t3_2.b) / 2))
+                                 ->  Seq Scan on public.prt1_p3 t1_1
+                                       Output: t1_1.a, t1_1.c
+                                 ->  Hash
+                                       Output: t3_2.a, t3_2.b, t3_2.c
+                                       ->  Seq Scan on public.prt1_e_p3 t3_2
+                                             Output: t3_2.a, t3_2.b, t3_2.c
+                                             Filter: ((t3_2.a % 25) = 0)
+(57 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+  a  |  c   |  b  |  c   | ?column? |  c   
+-----+------+-----+------+----------+------
+   0 | 0000 |   0 | 0000 |        0 | 0000
+  50 | 0050 |     |      |      100 | 0050
+ 100 | 0100 |     |      |      200 | 0100
+ 150 | 0150 | 150 | 0150 |      300 | 0150
+ 200 | 0200 |     |      |      400 | 0200
+ 250 | 0250 |     |      |      500 | 0250
+ 300 | 0300 | 300 | 0300 |      600 | 0300
+ 350 | 0350 |     |      |      700 | 0350
+ 400 | 0400 |     |      |      800 | 0400
+ 450 | 0450 | 450 | 0450 |      900 | 0450
+ 500 | 0500 |     |      |     1000 | 0500
+ 550 | 0550 |     |      |     1100 | 0550
+(12 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (uprt1 t1 LEFT JOIN uprt2 t2 ON t1.a = t2.b) RIGHT JOIN uprt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+  a  |  c   |  b  |  c   | ?column? |  c   
+-----+------+-----+------+----------+------
+   0 | 0000 |   0 | 0000 |        0 | 0000
+  50 | 0050 |     |      |      100 | 0050
+ 100 | 0100 |     |      |      200 | 0100
+ 150 | 0150 | 150 | 0150 |      300 | 0150
+ 200 | 0200 |     |      |      400 | 0200
+ 250 | 0250 |     |      |      500 | 0250
+ 300 | 0300 | 300 | 0300 |      600 | 0300
+ 350 | 0350 |     |      |      700 | 0350
+ 400 | 0400 |     |      |      800 | 0400
+ 450 | 0450 | 450 | 0450 |      900 | 0450
+ 500 | 0500 |     |      |     1000 | 0500
+ 550 | 0550 |     |      |     1100 | 0550
+(12 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t2.b = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+                                     QUERY PLAN                                     
+------------------------------------------------------------------------------------
+ Sort
+   Output: t1.a, t1.c, t2.b, t2.c, ((t3.a + t3.b)), t3.c
+   Sort Key: t1.a, t2.b, ((t3.a + t3.b))
+   ->  Result
+         Output: t1.a, t1.c, t2.b, t2.c, (t3.a + t3.b), t3.c
+         ->  Append
+               ->  Hash Right Join
+                     Output: t3.a, t3.b, t3.c, t2.b, t2.c, t1.a, t1.c
+                     Hash Cond: (t1.a = t2.b)
+                     ->  Seq Scan on public.prt1_p1 t1
+                           Output: t1.a, t1.c
+                     ->  Hash
+                           Output: t3.a, t3.b, t3.c, t2.b, t2.c
+                           ->  Hash Right Join
+                                 Output: t3.a, t3.b, t3.c, t2.b, t2.c
+                                 Hash Cond: (t2.b = ((t3.a + t3.b) / 2))
+                                 ->  Seq Scan on public.prt2_p1 t2
+                                       Output: t2.b, t2.c
+                                 ->  Hash
+                                       Output: t3.a, t3.b, t3.c
+                                       ->  Seq Scan on public.prt1_e_p1 t3
+                                             Output: t3.a, t3.b, t3.c
+                                             Filter: ((t3.a % 25) = 0)
+               ->  Hash Right Join
+                     Output: t3_1.a, t3_1.b, t3_1.c, t2_1.b, t2_1.c, t1_2.a, t1_2.c
+                     Hash Cond: (t1_2.a = t2_1.b)
+                     ->  Seq Scan on public.prt1_p2 t1_2
+                           Output: t1_2.a, t1_2.c
+                     ->  Hash
+                           Output: t3_1.a, t3_1.b, t3_1.c, t2_1.b, t2_1.c
+                           ->  Hash Right Join
+                                 Output: t3_1.a, t3_1.b, t3_1.c, t2_1.b, t2_1.c
+                                 Hash Cond: (t2_1.b = ((t3_1.a + t3_1.b) / 2))
+                                 ->  Seq Scan on public.prt2_p2 t2_1
+                                       Output: t2_1.b, t2_1.c
+                                 ->  Hash
+                                       Output: t3_1.a, t3_1.b, t3_1.c
+                                       ->  Seq Scan on public.prt1_e_p2 t3_1
+                                             Output: t3_1.a, t3_1.b, t3_1.c
+                                             Filter: ((t3_1.a % 25) = 0)
+               ->  Hash Right Join
+                     Output: t3_2.a, t3_2.b, t3_2.c, t2_2.b, t2_2.c, t1_1.a, t1_1.c
+                     Hash Cond: (t1_1.a = t2_2.b)
+                     ->  Seq Scan on public.prt1_p3 t1_1
+                           Output: t1_1.a, t1_1.c
+                     ->  Hash
+                           Output: t3_2.a, t3_2.b, t3_2.c, t2_2.b, t2_2.c
+                           ->  Hash Right Join
+                                 Output: t3_2.a, t3_2.b, t3_2.c, t2_2.b, t2_2.c
+                                 Hash Cond: (t2_2.b = ((t3_2.a + t3_2.b) / 2))
+                                 ->  Seq Scan on public.prt2_p3 t2_2
+                                       Output: t2_2.b, t2_2.c
+                                 ->  Hash
+                                       Output: t3_2.a, t3_2.b, t3_2.c
+                                       ->  Seq Scan on public.prt1_e_p3 t3_2
+                                             Output: t3_2.a, t3_2.b, t3_2.c
+                                             Filter: ((t3_2.a % 25) = 0)
+(57 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t2.b = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+  a  |  c   |  b  |  c   | ?column? |  c   
+-----+------+-----+------+----------+------
+   0 | 0000 |   0 | 0000 |        0 | 0000
+ 150 | 0150 | 150 | 0150 |      300 | 0150
+ 300 | 0300 | 300 | 0300 |      600 | 0300
+ 450 | 0450 | 450 | 0450 |      900 | 0450
+     |      |     |      |      100 | 0050
+     |      |     |      |      200 | 0100
+     |      |     |      |      400 | 0200
+     |      |     |      |      500 | 0250
+     |      |     |      |      700 | 0350
+     |      |     |      |      800 | 0400
+     |      |     |      |     1000 | 0500
+     |      |     |      |     1100 | 0550
+(12 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (uprt1 t1 RIGHT JOIN uprt2 t2 ON t1.a = t2.b) RIGHT JOIN uprt1_e t3 ON (t2.b = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+  a  |  c   |  b  |  c   | ?column? |  c   
+-----+------+-----+------+----------+------
+   0 | 0000 |   0 | 0000 |        0 | 0000
+ 150 | 0150 | 150 | 0150 |      300 | 0150
+ 300 | 0300 | 300 | 0300 |      600 | 0300
+ 450 | 0450 | 450 | 0450 |      900 | 0450
+     |      |     |      |      100 | 0050
+     |      |     |      |      200 | 0100
+     |      |     |      |      400 | 0200
+     |      |     |      |      500 | 0250
+     |      |     |      |      700 | 0350
+     |      |     |      |      800 | 0400
+     |      |     |      |     1000 | 0500
+     |      |     |      |     1100 | 0550
+(12 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM ((SELECT * FROM prt1 WHERE prt1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM prt2 WHERE prt2.b % 25 = 0) t2 ON (t1.a = t2.b)) FULL JOIN (SELECT * FROM prt1_e WHERE prt1_e.a % 25 = 0) t3 ON (t1.a = (t3.a + t3.b)/2) ORDER BY t1.a, t2.b, t3.a + t3.b;
+                                                  QUERY PLAN                                                   
+---------------------------------------------------------------------------------------------------------------
+ Sort
+   Output: prt1_p1.a, prt1_p1.c, prt2_p1.b, prt2_p1.c, ((prt1_e_p1.a + prt1_e_p1.b)), prt1_e_p1.c
+   Sort Key: prt1_p1.a, prt2_p1.b, ((prt1_e_p1.a + prt1_e_p1.b))
+   ->  Result
+         Output: prt1_p1.a, prt1_p1.c, prt2_p1.b, prt2_p1.c, (prt1_e_p1.a + prt1_e_p1.b), prt1_e_p1.c
+         ->  Append
+               ->  Hash Full Join
+                     Output: prt1_p1.a, prt1_p1.c, prt2_p1.b, prt2_p1.c, prt1_e_p1.a, prt1_e_p1.b, prt1_e_p1.c
+                     Hash Cond: (prt1_p1.a = ((prt1_e_p1.a + prt1_e_p1.b) / 2))
+                     ->  Hash Full Join
+                           Output: prt1_p1.a, prt1_p1.c, prt2_p1.b, prt2_p1.c
+                           Hash Cond: (prt1_p1.a = prt2_p1.b)
+                           ->  Seq Scan on public.prt1_p1
+                                 Output: prt1_p1.a, prt1_p1.c
+                                 Filter: ((prt1_p1.a % 25) = 0)
+                           ->  Hash
+                                 Output: prt2_p1.b, prt2_p1.c
+                                 ->  Seq Scan on public.prt2_p1
+                                       Output: prt2_p1.b, prt2_p1.c
+                                       Filter: ((prt2_p1.b % 25) = 0)
+                     ->  Hash
+                           Output: prt1_e_p1.a, prt1_e_p1.b, prt1_e_p1.c
+                           ->  Seq Scan on public.prt1_e_p1
+                                 Output: prt1_e_p1.a, prt1_e_p1.b, prt1_e_p1.c
+                                 Filter: ((prt1_e_p1.a % 25) = 0)
+               ->  Hash Full Join
+                     Output: prt1_p2.a, prt1_p2.c, prt2_p2.b, prt2_p2.c, prt1_e_p2.a, prt1_e_p2.b, prt1_e_p2.c
+                     Hash Cond: (prt1_p2.a = ((prt1_e_p2.a + prt1_e_p2.b) / 2))
+                     ->  Hash Full Join
+                           Output: prt1_p2.a, prt1_p2.c, prt2_p2.b, prt2_p2.c
+                           Hash Cond: (prt1_p2.a = prt2_p2.b)
+                           ->  Seq Scan on public.prt1_p2
+                                 Output: prt1_p2.a, prt1_p2.c
+                                 Filter: ((prt1_p2.a % 25) = 0)
+                           ->  Hash
+                                 Output: prt2_p2.b, prt2_p2.c
+                                 ->  Seq Scan on public.prt2_p2
+                                       Output: prt2_p2.b, prt2_p2.c
+                                       Filter: ((prt2_p2.b % 25) = 0)
+                     ->  Hash
+                           Output: prt1_e_p2.a, prt1_e_p2.b, prt1_e_p2.c
+                           ->  Seq Scan on public.prt1_e_p2
+                                 Output: prt1_e_p2.a, prt1_e_p2.b, prt1_e_p2.c
+                                 Filter: ((prt1_e_p2.a % 25) = 0)
+               ->  Hash Full Join
+                     Output: prt1_p3.a, prt1_p3.c, prt2_p3.b, prt2_p3.c, prt1_e_p3.a, prt1_e_p3.b, prt1_e_p3.c
+                     Hash Cond: (prt1_p3.a = ((prt1_e_p3.a + prt1_e_p3.b) / 2))
+                     ->  Hash Full Join
+                           Output: prt1_p3.a, prt1_p3.c, prt2_p3.b, prt2_p3.c
+                           Hash Cond: (prt1_p3.a = prt2_p3.b)
+                           ->  Seq Scan on public.prt1_p3
+                                 Output: prt1_p3.a, prt1_p3.c
+                                 Filter: ((prt1_p3.a % 25) = 0)
+                           ->  Hash
+                                 Output: prt2_p3.b, prt2_p3.c
+                                 ->  Seq Scan on public.prt2_p3
+                                       Output: prt2_p3.b, prt2_p3.c
+                                       Filter: ((prt2_p3.b % 25) = 0)
+                     ->  Hash
+                           Output: prt1_e_p3.a, prt1_e_p3.b, prt1_e_p3.c
+                           ->  Seq Scan on public.prt1_e_p3
+                                 Output: prt1_e_p3.a, prt1_e_p3.b, prt1_e_p3.c
+                                 Filter: ((prt1_e_p3.a % 25) = 0)
+(63 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM ((SELECT * FROM prt1 WHERE prt1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM prt2 WHERE prt2.b % 25 = 0) t2 ON (t1.a = t2.b)) FULL JOIN (SELECT * FROM prt1_e WHERE prt1_e.a % 25 = 0) t3 ON (t1.a = (t3.a + t3.b)/2) ORDER BY t1.a, t2.b, t3.a + t3.b;
+  a  |  c   |  b  |  c   | ?column? |  c   
+-----+------+-----+------+----------+------
+   0 | 0000 |   0 | 0000 |        0 | 0000
+  50 | 0050 |     |      |      100 | 0050
+ 100 | 0100 |     |      |      200 | 0100
+ 150 | 0150 | 150 | 0150 |      300 | 0150
+ 200 | 0200 |     |      |      400 | 0200
+ 250 | 0250 |     |      |      500 | 0250
+ 300 | 0300 | 300 | 0300 |      600 | 0300
+ 350 | 0350 |     |      |      700 | 0350
+ 400 | 0400 |     |      |      800 | 0400
+ 450 | 0450 | 450 | 0450 |      900 | 0450
+ 500 | 0500 |     |      |     1000 | 0500
+ 550 | 0550 |     |      |     1100 | 0550
+     |      |  75 | 0075 |          | 
+     |      | 225 | 0225 |          | 
+     |      | 375 | 0375 |          | 
+     |      | 525 | 0525 |          | 
+(16 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM ((SELECT * FROM uprt1 WHERE uprt1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM uprt2 WHERE uprt2.b % 25 = 0) t2 ON (t1.a = t2.b)) FULL JOIN (SELECT * FROM uprt1_e WHERE uprt1_e.a % 25 = 0) t3 ON (t1.a = (t3.a + t3.b)/2) ORDER BY t1.a, t2.b, t3.a + t3.b;
+  a  |  c   |  b  |  c   | ?column? |  c   
+-----+------+-----+------+----------+------
+   0 | 0000 |   0 | 0000 |        0 | 0000
+  50 | 0050 |     |      |      100 | 0050
+ 100 | 0100 |     |      |      200 | 0100
+ 150 | 0150 | 150 | 0150 |      300 | 0150
+ 200 | 0200 |     |      |      400 | 0200
+ 250 | 0250 |     |      |      500 | 0250
+ 300 | 0300 | 300 | 0300 |      600 | 0300
+ 350 | 0350 |     |      |      700 | 0350
+ 400 | 0400 |     |      |      800 | 0400
+ 450 | 0450 | 450 | 0450 |      900 | 0450
+ 500 | 0500 |     |      |     1000 | 0500
+ 550 | 0550 |     |      |     1100 | 0550
+     |      |  75 | 0075 |          | 
+     |      | 225 | 0225 |          | 
+     |      | 375 | 0375 |          | 
+     |      | 525 | 0525 |          | 
+(16 rows)
+
+-- Cases with non-nullable expressions in subquery results;
+-- make sure these go to null as expected
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.phv, t2.b, t2.phv, t3.a + t3.b, t3.phv FROM ((SELECT 50 phv, * FROM prt1 WHERE prt1.a % 25 = 0) t1 FULL JOIN (SELECT 75 phv, * FROM prt2 WHERE prt2.b % 25 = 0) t2 ON (t1.a = t2.b)) FULL JOIN (SELECT 50 phv, * FROM prt1_e WHERE prt1_e.a % 25 = 0) t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.a = t1.phv OR t2.b = t2.phv OR (t3.a + t3.b)/2 = t3.phv ORDER BY t1.a, t2.b, t3.a + t3.b;
+                                                      QUERY PLAN                                                      
+----------------------------------------------------------------------------------------------------------------------
+ Sort
+   Output: prt1_p1.a, (50), prt2_p1.b, (75), ((prt1_e_p1.a + prt1_e_p1.b)), (50)
+   Sort Key: prt1_p1.a, prt2_p1.b, ((prt1_e_p1.a + prt1_e_p1.b))
+   ->  Result
+         Output: prt1_p1.a, (50), prt2_p1.b, (75), (prt1_e_p1.a + prt1_e_p1.b), (50)
+         ->  Append
+               ->  Hash Full Join
+                     Output: prt1_p1.a, prt2_p1.b, prt1_e_p1.a, prt1_e_p1.b, (50), (75), (50)
+                     Hash Cond: (prt1_p1.a = ((prt1_e_p1.a + prt1_e_p1.b) / 2))
+                     Filter: ((prt1_p1.a = (50)) OR (prt2_p1.b = (75)) OR (((prt1_e_p1.a + prt1_e_p1.b) / 2) = (50)))
+                     ->  Hash Full Join
+                           Output: prt1_p1.a, prt2_p1.b, (50), (75)
+                           Hash Cond: (prt1_p1.a = prt2_p1.b)
+                           ->  Seq Scan on public.prt1_p1
+                                 Output: prt1_p1.a, 50
+                                 Filter: ((prt1_p1.a % 25) = 0)
+                           ->  Hash
+                                 Output: prt2_p1.b, (75)
+                                 ->  Seq Scan on public.prt2_p1
+                                       Output: prt2_p1.b, 75
+                                       Filter: ((prt2_p1.b % 25) = 0)
+                     ->  Hash
+                           Output: prt1_e_p1.a, prt1_e_p1.b, (50)
+                           ->  Seq Scan on public.prt1_e_p1
+                                 Output: prt1_e_p1.a, prt1_e_p1.b, 50
+                                 Filter: ((prt1_e_p1.a % 25) = 0)
+               ->  Hash Full Join
+                     Output: prt1_p2.a, prt2_p2.b, prt1_e_p2.a, prt1_e_p2.b, (50), (75), (50)
+                     Hash Cond: (prt1_p2.a = ((prt1_e_p2.a + prt1_e_p2.b) / 2))
+                     Filter: ((prt1_p2.a = (50)) OR (prt2_p2.b = (75)) OR (((prt1_e_p2.a + prt1_e_p2.b) / 2) = (50)))
+                     ->  Hash Full Join
+                           Output: prt1_p2.a, prt2_p2.b, (50), (75)
+                           Hash Cond: (prt1_p2.a = prt2_p2.b)
+                           ->  Seq Scan on public.prt1_p2
+                                 Output: prt1_p2.a, 50
+                                 Filter: ((prt1_p2.a % 25) = 0)
+                           ->  Hash
+                                 Output: prt2_p2.b, (75)
+                                 ->  Seq Scan on public.prt2_p2
+                                       Output: prt2_p2.b, 75
+                                       Filter: ((prt2_p2.b % 25) = 0)
+                     ->  Hash
+                           Output: prt1_e_p2.a, prt1_e_p2.b, (50)
+                           ->  Seq Scan on public.prt1_e_p2
+                                 Output: prt1_e_p2.a, prt1_e_p2.b, 50
+                                 Filter: ((prt1_e_p2.a % 25) = 0)
+               ->  Hash Full Join
+                     Output: prt1_p3.a, prt2_p3.b, prt1_e_p3.a, prt1_e_p3.b, (50), (75), (50)
+                     Hash Cond: (prt1_p3.a = ((prt1_e_p3.a + prt1_e_p3.b) / 2))
+                     Filter: ((prt1_p3.a = (50)) OR (prt2_p3.b = (75)) OR (((prt1_e_p3.a + prt1_e_p3.b) / 2) = (50)))
+                     ->  Hash Full Join
+                           Output: prt1_p3.a, prt2_p3.b, (50), (75)
+                           Hash Cond: (prt1_p3.a = prt2_p3.b)
+                           ->  Seq Scan on public.prt1_p3
+                                 Output: prt1_p3.a, 50
+                                 Filter: ((prt1_p3.a % 25) = 0)
+                           ->  Hash
+                                 Output: prt2_p3.b, (75)
+                                 ->  Seq Scan on public.prt2_p3
+                                       Output: prt2_p3.b, 75
+                                       Filter: ((prt2_p3.b % 25) = 0)
+                     ->  Hash
+                           Output: prt1_e_p3.a, prt1_e_p3.b, (50)
+                           ->  Seq Scan on public.prt1_e_p3
+                                 Output: prt1_e_p3.a, prt1_e_p3.b, 50
+                                 Filter: ((prt1_e_p3.a % 25) = 0)
+(66 rows)
+
+SELECT t1.a, t1.phv, t2.b, t2.phv, t3.a + t3.b, t3.phv FROM ((SELECT 50 phv, * FROM prt1 WHERE prt1.a % 25 = 0) t1 FULL JOIN (SELECT 75 phv, * FROM prt2 WHERE prt2.b % 25 = 0) t2 ON (t1.a = t2.b)) FULL JOIN (SELECT 50 phv, * FROM prt1_e WHERE prt1_e.a % 25 = 0) t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.a = t1.phv OR t2.b = t2.phv OR (t3.a + t3.b)/2 = t3.phv ORDER BY t1.a, t2.b, t3.a + t3.b;
+ a  | phv | b  | phv | ?column? | phv 
+----+-----+----+-----+----------+-----
+ 50 |  50 |    |     |      100 |  50
+    |     | 75 |  75 |          |    
+(2 rows)
+
+SELECT t1.a, t1.phv, t2.b, t2.phv, t3.a + t3.b, t3.phv FROM ((SELECT 50 phv, * FROM uprt1 WHERE uprt1.a % 25 = 0) t1 FULL JOIN (SELECT 75 phv, * FROM uprt2 WHERE uprt2.b % 25 = 0) t2 ON (t1.a = t2.b)) FULL JOIN (SELECT 50 phv, * FROM uprt1_e WHERE uprt1_e.a % 25 = 0) t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.a = t1.phv OR t2.b = t2.phv OR (t3.a + t3.b)/2 = t3.phv ORDER BY t1.a, t2.b, t3.a + t3.b;
+ a  | phv | b  | phv | ?column? | phv 
+----+-----+----+-----+----------+-----
+ 50 |  50 |    |     |      100 |  50
+    |     | 75 |  75 |          |    
+(2 rows)
+
+-- Semi-join
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1, prt1_e t2 WHERE t1.b % 25 = 0 AND t1.b = (t2.a + t2.b)/2) AND t1.a % 25 = 0 ORDER BY t1.a;
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Merge Append
+   Sort Key: t1.a
+   ->  Merge Semi Join
+         Output: t1.a, t1.b, t1.c
+         Merge Cond: (t1.a = t1_3.b)
+         ->  Sort
+               Output: t1.a, t1.b, t1.c
+               Sort Key: t1.a
+               ->  Seq Scan on public.prt1_p1 t1
+                     Output: t1.a, t1.b, t1.c
+                     Filter: ((t1.a % 25) = 0)
+         ->  Sort
+               Output: t1_3.b, t2.a, t2.b
+               Sort Key: t1_3.b
+               ->  Hash Join
+                     Output: t1_3.b, t2.a, t2.b
+                     Hash Cond: (((t2.a + t2.b) / 2) = t1_3.b)
+                     ->  Seq Scan on public.prt1_e_p1 t2
+                           Output: t2.a, t2.b
+                     ->  Hash
+                           Output: t1_3.b
+                           ->  Seq Scan on public.prt2_p1 t1_3
+                                 Output: t1_3.b
+                                 Filter: ((t1_3.b % 25) = 0)
+   ->  Merge Semi Join
+         Output: t1_2.a, t1_2.b, t1_2.c
+         Merge Cond: (t1_2.a = t1_4.b)
+         ->  Sort
+               Output: t1_2.a, t1_2.b, t1_2.c
+               Sort Key: t1_2.a
+               ->  Seq Scan on public.prt1_p2 t1_2
+                     Output: t1_2.a, t1_2.b, t1_2.c
+                     Filter: ((t1_2.a % 25) = 0)
+         ->  Sort
+               Output: t1_4.b, t2_1.a, t2_1.b
+               Sort Key: t1_4.b
+               ->  Hash Join
+                     Output: t1_4.b, t2_1.a, t2_1.b
+                     Hash Cond: (((t2_1.a + t2_1.b) / 2) = t1_4.b)
+                     ->  Seq Scan on public.prt1_e_p2 t2_1
+                           Output: t2_1.a, t2_1.b
+                     ->  Hash
+                           Output: t1_4.b
+                           ->  Seq Scan on public.prt2_p2 t1_4
+                                 Output: t1_4.b
+                                 Filter: ((t1_4.b % 25) = 0)
+   ->  Merge Semi Join
+         Output: t1_1.a, t1_1.b, t1_1.c
+         Merge Cond: (t1_1.a = t1_5.b)
+         ->  Sort
+               Output: t1_1.a, t1_1.b, t1_1.c
+               Sort Key: t1_1.a
+               ->  Seq Scan on public.prt1_p3 t1_1
+                     Output: t1_1.a, t1_1.b, t1_1.c
+                     Filter: ((t1_1.a % 25) = 0)
+         ->  Sort
+               Output: t1_5.b, t2_2.a, t2_2.b
+               Sort Key: t1_5.b
+               ->  Hash Join
+                     Output: t1_5.b, t2_2.a, t2_2.b
+                     Hash Cond: (((t2_2.a + t2_2.b) / 2) = t1_5.b)
+                     ->  Seq Scan on public.prt1_e_p3 t2_2
+                           Output: t2_2.a, t2_2.b
+                     ->  Hash
+                           Output: t1_5.b
+                           ->  Seq Scan on public.prt2_p3 t1_5
+                                 Output: t1_5.b
+                                 Filter: ((t1_5.b % 25) = 0)
+(68 rows)
+
+SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1, prt1_e t2 WHERE t1.b % 25 = 0 AND t1.b = (t2.a + t2.b)/2) AND t1.a % 25 = 0 ORDER BY t1.a;
+  a  |  b  |  c   
+-----+-----+------
+   0 |   0 | 0000
+ 150 | 150 | 0150
+ 300 | 300 | 0300
+ 450 | 450 | 0450
+(4 rows)
+
+SELECT t1.* FROM uprt1 t1 WHERE t1.a IN (SELECT t1.b FROM uprt2 t1, uprt1_e t2 WHERE t1.b % 25 = 0 AND t1.b = (t2.a + t2.b)/2) AND t1.a % 25 = 0 ORDER BY t1.a;
+  a  |  b  |  c   
+-----+-----+------
+   0 |   0 | 0000
+ 150 | 150 | 0150
+ 300 | 300 | 0300
+ 450 | 450 | 0450
+(4 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.a %25 = 0)) AND t1.a % 25 = 0 ORDER BY t1.a;
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Merge Append
+   Sort Key: t1.a
+   ->  Merge Semi Join
+         Output: t1.a, t1.b, t1.c
+         Merge Cond: (t1.a = t1_3.b)
+         ->  Sort
+               Output: t1.a, t1.b, t1.c
+               Sort Key: t1.a
+               ->  Seq Scan on public.prt1_p1 t1
+                     Output: t1.a, t1.b, t1.c
+                     Filter: ((t1.a % 25) = 0)
+         ->  Sort
+               Output: t1_3.b, t1_6.a, t1_6.b
+               Sort Key: t1_3.b
+               ->  Hash Semi Join
+                     Output: t1_3.b, t1_6.a, t1_6.b
+                     Hash Cond: (t1_3.b = ((t1_6.a + t1_6.b) / 2))
+                     ->  Seq Scan on public.prt2_p1 t1_3
+                           Output: t1_3.b
+                     ->  Hash
+                           Output: t1_6.a, t1_6.b
+                           ->  Seq Scan on public.prt1_e_p1 t1_6
+                                 Output: t1_6.a, t1_6.b
+                                 Filter: ((t1_6.a % 25) = 0)
+   ->  Merge Semi Join
+         Output: t1_2.a, t1_2.b, t1_2.c
+         Merge Cond: (t1_2.a = t1_4.b)
+         ->  Sort
+               Output: t1_2.a, t1_2.b, t1_2.c
+               Sort Key: t1_2.a
+               ->  Seq Scan on public.prt1_p2 t1_2
+                     Output: t1_2.a, t1_2.b, t1_2.c
+                     Filter: ((t1_2.a % 25) = 0)
+         ->  Sort
+               Output: t1_4.b, t1_7.a, t1_7.b
+               Sort Key: t1_4.b
+               ->  Hash Semi Join
+                     Output: t1_4.b, t1_7.a, t1_7.b
+                     Hash Cond: (t1_4.b = ((t1_7.a + t1_7.b) / 2))
+                     ->  Seq Scan on public.prt2_p2 t1_4
+                           Output: t1_4.b
+                     ->  Hash
+                           Output: t1_7.a, t1_7.b
+                           ->  Seq Scan on public.prt1_e_p2 t1_7
+                                 Output: t1_7.a, t1_7.b
+                                 Filter: ((t1_7.a % 25) = 0)
+   ->  Merge Semi Join
+         Output: t1_1.a, t1_1.b, t1_1.c
+         Merge Cond: (t1_1.a = t1_5.b)
+         ->  Sort
+               Output: t1_1.a, t1_1.b, t1_1.c
+               Sort Key: t1_1.a
+               ->  Seq Scan on public.prt1_p3 t1_1
+                     Output: t1_1.a, t1_1.b, t1_1.c
+                     Filter: ((t1_1.a % 25) = 0)
+         ->  Sort
+               Output: t1_5.b, t1_8.a, t1_8.b
+               Sort Key: t1_5.b
+               ->  Hash Semi Join
+                     Output: t1_5.b, t1_8.a, t1_8.b
+                     Hash Cond: (t1_5.b = ((t1_8.a + t1_8.b) / 2))
+                     ->  Seq Scan on public.prt2_p3 t1_5
+                           Output: t1_5.b
+                     ->  Hash
+                           Output: t1_8.a, t1_8.b
+                           ->  Seq Scan on public.prt1_e_p3 t1_8
+                                 Output: t1_8.a, t1_8.b
+                                 Filter: ((t1_8.a % 25) = 0)
+(68 rows)
+
+SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.a %25 = 0)) AND t1.a % 25 = 0 ORDER BY t1.a;
+  a  |  b  |  c   
+-----+-----+------
+   0 |   0 | 0000
+ 150 | 150 | 0150
+ 300 | 300 | 0300
+ 450 | 450 | 0450
+(4 rows)
+
+SELECT t1.* FROM uprt1 t1 WHERE t1.a IN (SELECT t1.b FROM uprt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM uprt1_e t1 WHERE t1.a %25 = 0)) AND t1.a % 25 = 0 ORDER BY t1.a;
+  a  |  b  |  c   
+-----+-----+------
+   0 |   0 | 0000
+ 150 | 150 | 0150
+ 300 | 300 | 0300
+ 450 | 450 | 0450
+(4 rows)
+
+-- test merge joins with and without using indexes
+SET enable_hashjoin TO off;
+SET enable_nestloop TO off;
+CREATE INDEX iprt1_p1_a on prt1_p1(a);
+CREATE INDEX iprt1_p2_a on prt1_p2(a);
+CREATE INDEX iprt1_p3_a on prt1_p3(a);
+CREATE INDEX iprt2_p1_b on prt2_p1(b);
+CREATE INDEX iprt2_p2_b on prt2_p2(b);
+CREATE INDEX iprt2_p3_b on prt2_p3(b);
+CREATE INDEX iprt1_e_p1_ab2 on prt1_e_p1(((a+b)/2));
+CREATE INDEX iprt1_e_p2_ab2 on prt1_e_p2(((a+b)/2));
+CREATE INDEX iprt1_e_p3_ab2 on prt1_e_p3(((a+b)/2));
+ANALYZE prt1;
+ANALYZE prt1_p1;
+ANALYZE prt1_p2;
+ANALYZE prt1_p3;
+ANALYZE prt2;
+ANALYZE prt2_p1;
+ANALYZE prt2_p2;
+ANALYZE prt2_p3;
+ANALYZE prt1_e;
+ANALYZE prt1_e_p1;
+ANALYZE prt1_e_p2;
+ANALYZE prt1_e_p3;
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t2.b = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+                                             QUERY PLAN                                              
+-----------------------------------------------------------------------------------------------------
+ Sort
+   Output: t1.a, t1.c, t2.b, t2.c, ((t3.a + t3.b)), t3.c
+   Sort Key: t1.a, t2.b, ((t3.a + t3.b))
+   ->  Result
+         Output: t1.a, t1.c, t2.b, t2.c, (t3.a + t3.b), t3.c
+         ->  Append
+               ->  Merge Left Join
+                     Output: t3.a, t3.b, t3.c, t2.b, t2.c, t1.a, t1.c
+                     Merge Cond: (t2.b = t1.a)
+                     ->  Sort
+                           Output: t3.a, t3.b, t3.c, t2.b, t2.c
+                           Sort Key: t2.b
+                           ->  Merge Left Join
+                                 Output: t3.a, t3.b, t3.c, t2.b, t2.c
+                                 Merge Cond: ((((t3.a + t3.b) / 2)) = t2.b)
+                                 ->  Sort
+                                       Output: t3.a, t3.b, t3.c, (((t3.a + t3.b) / 2))
+                                       Sort Key: (((t3.a + t3.b) / 2))
+                                       ->  Seq Scan on public.prt1_e_p1 t3
+                                             Output: t3.a, t3.b, t3.c, ((t3.a + t3.b) / 2)
+                                             Filter: ((t3.a % 25) = 0)
+                                 ->  Sort
+                                       Output: t2.b, t2.c
+                                       Sort Key: t2.b
+                                       ->  Seq Scan on public.prt2_p1 t2
+                                             Output: t2.b, t2.c
+                     ->  Sort
+                           Output: t1.a, t1.c
+                           Sort Key: t1.a
+                           ->  Seq Scan on public.prt1_p1 t1
+                                 Output: t1.a, t1.c
+               ->  Merge Left Join
+                     Output: t3_1.a, t3_1.b, t3_1.c, t2_1.b, t2_1.c, t1_2.a, t1_2.c
+                     Merge Cond: (t2_1.b = t1_2.a)
+                     ->  Sort
+                           Output: t3_1.a, t3_1.b, t3_1.c, t2_1.b, t2_1.c
+                           Sort Key: t2_1.b
+                           ->  Merge Left Join
+                                 Output: t3_1.a, t3_1.b, t3_1.c, t2_1.b, t2_1.c
+                                 Merge Cond: ((((t3_1.a + t3_1.b) / 2)) = t2_1.b)
+                                 ->  Sort
+                                       Output: t3_1.a, t3_1.b, t3_1.c, (((t3_1.a + t3_1.b) / 2))
+                                       Sort Key: (((t3_1.a + t3_1.b) / 2))
+                                       ->  Seq Scan on public.prt1_e_p2 t3_1
+                                             Output: t3_1.a, t3_1.b, t3_1.c, ((t3_1.a + t3_1.b) / 2)
+                                             Filter: ((t3_1.a % 25) = 0)
+                                 ->  Sort
+                                       Output: t2_1.b, t2_1.c
+                                       Sort Key: t2_1.b
+                                       ->  Seq Scan on public.prt2_p2 t2_1
+                                             Output: t2_1.b, t2_1.c
+                     ->  Sort
+                           Output: t1_2.a, t1_2.c
+                           Sort Key: t1_2.a
+                           ->  Seq Scan on public.prt1_p2 t1_2
+                                 Output: t1_2.a, t1_2.c
+               ->  Merge Left Join
+                     Output: t3_2.a, t3_2.b, t3_2.c, t2_2.b, t2_2.c, t1_1.a, t1_1.c
+                     Merge Cond: (t2_2.b = t1_1.a)
+                     ->  Sort
+                           Output: t3_2.a, t3_2.b, t3_2.c, t2_2.b, t2_2.c
+                           Sort Key: t2_2.b
+                           ->  Merge Left Join
+                                 Output: t3_2.a, t3_2.b, t3_2.c, t2_2.b, t2_2.c
+                                 Merge Cond: ((((t3_2.a + t3_2.b) / 2)) = t2_2.b)
+                                 ->  Sort
+                                       Output: t3_2.a, t3_2.b, t3_2.c, (((t3_2.a + t3_2.b) / 2))
+                                       Sort Key: (((t3_2.a + t3_2.b) / 2))
+                                       ->  Seq Scan on public.prt1_e_p3 t3_2
+                                             Output: t3_2.a, t3_2.b, t3_2.c, ((t3_2.a + t3_2.b) / 2)
+                                             Filter: ((t3_2.a % 25) = 0)
+                                 ->  Sort
+                                       Output: t2_2.b, t2_2.c
+                                       Sort Key: t2_2.b
+                                       ->  Seq Scan on public.prt2_p3 t2_2
+                                             Output: t2_2.b, t2_2.c
+                     ->  Sort
+                           Output: t1_1.a, t1_1.c
+                           Sort Key: t1_1.a
+                           ->  Seq Scan on public.prt1_p3 t1_1
+                                 Output: t1_1.a, t1_1.c
+(81 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t2.b = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+  a  |  c   |  b  |  c   | ?column? |  c   
+-----+------+-----+------+----------+------
+   0 | 0000 |   0 | 0000 |        0 | 0000
+ 150 | 0150 | 150 | 0150 |      300 | 0150
+ 300 | 0300 | 300 | 0300 |      600 | 0300
+ 450 | 0450 | 450 | 0450 |      900 | 0450
+     |      |     |      |      100 | 0050
+     |      |     |      |      200 | 0100
+     |      |     |      |      400 | 0200
+     |      |     |      |      500 | 0250
+     |      |     |      |      700 | 0350
+     |      |     |      |      800 | 0400
+     |      |     |      |     1000 | 0500
+     |      |     |      |     1100 | 0550
+(12 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (uprt1 t1 RIGHT JOIN uprt2 t2 ON t1.a = t2.b) RIGHT JOIN uprt1_e t3 ON (t2.b = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+  a  |  c   |  b  |  c   | ?column? |  c   
+-----+------+-----+------+----------+------
+   0 | 0000 |   0 | 0000 |        0 | 0000
+ 150 | 0150 | 150 | 0150 |      300 | 0150
+ 300 | 0300 | 300 | 0300 |      600 | 0300
+ 450 | 0450 | 450 | 0450 |      900 | 0450
+     |      |     |      |      100 | 0050
+     |      |     |      |      200 | 0100
+     |      |     |      |      400 | 0200
+     |      |     |      |      500 | 0250
+     |      |     |      |      700 | 0350
+     |      |     |      |      800 | 0400
+     |      |     |      |     1000 | 0500
+     |      |     |      |     1100 | 0550
+(12 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.a %25 = 0)) AND t1.a % 25 = 0 ORDER BY t1.a;
+                                   QUERY PLAN                                    
+---------------------------------------------------------------------------------
+ Merge Append
+   Sort Key: t1.a
+   ->  Merge Semi Join
+         Output: t1.a, t1.b, t1.c
+         Merge Cond: (t1.a = t1_3.b)
+         ->  Sort
+               Output: t1.a, t1.b, t1.c
+               Sort Key: t1.a
+               ->  Seq Scan on public.prt1_p1 t1
+                     Output: t1.a, t1.b, t1.c
+                     Filter: ((t1.a % 25) = 0)
+         ->  Materialize
+               Output: t1_3.b, t1_6.a, t1_6.b
+               ->  Merge Semi Join
+                     Output: t1_3.b, t1_6.a, t1_6.b
+                     Merge Cond: (t1_3.b = (((t1_6.a + t1_6.b) / 2)))
+                     ->  Sort
+                           Output: t1_3.b
+                           Sort Key: t1_3.b
+                           ->  Seq Scan on public.prt2_p1 t1_3
+                                 Output: t1_3.b
+                     ->  Sort
+                           Output: t1_6.a, t1_6.b, (((t1_6.a + t1_6.b) / 2))
+                           Sort Key: (((t1_6.a + t1_6.b) / 2))
+                           ->  Seq Scan on public.prt1_e_p1 t1_6
+                                 Output: t1_6.a, t1_6.b, ((t1_6.a + t1_6.b) / 2)
+                                 Filter: ((t1_6.a % 25) = 0)
+   ->  Merge Semi Join
+         Output: t1_2.a, t1_2.b, t1_2.c
+         Merge Cond: (t1_2.a = t1_4.b)
+         ->  Sort
+               Output: t1_2.a, t1_2.b, t1_2.c
+               Sort Key: t1_2.a
+               ->  Seq Scan on public.prt1_p2 t1_2
+                     Output: t1_2.a, t1_2.b, t1_2.c
+                     Filter: ((t1_2.a % 25) = 0)
+         ->  Materialize
+               Output: t1_4.b, t1_7.a, t1_7.b
+               ->  Merge Semi Join
+                     Output: t1_4.b, t1_7.a, t1_7.b
+                     Merge Cond: (t1_4.b = (((t1_7.a + t1_7.b) / 2)))
+                     ->  Sort
+                           Output: t1_4.b
+                           Sort Key: t1_4.b
+                           ->  Seq Scan on public.prt2_p2 t1_4
+                                 Output: t1_4.b
+                     ->  Sort
+                           Output: t1_7.a, t1_7.b, (((t1_7.a + t1_7.b) / 2))
+                           Sort Key: (((t1_7.a + t1_7.b) / 2))
+                           ->  Seq Scan on public.prt1_e_p2 t1_7
+                                 Output: t1_7.a, t1_7.b, ((t1_7.a + t1_7.b) / 2)
+                                 Filter: ((t1_7.a % 25) = 0)
+   ->  Merge Semi Join
+         Output: t1_1.a, t1_1.b, t1_1.c
+         Merge Cond: (t1_1.a = t1_5.b)
+         ->  Sort
+               Output: t1_1.a, t1_1.b, t1_1.c
+               Sort Key: t1_1.a
+               ->  Seq Scan on public.prt1_p3 t1_1
+                     Output: t1_1.a, t1_1.b, t1_1.c
+                     Filter: ((t1_1.a % 25) = 0)
+         ->  Materialize
+               Output: t1_5.b, t1_8.a, t1_8.b
+               ->  Merge Semi Join
+                     Output: t1_5.b, t1_8.a, t1_8.b
+                     Merge Cond: (t1_5.b = (((t1_8.a + t1_8.b) / 2)))
+                     ->  Sort
+                           Output: t1_5.b
+                           Sort Key: t1_5.b
+                           ->  Seq Scan on public.prt2_p3 t1_5
+                                 Output: t1_5.b
+                     ->  Sort
+                           Output: t1_8.a, t1_8.b, (((t1_8.a + t1_8.b) / 2))
+                           Sort Key: (((t1_8.a + t1_8.b) / 2))
+                           ->  Seq Scan on public.prt1_e_p3 t1_8
+                                 Output: t1_8.a, t1_8.b, ((t1_8.a + t1_8.b) / 2)
+                                 Filter: ((t1_8.a % 25) = 0)
+(77 rows)
+
+SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.a %25 = 0)) AND t1.a % 25 = 0 ORDER BY t1.a;
+  a  |  b  |  c   
+-----+-----+------
+   0 |   0 | 0000
+ 150 | 150 | 0150
+ 300 | 300 | 0300
+ 450 | 450 | 0450
+(4 rows)
+
+SELECT t1.* FROM uprt1 t1 WHERE t1.a IN (SELECT t1.b FROM uprt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM uprt1_e t1 WHERE t1.a %25 = 0)) AND t1.a % 25 = 0 ORDER BY t1.a;
+  a  |  b  |  c   
+-----+-----+------
+   0 |   0 | 0000
+ 150 | 150 | 0150
+ 300 | 300 | 0300
+ 450 | 450 | 0450
+(4 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+                                             QUERY PLAN                                              
+-----------------------------------------------------------------------------------------------------
+ Sort
+   Output: t1.a, t1.c, t2.b, t2.c, ((t3.a + t3.b)), t3.c
+   Sort Key: t1.a, t2.b, ((t3.a + t3.b))
+   ->  Result
+         Output: t1.a, t1.c, t2.b, t2.c, (t3.a + t3.b), t3.c
+         ->  Append
+               ->  Merge Left Join
+                     Output: t3.a, t3.b, t3.c, t1.a, t1.c, t2.b, t2.c
+                     Merge Cond: (t1.a = t2.b)
+                     ->  Sort
+                           Output: t3.a, t3.b, t3.c, t1.a, t1.c
+                           Sort Key: t1.a
+                           ->  Merge Left Join
+                                 Output: t3.a, t3.b, t3.c, t1.a, t1.c
+                                 Merge Cond: ((((t3.a + t3.b) / 2)) = t1.a)
+                                 ->  Sort
+                                       Output: t3.a, t3.b, t3.c, (((t3.a + t3.b) / 2))
+                                       Sort Key: (((t3.a + t3.b) / 2))
+                                       ->  Seq Scan on public.prt1_e_p1 t3
+                                             Output: t3.a, t3.b, t3.c, ((t3.a + t3.b) / 2)
+                                             Filter: ((t3.a % 25) = 0)
+                                 ->  Sort
+                                       Output: t1.a, t1.c
+                                       Sort Key: t1.a
+                                       ->  Seq Scan on public.prt1_p1 t1
+                                             Output: t1.a, t1.c
+                     ->  Sort
+                           Output: t2.b, t2.c
+                           Sort Key: t2.b
+                           ->  Seq Scan on public.prt2_p1 t2
+                                 Output: t2.b, t2.c
+               ->  Merge Left Join
+                     Output: t3_1.a, t3_1.b, t3_1.c, t1_2.a, t1_2.c, t2_1.b, t2_1.c
+                     Merge Cond: (t1_2.a = t2_1.b)
+                     ->  Sort
+                           Output: t3_1.a, t3_1.b, t3_1.c, t1_2.a, t1_2.c
+                           Sort Key: t1_2.a
+                           ->  Merge Left Join
+                                 Output: t3_1.a, t3_1.b, t3_1.c, t1_2.a, t1_2.c
+                                 Merge Cond: ((((t3_1.a + t3_1.b) / 2)) = t1_2.a)
+                                 ->  Sort
+                                       Output: t3_1.a, t3_1.b, t3_1.c, (((t3_1.a + t3_1.b) / 2))
+                                       Sort Key: (((t3_1.a + t3_1.b) / 2))
+                                       ->  Seq Scan on public.prt1_e_p2 t3_1
+                                             Output: t3_1.a, t3_1.b, t3_1.c, ((t3_1.a + t3_1.b) / 2)
+                                             Filter: ((t3_1.a % 25) = 0)
+                                 ->  Sort
+                                       Output: t1_2.a, t1_2.c
+                                       Sort Key: t1_2.a
+                                       ->  Seq Scan on public.prt1_p2 t1_2
+                                             Output: t1_2.a, t1_2.c
+                     ->  Sort
+                           Output: t2_1.b, t2_1.c
+                           Sort Key: t2_1.b
+                           ->  Seq Scan on public.prt2_p2 t2_1
+                                 Output: t2_1.b, t2_1.c
+               ->  Merge Left Join
+                     Output: t3_2.a, t3_2.b, t3_2.c, t1_1.a, t1_1.c, t2_2.b, t2_2.c
+                     Merge Cond: (t1_1.a = t2_2.b)
+                     ->  Sort
+                           Output: t3_2.a, t3_2.b, t3_2.c, t1_1.a, t1_1.c
+                           Sort Key: t1_1.a
+                           ->  Merge Left Join
+                                 Output: t3_2.a, t3_2.b, t3_2.c, t1_1.a, t1_1.c
+                                 Merge Cond: ((((t3_2.a + t3_2.b) / 2)) = t1_1.a)
+                                 ->  Sort
+                                       Output: t3_2.a, t3_2.b, t3_2.c, (((t3_2.a + t3_2.b) / 2))
+                                       Sort Key: (((t3_2.a + t3_2.b) / 2))
+                                       ->  Seq Scan on public.prt1_e_p3 t3_2
+                                             Output: t3_2.a, t3_2.b, t3_2.c, ((t3_2.a + t3_2.b) / 2)
+                                             Filter: ((t3_2.a % 25) = 0)
+                                 ->  Sort
+                                       Output: t1_1.a, t1_1.c
+                                       Sort Key: t1_1.a
+                                       ->  Seq Scan on public.prt1_p3 t1_1
+                                             Output: t1_1.a, t1_1.c
+                     ->  Sort
+                           Output: t2_2.b, t2_2.c
+                           Sort Key: t2_2.b
+                           ->  Seq Scan on public.prt2_p3 t2_2
+                                 Output: t2_2.b, t2_2.c
+(81 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+  a  |  c   |  b  |  c   | ?column? |  c   
+-----+------+-----+------+----------+------
+   0 | 0000 |   0 | 0000 |        0 | 0000
+  50 | 0050 |     |      |      100 | 0050
+ 100 | 0100 |     |      |      200 | 0100
+ 150 | 0150 | 150 | 0150 |      300 | 0150
+ 200 | 0200 |     |      |      400 | 0200
+ 250 | 0250 |     |      |      500 | 0250
+ 300 | 0300 | 300 | 0300 |      600 | 0300
+ 350 | 0350 |     |      |      700 | 0350
+ 400 | 0400 |     |      |      800 | 0400
+ 450 | 0450 | 450 | 0450 |      900 | 0450
+ 500 | 0500 |     |      |     1000 | 0500
+ 550 | 0550 |     |      |     1100 | 0550
+(12 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (uprt1 t1 LEFT JOIN uprt2 t2 ON t1.a = t2.b) RIGHT JOIN uprt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+  a  |  c   |  b  |  c   | ?column? |  c   
+-----+------+-----+------+----------+------
+   0 | 0000 |   0 | 0000 |        0 | 0000
+  50 | 0050 |     |      |      100 | 0050
+ 100 | 0100 |     |      |      200 | 0100
+ 150 | 0150 | 150 | 0150 |      300 | 0150
+ 200 | 0200 |     |      |      400 | 0200
+ 250 | 0250 |     |      |      500 | 0250
+ 300 | 0300 | 300 | 0300 |      600 | 0300
+ 350 | 0350 |     |      |      700 | 0350
+ 400 | 0400 |     |      |      800 | 0400
+ 450 | 0450 | 450 | 0450 |      900 | 0450
+ 500 | 0500 |     |      |     1000 | 0500
+ 550 | 0550 |     |      |     1100 | 0550
+(12 rows)
+
+SET enable_seqscan TO off;
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t2.b = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+                                          QUERY PLAN                                          
+----------------------------------------------------------------------------------------------
+ Sort
+   Output: t1.a, t1.c, t2.b, t2.c, ((t3.a + t3.b)), t3.c
+   Sort Key: t1.a, t2.b, ((t3.a + t3.b))
+   ->  Result
+         Output: t1.a, t1.c, t2.b, t2.c, (t3.a + t3.b), t3.c
+         ->  Append
+               ->  Merge Left Join
+                     Output: t3.a, t3.b, t3.c, t2.b, t2.c, t1.a, t1.c
+                     Merge Cond: (t2.b = t1.a)
+                     ->  Sort
+                           Output: t3.a, t3.b, t3.c, t2.b, t2.c
+                           Sort Key: t2.b
+                           ->  Merge Left Join
+                                 Output: t3.a, t3.b, t3.c, t2.b, t2.c
+                                 Merge Cond: (((t3.a + t3.b) / 2) = t2.b)
+                                 ->  Index Scan using iprt1_e_p1_ab2 on public.prt1_e_p1 t3
+                                       Output: t3.a, t3.b, t3.c
+                                       Filter: ((t3.a % 25) = 0)
+                                 ->  Index Scan using iprt2_p1_b on public.prt2_p1 t2
+                                       Output: t2.b, t2.c
+                     ->  Index Scan using iprt1_p1_a on public.prt1_p1 t1
+                           Output: t1.a, t1.c
+               ->  Merge Left Join
+                     Output: t3_1.a, t3_1.b, t3_1.c, t2_1.b, t2_1.c, t1_2.a, t1_2.c
+                     Merge Cond: (t2_1.b = t1_2.a)
+                     ->  Sort
+                           Output: t3_1.a, t3_1.b, t3_1.c, t2_1.b, t2_1.c
+                           Sort Key: t2_1.b
+                           ->  Merge Left Join
+                                 Output: t3_1.a, t3_1.b, t3_1.c, t2_1.b, t2_1.c
+                                 Merge Cond: (((t3_1.a + t3_1.b) / 2) = t2_1.b)
+                                 ->  Index Scan using iprt1_e_p2_ab2 on public.prt1_e_p2 t3_1
+                                       Output: t3_1.a, t3_1.b, t3_1.c
+                                       Filter: ((t3_1.a % 25) = 0)
+                                 ->  Index Scan using iprt2_p2_b on public.prt2_p2 t2_1
+                                       Output: t2_1.b, t2_1.c
+                     ->  Index Scan using iprt1_p2_a on public.prt1_p2 t1_2
+                           Output: t1_2.a, t1_2.c
+               ->  Merge Right Join
+                     Output: t3_2.a, t3_2.b, t3_2.c, t2_2.b, t2_2.c, t1_1.a, t1_1.c
+                     Merge Cond: (t2_2.b = ((t3_2.a + t3_2.b) / 2))
+                     ->  Merge Left Join
+                           Output: t2_2.b, t2_2.c, t1_1.a, t1_1.c
+                           Merge Cond: (t2_2.b = t1_1.a)
+                           ->  Index Scan using iprt2_p3_b on public.prt2_p3 t2_2
+                                 Output: t2_2.b, t2_2.c
+                           ->  Index Scan using iprt1_p3_a on public.prt1_p3 t1_1
+                                 Output: t1_1.a, t1_1.c
+                     ->  Index Scan using iprt1_e_p3_ab2 on public.prt1_e_p3 t3_2
+                           Output: t3_2.a, t3_2.b, t3_2.c
+                           Filter: ((t3_2.a % 25) = 0)
+(51 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t2.b = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+  a  |  c   |  b  |  c   | ?column? |  c   
+-----+------+-----+------+----------+------
+   0 | 0000 |   0 | 0000 |        0 | 0000
+ 150 | 0150 | 150 | 0150 |      300 | 0150
+ 300 | 0300 | 300 | 0300 |      600 | 0300
+ 450 | 0450 | 450 | 0450 |      900 | 0450
+     |      |     |      |      100 | 0050
+     |      |     |      |      200 | 0100
+     |      |     |      |      400 | 0200
+     |      |     |      |      500 | 0250
+     |      |     |      |      700 | 0350
+     |      |     |      |      800 | 0400
+     |      |     |      |     1000 | 0500
+     |      |     |      |     1100 | 0550
+(12 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (uprt1 t1 RIGHT JOIN uprt2 t2 ON t1.a = t2.b) RIGHT JOIN uprt1_e t3 ON (t2.b = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+  a  |  c   |  b  |  c   | ?column? |  c   
+-----+------+-----+------+----------+------
+   0 | 0000 |   0 | 0000 |        0 | 0000
+ 150 | 0150 | 150 | 0150 |      300 | 0150
+ 300 | 0300 | 300 | 0300 |      600 | 0300
+ 450 | 0450 | 450 | 0450 |      900 | 0450
+     |      |     |      |      100 | 0050
+     |      |     |      |      200 | 0100
+     |      |     |      |      400 | 0200
+     |      |     |      |      500 | 0250
+     |      |     |      |      700 | 0350
+     |      |     |      |      800 | 0400
+     |      |     |      |     1000 | 0500
+     |      |     |      |     1100 | 0550
+(12 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.a %25 = 0)) AND t1.a % 25 = 0 ORDER BY t1.a;
+                                    QUERY PLAN                                    
+----------------------------------------------------------------------------------
+ Merge Append
+   Sort Key: t1.a
+   ->  Merge Semi Join
+         Output: t1.a, t1.b, t1.c
+         Merge Cond: (t1.a = t1_3.b)
+         ->  Index Scan using iprt1_p1_a on public.prt1_p1 t1
+               Output: t1.a, t1.b, t1.c
+               Filter: ((t1.a % 25) = 0)
+         ->  Materialize
+               Output: t1_3.b, t1_6.a, t1_6.b
+               ->  Merge Semi Join
+                     Output: t1_3.b, t1_6.a, t1_6.b
+                     Merge Cond: (t1_3.b = ((t1_6.a + t1_6.b) / 2))
+                     ->  Index Only Scan using iprt2_p1_b on public.prt2_p1 t1_3
+                           Output: t1_3.b
+                     ->  Index Scan using iprt1_e_p1_ab2 on public.prt1_e_p1 t1_6
+                           Output: t1_6.a, t1_6.b
+                           Filter: ((t1_6.a % 25) = 0)
+   ->  Merge Semi Join
+         Output: t1_2.a, t1_2.b, t1_2.c
+         Merge Cond: (t1_2.a = t1_4.b)
+         ->  Index Scan using iprt1_p2_a on public.prt1_p2 t1_2
+               Output: t1_2.a, t1_2.b, t1_2.c
+               Filter: ((t1_2.a % 25) = 0)
+         ->  Materialize
+               Output: t1_4.b, t1_7.a, t1_7.b
+               ->  Merge Semi Join
+                     Output: t1_4.b, t1_7.a, t1_7.b
+                     Merge Cond: (t1_4.b = ((t1_7.a + t1_7.b) / 2))
+                     ->  Index Only Scan using iprt2_p2_b on public.prt2_p2 t1_4
+                           Output: t1_4.b
+                     ->  Index Scan using iprt1_e_p2_ab2 on public.prt1_e_p2 t1_7
+                           Output: t1_7.a, t1_7.b
+                           Filter: ((t1_7.a % 25) = 0)
+   ->  Merge Semi Join
+         Output: t1_1.a, t1_1.b, t1_1.c
+         Merge Cond: (t1_1.a = t1_5.b)
+         ->  Index Scan using iprt1_p3_a on public.prt1_p3 t1_1
+               Output: t1_1.a, t1_1.b, t1_1.c
+               Filter: ((t1_1.a % 25) = 0)
+         ->  Materialize
+               Output: t1_5.b, t1_8.a, t1_8.b
+               ->  Merge Semi Join
+                     Output: t1_5.b, t1_8.a, t1_8.b
+                     Merge Cond: (t1_5.b = ((t1_8.a + t1_8.b) / 2))
+                     ->  Index Only Scan using iprt2_p3_b on public.prt2_p3 t1_5
+                           Output: t1_5.b
+                     ->  Index Scan using iprt1_e_p3_ab2 on public.prt1_e_p3 t1_8
+                           Output: t1_8.a, t1_8.b
+                           Filter: ((t1_8.a % 25) = 0)
+(50 rows)
+
+SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.a %25 = 0)) AND t1.a % 25 = 0 ORDER BY t1.a;
+  a  |  b  |  c   
+-----+-----+------
+   0 |   0 | 0000
+ 150 | 150 | 0150
+ 300 | 300 | 0300
+ 450 | 450 | 0450
+(4 rows)
+
+SELECT t1.* FROM uprt1 t1 WHERE t1.a IN (SELECT t1.b FROM uprt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM uprt1_e t1 WHERE t1.a %25 = 0)) AND t1.a % 25 = 0 ORDER BY t1.a;
+  a  |  b  |  c   
+-----+-----+------
+   0 |   0 | 0000
+ 150 | 150 | 0150
+ 300 | 300 | 0300
+ 450 | 450 | 0450
+(4 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+                                          QUERY PLAN                                          
+----------------------------------------------------------------------------------------------
+ Sort
+   Output: t1.a, t1.c, t2.b, t2.c, ((t3.a + t3.b)), t3.c
+   Sort Key: t1.a, t2.b, ((t3.a + t3.b))
+   ->  Result
+         Output: t1.a, t1.c, t2.b, t2.c, (t3.a + t3.b), t3.c
+         ->  Append
+               ->  Merge Left Join
+                     Output: t3.a, t3.b, t3.c, t1.a, t1.c, t2.b, t2.c
+                     Merge Cond: (t1.a = t2.b)
+                     ->  Sort
+                           Output: t3.a, t3.b, t3.c, t1.a, t1.c
+                           Sort Key: t1.a
+                           ->  Merge Left Join
+                                 Output: t3.a, t3.b, t3.c, t1.a, t1.c
+                                 Merge Cond: (((t3.a + t3.b) / 2) = t1.a)
+                                 ->  Index Scan using iprt1_e_p1_ab2 on public.prt1_e_p1 t3
+                                       Output: t3.a, t3.b, t3.c
+                                       Filter: ((t3.a % 25) = 0)
+                                 ->  Index Scan using iprt1_p1_a on public.prt1_p1 t1
+                                       Output: t1.a, t1.c
+                     ->  Index Scan using iprt2_p1_b on public.prt2_p1 t2
+                           Output: t2.b, t2.c
+               ->  Merge Left Join
+                     Output: t3_1.a, t3_1.b, t3_1.c, t1_2.a, t1_2.c, t2_1.b, t2_1.c
+                     Merge Cond: (t1_2.a = t2_1.b)
+                     ->  Sort
+                           Output: t3_1.a, t3_1.b, t3_1.c, t1_2.a, t1_2.c
+                           Sort Key: t1_2.a
+                           ->  Merge Left Join
+                                 Output: t3_1.a, t3_1.b, t3_1.c, t1_2.a, t1_2.c
+                                 Merge Cond: (((t3_1.a + t3_1.b) / 2) = t1_2.a)
+                                 ->  Index Scan using iprt1_e_p2_ab2 on public.prt1_e_p2 t3_1
+                                       Output: t3_1.a, t3_1.b, t3_1.c
+                                       Filter: ((t3_1.a % 25) = 0)
+                                 ->  Index Scan using iprt1_p2_a on public.prt1_p2 t1_2
+                                       Output: t1_2.a, t1_2.c
+                     ->  Index Scan using iprt2_p2_b on public.prt2_p2 t2_1
+                           Output: t2_1.b, t2_1.c
+               ->  Merge Left Join
+                     Output: t3_2.a, t3_2.b, t3_2.c, t1_1.a, t1_1.c, t2_2.b, t2_2.c
+                     Merge Cond: (t1_1.a = t2_2.b)
+                     ->  Sort
+                           Output: t3_2.a, t3_2.b, t3_2.c, t1_1.a, t1_1.c
+                           Sort Key: t1_1.a
+                           ->  Merge Left Join
+                                 Output: t3_2.a, t3_2.b, t3_2.c, t1_1.a, t1_1.c
+                                 Merge Cond: (((t3_2.a + t3_2.b) / 2) = t1_1.a)
+                                 ->  Index Scan using iprt1_e_p3_ab2 on public.prt1_e_p3 t3_2
+                                       Output: t3_2.a, t3_2.b, t3_2.c
+                                       Filter: ((t3_2.a % 25) = 0)
+                                 ->  Index Scan using iprt1_p3_a on public.prt1_p3 t1_1
+                                       Output: t1_1.a, t1_1.c
+                     ->  Index Scan using iprt2_p3_b on public.prt2_p3 t2_2
+                           Output: t2_2.b, t2_2.c
+(54 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+  a  |  c   |  b  |  c   | ?column? |  c   
+-----+------+-----+------+----------+------
+   0 | 0000 |   0 | 0000 |        0 | 0000
+  50 | 0050 |     |      |      100 | 0050
+ 100 | 0100 |     |      |      200 | 0100
+ 150 | 0150 | 150 | 0150 |      300 | 0150
+ 200 | 0200 |     |      |      400 | 0200
+ 250 | 0250 |     |      |      500 | 0250
+ 300 | 0300 | 300 | 0300 |      600 | 0300
+ 350 | 0350 |     |      |      700 | 0350
+ 400 | 0400 |     |      |      800 | 0400
+ 450 | 0450 | 450 | 0450 |      900 | 0450
+ 500 | 0500 |     |      |     1000 | 0500
+ 550 | 0550 |     |      |     1100 | 0550
+(12 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (uprt1 t1 LEFT JOIN uprt2 t2 ON t1.a = t2.b) RIGHT JOIN uprt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+  a  |  c   |  b  |  c   | ?column? |  c   
+-----+------+-----+------+----------+------
+   0 | 0000 |   0 | 0000 |        0 | 0000
+  50 | 0050 |     |      |      100 | 0050
+ 100 | 0100 |     |      |      200 | 0100
+ 150 | 0150 | 150 | 0150 |      300 | 0150
+ 200 | 0200 |     |      |      400 | 0200
+ 250 | 0250 |     |      |      500 | 0250
+ 300 | 0300 | 300 | 0300 |      600 | 0300
+ 350 | 0350 |     |      |      700 | 0350
+ 400 | 0400 |     |      |      800 | 0400
+ 450 | 0450 | 450 | 0450 |      900 | 0450
+ 500 | 0500 |     |      |     1000 | 0500
+ 550 | 0550 |     |      |     1100 | 0550
+(12 rows)
+
+-- lateral references and parameterized paths
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT * FROM prt1 t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss
+			  ON t1.a = ss.t2a WHERE t1.a % 25 = 0 ORDER BY t1.a;
+                                          QUERY PLAN                                           
+-----------------------------------------------------------------------------------------------
+ Result
+   Output: t1.a, t1.b, t1.c, t2.a, t3.a, (LEAST(t1.a, t2.a, t3.a)), t1.a
+   ->  Merge Append
+         Sort Key: t1.a
+         ->  Nested Loop Left Join
+               Output: t1.a, t1.b, t1.c, t2.a, t3.a, (LEAST(t1.a, t2.a, t3.a))
+               ->  Index Scan using iprt1_p1_a on public.prt1_p1 t1
+                     Output: t1.a, t1.b, t1.c
+                     Filter: ((t1.a % 25) = 0)
+               ->  Merge Join
+                     Output: t2.a, t3.a, LEAST(t1.a, t2.a, t3.a)
+                     Merge Cond: (t2.a = t3.b)
+                     ->  Index Only Scan using iprt1_p1_a on public.prt1_p1 t2
+                           Output: t2.a
+                           Index Cond: (t2.a = t1.a)
+                     ->  Index Scan using iprt2_p1_b on public.prt2_p1 t3
+                           Output: t3.a, t3.b
+         ->  Nested Loop Left Join
+               Output: t1_2.a, t1_2.b, t1_2.c, t2_2.a, t3_1.a, (LEAST(t1_2.a, t2_2.a, t3_1.a))
+               ->  Index Scan using iprt1_p2_a on public.prt1_p2 t1_2
+                     Output: t1_2.a, t1_2.b, t1_2.c
+                     Filter: ((t1_2.a % 25) = 0)
+               ->  Merge Join
+                     Output: t2_2.a, t3_1.a, LEAST(t1_2.a, t2_2.a, t3_1.a)
+                     Merge Cond: (t2_2.a = t3_1.b)
+                     ->  Index Only Scan using iprt1_p2_a on public.prt1_p2 t2_2
+                           Output: t2_2.a
+                           Index Cond: (t2_2.a = t1_2.a)
+                     ->  Index Scan using iprt2_p2_b on public.prt2_p2 t3_1
+                           Output: t3_1.a, t3_1.b
+         ->  Nested Loop Left Join
+               Output: t1_1.a, t1_1.b, t1_1.c, t2_1.a, t3_2.a, (LEAST(t1_1.a, t2_1.a, t3_2.a))
+               ->  Index Scan using iprt1_p3_a on public.prt1_p3 t1_1
+                     Output: t1_1.a, t1_1.b, t1_1.c
+                     Filter: ((t1_1.a % 25) = 0)
+               ->  Merge Join
+                     Output: t2_1.a, t3_2.a, LEAST(t1_1.a, t2_1.a, t3_2.a)
+                     Merge Cond: (t2_1.a = t3_2.b)
+                     ->  Index Only Scan using iprt1_p3_a on public.prt1_p3 t2_1
+                           Output: t2_1.a
+                           Index Cond: (t2_1.a = t1_1.a)
+                     ->  Index Scan using iprt2_p3_b on public.prt2_p3 t3_2
+                           Output: t3_2.a, t3_2.b
+(43 rows)
+
+SELECT * FROM prt1 t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss
+			  ON t1.a = ss.t2a WHERE t1.a % 25 = 0 ORDER BY t1.a;
+  a  |  b  |  c   | t2a | t3a | least 
+-----+-----+------+-----+-----+-------
+   0 |   0 | 0000 |   0 |   0 |     0
+  50 |  50 | 0050 |     |     |      
+ 100 | 100 | 0100 |     |     |      
+ 150 | 150 | 0150 | 150 | 150 |   150
+ 200 | 200 | 0200 |     |     |      
+ 250 | 250 | 0250 |     |     |      
+ 300 | 300 | 0300 | 300 | 300 |   300
+ 350 | 350 | 0350 |     |     |      
+ 400 | 400 | 0400 |     |     |      
+ 450 | 450 | 0450 | 450 | 450 |   450
+ 500 | 500 | 0500 |     |     |      
+ 550 | 550 | 0550 |     |     |      
+(12 rows)
+
+SELECT * FROM uprt1 t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM uprt1 t2 JOIN uprt2 t3 ON (t2.a = t3.b)) ss
+			  ON t1.a = ss.t2a WHERE t1.a % 25 = 0 ORDER BY t1.a;
+  a  |  b  |  c   | t2a | t3a | least 
+-----+-----+------+-----+-----+-------
+   0 |   0 | 0000 |   0 |   0 |     0
+  50 |  50 | 0050 |     |     |      
+ 100 | 100 | 0100 |     |     |      
+ 150 | 150 | 0150 | 150 | 150 |   150
+ 200 | 200 | 0200 |     |     |      
+ 250 | 250 | 0250 |     |     |      
+ 300 | 300 | 0300 | 300 | 300 |   300
+ 350 | 350 | 0350 |     |     |      
+ 400 | 400 | 0400 |     |     |      
+ 450 | 450 | 0450 | 450 | 450 |   450
+ 500 | 500 | 0500 |     |     |      
+ 550 | 550 | 0550 |     |     |      
+(12 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT * FROM prt1 t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss
+			  ON t1.b = ss.t2a WHERE t1.a % 25 = 0 ORDER BY t1.a;
+                                QUERY PLAN                                 
+---------------------------------------------------------------------------
+ Nested Loop Left Join
+   Output: t1.a, t1.b, t1.c, t2.a, t3.a, (LEAST(t1.a, t2.a, t3.a)), t1.a
+   ->  Merge Append
+         Sort Key: t1.a
+         ->  Sort
+               Output: t1.a, t1.b, t1.c
+               Sort Key: t1.a
+               ->  Seq Scan on public.prt1 t1
+                     Output: t1.a, t1.b, t1.c
+                     Filter: ((t1.a % 25) = 0)
+         ->  Index Scan using iprt1_p1_a on public.prt1_p1 t1_1
+               Output: t1_1.a, t1_1.b, t1_1.c
+               Filter: ((t1_1.a % 25) = 0)
+         ->  Index Scan using iprt1_p3_a on public.prt1_p3 t1_2
+               Output: t1_2.a, t1_2.b, t1_2.c
+               Filter: ((t1_2.a % 25) = 0)
+         ->  Index Scan using iprt1_p2_a on public.prt1_p2 t1_3
+               Output: t1_3.a, t1_3.b, t1_3.c
+               Filter: ((t1_3.a % 25) = 0)
+   ->  Append
+         ->  Merge Join
+               Output: t2.a, t3.a, LEAST(t1.a, t2.a, t3.a)
+               Merge Cond: (t2.a = t3.b)
+               ->  Index Only Scan using iprt1_p1_a on public.prt1_p1 t2
+                     Output: t2.a
+                     Index Cond: (t2.a = t1.b)
+               ->  Index Scan using iprt2_p1_b on public.prt2_p1 t3
+                     Output: t3.a, t3.b
+         ->  Merge Join
+               Output: t2_2.a, t3_1.a, LEAST(t1.a, t2_2.a, t3_1.a)
+               Merge Cond: (t2_2.a = t3_1.b)
+               ->  Index Only Scan using iprt1_p2_a on public.prt1_p2 t2_2
+                     Output: t2_2.a
+                     Index Cond: (t2_2.a = t1.b)
+               ->  Index Scan using iprt2_p2_b on public.prt2_p2 t3_1
+                     Output: t3_1.a, t3_1.b
+         ->  Merge Join
+               Output: t2_1.a, t3_2.a, LEAST(t1.a, t2_1.a, t3_2.a)
+               Merge Cond: (t2_1.a = t3_2.b)
+               ->  Index Only Scan using iprt1_p3_a on public.prt1_p3 t2_1
+                     Output: t2_1.a
+                     Index Cond: (t2_1.a = t1.b)
+               ->  Index Scan using iprt2_p3_b on public.prt2_p3 t3_2
+                     Output: t3_2.a, t3_2.b
+(44 rows)
+
+SELECT * FROM prt1 t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss
+			  ON t1.b = ss.t2a WHERE t1.a % 25 = 0 ORDER BY t1.a;
+  a  |  b  |  c   | t2a | t3a | least 
+-----+-----+------+-----+-----+-------
+   0 |   0 | 0000 |   0 |   0 |     0
+  50 |  50 | 0050 |     |     |      
+ 100 | 100 | 0100 |     |     |      
+ 150 | 150 | 0150 | 150 | 150 |   150
+ 200 | 200 | 0200 |     |     |      
+ 250 | 250 | 0250 |     |     |      
+ 300 | 300 | 0300 | 300 | 300 |   300
+ 350 | 350 | 0350 |     |     |      
+ 400 | 400 | 0400 |     |     |      
+ 450 | 450 | 0450 | 450 | 450 |   450
+ 500 | 500 | 0500 |     |     |      
+ 550 | 550 | 0550 |     |     |      
+(12 rows)
+
+SELECT * FROM uprt1 t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM uprt1 t2 JOIN uprt2 t3 ON (t2.a = t3.b)) ss
+			  ON t1.b = ss.t2a WHERE t1.a % 25 = 0 ORDER BY t1.a;
+  a  |  b  |  c   | t2a | t3a | least 
+-----+-----+------+-----+-----+-------
+   0 |   0 | 0000 |   0 |   0 |     0
+  50 |  50 | 0050 |     |     |      
+ 100 | 100 | 0100 |     |     |      
+ 150 | 150 | 0150 | 150 | 150 |   150
+ 200 | 200 | 0200 |     |     |      
+ 250 | 250 | 0250 |     |     |      
+ 300 | 300 | 0300 | 300 | 300 |   300
+ 350 | 350 | 0350 |     |     |      
+ 400 | 400 | 0400 |     |     |      
+ 450 | 450 | 0450 | 450 | 450 |   450
+ 500 | 500 | 0500 |     |     |      
+ 550 | 550 | 0550 |     |     |      
+(12 rows)
+
+RESET enable_hashjoin;
+RESET enable_nestloop;
+RESET enable_seqscan;
+--
+-- partitioned by multiple columns
+--
+CREATE TABLE prt1_m (a int, b int, c varchar) PARTITION BY RANGE(a, ((a + b)/2));
+CREATE TABLE prt1_m_p1 PARTITION OF prt1_m FOR VALUES FROM (0, 0) TO (250, 250);
+CREATE TABLE prt1_m_p2 PARTITION OF prt1_m FOR VALUES FROM (250, 250) TO (500, 500);
+CREATE TABLE prt1_m_p3 PARTITION OF prt1_m FOR VALUES FROM (500, 500) TO (600, 600);
+INSERT INTO prt1_m SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 2) i;
+ANALYZE prt1_m;
+ANALYZE prt1_m_p1;
+ANALYZE prt1_m_p2;
+ANALYZE prt1_m_p3;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE uprt1_m AS SELECT * FROM prt1_m;
+CREATE TABLE prt2_m (a int, b int, c varchar) PARTITION BY RANGE(((b + a)/2), b);
+CREATE TABLE prt2_m_p1 PARTITION OF prt2_m FOR VALUES FROM (0, 0) TO (250, 250);
+CREATE TABLE prt2_m_p2 PARTITION OF prt2_m FOR VALUES FROM (250, 250) TO (500, 500);
+CREATE TABLE prt2_m_p3 PARTITION OF prt2_m FOR VALUES FROM (500, 500) TO (600, 600);
+INSERT INTO prt2_m SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 3) i;
+ANALYZE prt2_m;
+ANALYZE prt2_m_p1;
+ANALYZE prt2_m_p2;
+ANALYZE prt2_m_p3;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE uprt2_m AS SELECT * FROM prt2_m;
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_m t1 RIGHT JOIN prt2_m t2 ON t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2 WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+                                                 QUERY PLAN                                                 
+------------------------------------------------------------------------------------------------------------
+ Sort
+   Output: t1.a, t1.c, t2.b, t2.c
+   Sort Key: t1.a, t2.b
+   ->  Result
+         Output: t1.a, t1.c, t2.b, t2.c
+         ->  Append
+               ->  Hash Right Join
+                     Output: t2.b, t2.c, t1.a, t1.c
+                     Hash Cond: ((((t1.a + t1.b) / 2) = t2.b) AND (t1.a = ((t2.b + t2.a) / 2)))
+                     ->  Seq Scan on public.prt1_m_p1 t1
+                           Output: t1.a, t1.c, t1.b
+                     ->  Hash
+                           Output: t2.b, t2.c, t2.a
+                           ->  Seq Scan on public.prt2_m_p1 t2
+                                 Output: t2.b, t2.c, t2.a
+                                 Filter: ((t2.b % 25) = 0)
+               ->  Hash Right Join
+                     Output: t2_1.b, t2_1.c, t1_1.a, t1_1.c
+                     Hash Cond: ((((t1_1.a + t1_1.b) / 2) = t2_1.b) AND (t1_1.a = ((t2_1.b + t2_1.a) / 2)))
+                     ->  Seq Scan on public.prt1_m_p2 t1_1
+                           Output: t1_1.a, t1_1.c, t1_1.b
+                     ->  Hash
+                           Output: t2_1.b, t2_1.c, t2_1.a
+                           ->  Seq Scan on public.prt2_m_p2 t2_1
+                                 Output: t2_1.b, t2_1.c, t2_1.a
+                                 Filter: ((t2_1.b % 25) = 0)
+               ->  Hash Right Join
+                     Output: t2_2.b, t2_2.c, t1_2.a, t1_2.c
+                     Hash Cond: ((((t1_2.a + t1_2.b) / 2) = t2_2.b) AND (t1_2.a = ((t2_2.b + t2_2.a) / 2)))
+                     ->  Seq Scan on public.prt1_m_p3 t1_2
+                           Output: t1_2.a, t1_2.c, t1_2.b
+                     ->  Hash
+                           Output: t2_2.b, t2_2.c, t2_2.a
+                           ->  Seq Scan on public.prt2_m_p3 t2_2
+                                 Output: t2_2.b, t2_2.c, t2_2.a
+                                 Filter: ((t2_2.b % 25) = 0)
+(36 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_m t1 RIGHT JOIN prt2_m t2 ON t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2 WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+ 150 | 0150 | 150 | 0150
+ 300 | 0300 | 300 | 0300
+ 450 | 0450 | 450 | 0450
+     |      |  75 | 0075
+     |      | 225 | 0225
+     |      | 375 | 0375
+     |      | 525 | 0525
+(8 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM uprt1_m t1 RIGHT JOIN uprt2_m t2 ON t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2 WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+ 150 | 0150 | 150 | 0150
+ 300 | 0300 | 300 | 0300
+ 450 | 0450 | 450 | 0450
+     |      |  75 | 0075
+     |      | 225 | 0225
+     |      | 375 | 0375
+     |      | 525 | 0525
+(8 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_m WHERE prt1_m.a % 25 = 0) t1 FULL JOIN (SELECT * FROM prt2_m WHERE prt2_m.b % 25 = 0) t2 ON (t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2) ORDER BY t1.a, t2.b;
+                                                             QUERY PLAN                                                             
+------------------------------------------------------------------------------------------------------------------------------------
+ Sort
+   Output: prt1_m_p1.a, prt1_m_p1.c, prt2_m_p1.b, prt2_m_p1.c
+   Sort Key: prt1_m_p1.a, prt2_m_p1.b
+   ->  Append
+         ->  Hash Full Join
+               Output: prt1_m_p1.a, prt1_m_p1.c, prt2_m_p1.b, prt2_m_p1.c
+               Hash Cond: ((prt1_m_p1.a = ((prt2_m_p1.b + prt2_m_p1.a) / 2)) AND (((prt1_m_p1.a + prt1_m_p1.b) / 2) = prt2_m_p1.b))
+               ->  Seq Scan on public.prt1_m_p1
+                     Output: prt1_m_p1.a, prt1_m_p1.c, prt1_m_p1.b
+                     Filter: ((prt1_m_p1.a % 25) = 0)
+               ->  Hash
+                     Output: prt2_m_p1.b, prt2_m_p1.c, prt2_m_p1.a
+                     ->  Seq Scan on public.prt2_m_p1
+                           Output: prt2_m_p1.b, prt2_m_p1.c, prt2_m_p1.a
+                           Filter: ((prt2_m_p1.b % 25) = 0)
+         ->  Hash Full Join
+               Output: prt1_m_p2.a, prt1_m_p2.c, prt2_m_p2.b, prt2_m_p2.c
+               Hash Cond: ((prt1_m_p2.a = ((prt2_m_p2.b + prt2_m_p2.a) / 2)) AND (((prt1_m_p2.a + prt1_m_p2.b) / 2) = prt2_m_p2.b))
+               ->  Seq Scan on public.prt1_m_p2
+                     Output: prt1_m_p2.a, prt1_m_p2.c, prt1_m_p2.b
+                     Filter: ((prt1_m_p2.a % 25) = 0)
+               ->  Hash
+                     Output: prt2_m_p2.b, prt2_m_p2.c, prt2_m_p2.a
+                     ->  Seq Scan on public.prt2_m_p2
+                           Output: prt2_m_p2.b, prt2_m_p2.c, prt2_m_p2.a
+                           Filter: ((prt2_m_p2.b % 25) = 0)
+         ->  Hash Full Join
+               Output: prt1_m_p3.a, prt1_m_p3.c, prt2_m_p3.b, prt2_m_p3.c
+               Hash Cond: ((prt1_m_p3.a = ((prt2_m_p3.b + prt2_m_p3.a) / 2)) AND (((prt1_m_p3.a + prt1_m_p3.b) / 2) = prt2_m_p3.b))
+               ->  Seq Scan on public.prt1_m_p3
+                     Output: prt1_m_p3.a, prt1_m_p3.c, prt1_m_p3.b
+                     Filter: ((prt1_m_p3.a % 25) = 0)
+               ->  Hash
+                     Output: prt2_m_p3.b, prt2_m_p3.c, prt2_m_p3.a
+                     ->  Seq Scan on public.prt2_m_p3
+                           Output: prt2_m_p3.b, prt2_m_p3.c, prt2_m_p3.a
+                           Filter: ((prt2_m_p3.b % 25) = 0)
+(37 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_m WHERE prt1_m.a % 25 = 0) t1 FULL JOIN (SELECT * FROM prt2_m WHERE prt2_m.b % 25 = 0) t2 ON (t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2) ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+  50 | 0050 |     | 
+ 100 | 0100 |     | 
+ 150 | 0150 | 150 | 0150
+ 200 | 0200 |     | 
+ 250 | 0250 |     | 
+ 300 | 0300 | 300 | 0300
+ 350 | 0350 |     | 
+ 400 | 0400 |     | 
+ 450 | 0450 | 450 | 0450
+ 500 | 0500 |     | 
+ 550 | 0550 |     | 
+     |      |  75 | 0075
+     |      | 225 | 0225
+     |      | 375 | 0375
+     |      | 525 | 0525
+(16 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM uprt1_m t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM uprt2_m t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2) ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+  50 | 0050 |     | 
+ 100 | 0100 |     | 
+ 150 | 0150 | 150 | 0150
+ 200 | 0200 |     | 
+ 250 | 0250 |     | 
+ 300 | 0300 | 300 | 0300
+ 350 | 0350 |     | 
+ 400 | 0400 |     | 
+ 450 | 0450 | 450 | 0450
+ 500 | 0500 |     | 
+ 550 | 0550 |     | 
+     |      |  75 | 0075
+     |      | 225 | 0225
+     |      | 375 | 0375
+     |      | 525 | 0525
+(16 rows)
+
+--
+-- tests for list partitioned tables.
+--
+CREATE TABLE plt1 (a int, b int, c text) PARTITION BY LIST(c);
+CREATE TABLE plt1_p1 PARTITION OF plt1 FOR VALUES IN ('0000', '0003', '0004', '0010');
+CREATE TABLE plt1_p2 PARTITION OF plt1 FOR VALUES IN ('0001', '0005', '0002', '0009');
+CREATE TABLE plt1_p3 PARTITION OF plt1 FOR VALUES IN ('0006', '0007', '0008', '0011');
+INSERT INTO plt1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i;
+ANALYZE plt1;
+ANALYZE plt1_p1;
+ANALYZE plt1_p2;
+ANALYZE plt1_p3;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE uplt1 AS SELECT * FROM plt1;
+CREATE TABLE plt2 (a int, b int, c text) PARTITION BY LIST(c);
+CREATE TABLE plt2_p1 PARTITION OF plt2 FOR VALUES IN ('0000', '0003', '0004', '0010');
+CREATE TABLE plt2_p2 PARTITION OF plt2 FOR VALUES IN ('0001', '0005', '0002', '0009');
+CREATE TABLE plt2_p3 PARTITION OF plt2 FOR VALUES IN ('0006', '0007', '0008', '0011');
+INSERT INTO plt2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 3) i;
+ANALYZE plt2;
+ANALYZE plt2_p1;
+ANALYZE plt2_p2;
+ANALYZE plt2_p3;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE uplt2 AS SELECT * FROM plt2;
+--
+-- list partitioned by expression
+--
+CREATE TABLE plt1_e (a int, b int, c text) PARTITION BY LIST(ltrim(c, 'A'));
+CREATE TABLE plt1_e_p1 PARTITION OF plt1_e FOR VALUES IN ('0000', '0003', '0004', '0010');
+CREATE TABLE plt1_e_p2 PARTITION OF plt1_e FOR VALUES IN ('0001', '0005', '0002', '0009');
+CREATE TABLE plt1_e_p3 PARTITION OF plt1_e FOR VALUES IN ('0006', '0007', '0008', '0011');
+INSERT INTO plt1_e SELECT i, i, 'A' || to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i;
+ANALYZE plt1_e;
+ANALYZE plt1_e_p1;
+ANALYZE plt1_e_p2;
+ANALYZE plt1_e_p3;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE uplt1_e AS SELECT * FROM plt1_e;
+--
+-- N-way join
+--
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM plt1 t1, plt2 t2, plt1_e t3 WHERE t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c;
+                                        QUERY PLAN                                        
+------------------------------------------------------------------------------------------
+ Sort
+   Output: (avg(t1.a)), (avg(t2.b)), (avg((t3.a + t3.b))), t1.c, t2.c, t3.c
+   Sort Key: t1.c, t3.c
+   ->  HashAggregate
+         Output: avg(t1.a), avg(t2.b), avg((t3.a + t3.b)), t1.c, t2.c, t3.c
+         Group Key: t1.c, t2.c, t3.c
+         ->  Result
+               Output: t1.c, t2.c, t3.c, t1.a, t2.b, t3.a, t3.b
+               ->  Append
+                     ->  Hash Join
+                           Output: t1.a, t1.c, t2.b, t2.c, t3.a, t3.b, t3.c
+                           Hash Cond: (t1.c = t2.c)
+                           ->  Seq Scan on public.plt1_p1 t1
+                                 Output: t1.a, t1.c
+                           ->  Hash
+                                 Output: t2.b, t2.c, t3.a, t3.b, t3.c
+                                 ->  Hash Join
+                                       Output: t2.b, t2.c, t3.a, t3.b, t3.c
+                                       Hash Cond: (t2.c = ltrim(t3.c, 'A'::text))
+                                       ->  Seq Scan on public.plt2_p1 t2
+                                             Output: t2.b, t2.c
+                                       ->  Hash
+                                             Output: t3.a, t3.b, t3.c
+                                             ->  Seq Scan on public.plt1_e_p1 t3
+                                                   Output: t3.a, t3.b, t3.c
+                     ->  Hash Join
+                           Output: t1_1.a, t1_1.c, t2_1.b, t2_1.c, t3_1.a, t3_1.b, t3_1.c
+                           Hash Cond: (t1_1.c = t2_1.c)
+                           ->  Seq Scan on public.plt1_p2 t1_1
+                                 Output: t1_1.a, t1_1.c
+                           ->  Hash
+                                 Output: t2_1.b, t2_1.c, t3_1.a, t3_1.b, t3_1.c
+                                 ->  Hash Join
+                                       Output: t2_1.b, t2_1.c, t3_1.a, t3_1.b, t3_1.c
+                                       Hash Cond: (t2_1.c = ltrim(t3_1.c, 'A'::text))
+                                       ->  Seq Scan on public.plt2_p2 t2_1
+                                             Output: t2_1.b, t2_1.c
+                                       ->  Hash
+                                             Output: t3_1.a, t3_1.b, t3_1.c
+                                             ->  Seq Scan on public.plt1_e_p2 t3_1
+                                                   Output: t3_1.a, t3_1.b, t3_1.c
+                     ->  Hash Join
+                           Output: t1_2.a, t1_2.c, t2_2.b, t2_2.c, t3_2.a, t3_2.b, t3_2.c
+                           Hash Cond: (t1_2.c = t2_2.c)
+                           ->  Seq Scan on public.plt1_p3 t1_2
+                                 Output: t1_2.a, t1_2.c
+                           ->  Hash
+                                 Output: t2_2.b, t2_2.c, t3_2.a, t3_2.b, t3_2.c
+                                 ->  Hash Join
+                                       Output: t2_2.b, t2_2.c, t3_2.a, t3_2.b, t3_2.c
+                                       Hash Cond: (t2_2.c = ltrim(t3_2.c, 'A'::text))
+                                       ->  Seq Scan on public.plt2_p3 t2_2
+                                             Output: t2_2.b, t2_2.c
+                                       ->  Hash
+                                             Output: t3_2.a, t3_2.b, t3_2.c
+                                             ->  Seq Scan on public.plt1_e_p3 t3_2
+                                                   Output: t3_2.a, t3_2.b, t3_2.c
+(57 rows)
+
+SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM plt1 t1, plt2 t2, plt1_e t3 WHERE t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c;
+         avg          |         avg          |          avg          |  c   |  c   |   c   
+----------------------+----------------------+-----------------------+------+------+-------
+  24.0000000000000000 |  24.0000000000000000 |   48.0000000000000000 | 0000 | 0000 | A0000
+  74.0000000000000000 |  75.0000000000000000 |  148.0000000000000000 | 0001 | 0001 | A0001
+ 124.0000000000000000 | 124.5000000000000000 |  248.0000000000000000 | 0002 | 0002 | A0002
+ 174.0000000000000000 | 174.0000000000000000 |  348.0000000000000000 | 0003 | 0003 | A0003
+ 224.0000000000000000 | 225.0000000000000000 |  448.0000000000000000 | 0004 | 0004 | A0004
+ 274.0000000000000000 | 274.5000000000000000 |  548.0000000000000000 | 0005 | 0005 | A0005
+ 324.0000000000000000 | 324.0000000000000000 |  648.0000000000000000 | 0006 | 0006 | A0006
+ 374.0000000000000000 | 375.0000000000000000 |  748.0000000000000000 | 0007 | 0007 | A0007
+ 424.0000000000000000 | 424.5000000000000000 |  848.0000000000000000 | 0008 | 0008 | A0008
+ 474.0000000000000000 | 474.0000000000000000 |  948.0000000000000000 | 0009 | 0009 | A0009
+ 524.0000000000000000 | 525.0000000000000000 | 1048.0000000000000000 | 0010 | 0010 | A0010
+ 574.0000000000000000 | 574.5000000000000000 | 1148.0000000000000000 | 0011 | 0011 | A0011
+(12 rows)
+
+SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM uplt1 t1, uplt2 t2, uplt1_e t3 WHERE t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c;
+         avg          |         avg          |          avg          |  c   |  c   |   c   
+----------------------+----------------------+-----------------------+------+------+-------
+  24.0000000000000000 |  24.0000000000000000 |   48.0000000000000000 | 0000 | 0000 | A0000
+  74.0000000000000000 |  75.0000000000000000 |  148.0000000000000000 | 0001 | 0001 | A0001
+ 124.0000000000000000 | 124.5000000000000000 |  248.0000000000000000 | 0002 | 0002 | A0002
+ 174.0000000000000000 | 174.0000000000000000 |  348.0000000000000000 | 0003 | 0003 | A0003
+ 224.0000000000000000 | 225.0000000000000000 |  448.0000000000000000 | 0004 | 0004 | A0004
+ 274.0000000000000000 | 274.5000000000000000 |  548.0000000000000000 | 0005 | 0005 | A0005
+ 324.0000000000000000 | 324.0000000000000000 |  648.0000000000000000 | 0006 | 0006 | A0006
+ 374.0000000000000000 | 375.0000000000000000 |  748.0000000000000000 | 0007 | 0007 | A0007
+ 424.0000000000000000 | 424.5000000000000000 |  848.0000000000000000 | 0008 | 0008 | A0008
+ 474.0000000000000000 | 474.0000000000000000 |  948.0000000000000000 | 0009 | 0009 | A0009
+ 524.0000000000000000 | 525.0000000000000000 | 1048.0000000000000000 | 0010 | 0010 | A0010
+ 574.0000000000000000 | 574.5000000000000000 | 1148.0000000000000000 | 0011 | 0011 | A0011
+(12 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (plt1 t1 LEFT JOIN plt2 t2 ON t1.a = t2.b AND t1.c = t2.c) LEFT JOIN plt1_e t3 ON (t1.a = t3.a AND ltrim(t3.c, 'A') = t1.c) WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+                                         QUERY PLAN                                         
+--------------------------------------------------------------------------------------------
+ Sort
+   Output: t1.a, t1.c, t2.b, t2.c, ((t3.a + t3.b)), t3.c
+   Sort Key: t1.a, t2.b, ((t3.a + t3.b))
+   ->  Result
+         Output: t1.a, t1.c, t2.b, t2.c, (t3.a + t3.b), t3.c
+         ->  Append
+               ->  Hash Right Join
+                     Output: t1.a, t1.c, t2.b, t2.c, t3.a, t3.b, t3.c
+                     Hash Cond: ((t3.a = t1.a) AND (ltrim(t3.c, 'A'::text) = t1.c))
+                     ->  Seq Scan on public.plt1_e_p1 t3
+                           Output: t3.a, t3.b, t3.c
+                     ->  Hash
+                           Output: t1.a, t1.c, t2.b, t2.c
+                           ->  Hash Right Join
+                                 Output: t1.a, t1.c, t2.b, t2.c
+                                 Hash Cond: ((t2.b = t1.a) AND (t2.c = t1.c))
+                                 ->  Seq Scan on public.plt2_p1 t2
+                                       Output: t2.b, t2.c
+                                 ->  Hash
+                                       Output: t1.a, t1.c
+                                       ->  Seq Scan on public.plt1_p1 t1
+                                             Output: t1.a, t1.c
+                                             Filter: ((t1.a % 25) = 0)
+               ->  Hash Right Join
+                     Output: t1_1.a, t1_1.c, t2_1.b, t2_1.c, t3_1.a, t3_1.b, t3_1.c
+                     Hash Cond: ((t3_1.a = t1_1.a) AND (ltrim(t3_1.c, 'A'::text) = t1_1.c))
+                     ->  Seq Scan on public.plt1_e_p2 t3_1
+                           Output: t3_1.a, t3_1.b, t3_1.c
+                     ->  Hash
+                           Output: t1_1.a, t1_1.c, t2_1.b, t2_1.c
+                           ->  Hash Right Join
+                                 Output: t1_1.a, t1_1.c, t2_1.b, t2_1.c
+                                 Hash Cond: ((t2_1.b = t1_1.a) AND (t2_1.c = t1_1.c))
+                                 ->  Seq Scan on public.plt2_p2 t2_1
+                                       Output: t2_1.b, t2_1.c
+                                 ->  Hash
+                                       Output: t1_1.a, t1_1.c
+                                       ->  Seq Scan on public.plt1_p2 t1_1
+                                             Output: t1_1.a, t1_1.c
+                                             Filter: ((t1_1.a % 25) = 0)
+               ->  Hash Right Join
+                     Output: t1_2.a, t1_2.c, t2_2.b, t2_2.c, t3_2.a, t3_2.b, t3_2.c
+                     Hash Cond: ((t3_2.a = t1_2.a) AND (ltrim(t3_2.c, 'A'::text) = t1_2.c))
+                     ->  Seq Scan on public.plt1_e_p3 t3_2
+                           Output: t3_2.a, t3_2.b, t3_2.c
+                     ->  Hash
+                           Output: t1_2.a, t1_2.c, t2_2.b, t2_2.c
+                           ->  Hash Right Join
+                                 Output: t1_2.a, t1_2.c, t2_2.b, t2_2.c
+                                 Hash Cond: ((t2_2.b = t1_2.a) AND (t2_2.c = t1_2.c))
+                                 ->  Seq Scan on public.plt2_p3 t2_2
+                                       Output: t2_2.b, t2_2.c
+                                 ->  Hash
+                                       Output: t1_2.a, t1_2.c
+                                       ->  Seq Scan on public.plt1_p3 t1_2
+                                             Output: t1_2.a, t1_2.c
+                                             Filter: ((t1_2.a % 25) = 0)
+(57 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (plt1 t1 LEFT JOIN plt2 t2 ON t1.a = t2.b AND t1.c = t2.c) LEFT JOIN plt1_e t3 ON (t1.a = t3.a AND ltrim(t3.c, 'A') = t1.c) WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+  a  |  c   |  b  |  c   | ?column? |   c   
+-----+------+-----+------+----------+-------
+   0 | 0000 |   0 | 0000 |        0 | A0000
+  50 | 0001 |     |      |      100 | A0001
+ 100 | 0002 |     |      |      200 | A0002
+ 150 | 0003 | 150 | 0003 |      300 | A0003
+ 200 | 0004 |     |      |      400 | A0004
+ 250 | 0005 |     |      |      500 | A0005
+ 300 | 0006 | 300 | 0006 |      600 | A0006
+ 350 | 0007 |     |      |      700 | A0007
+ 400 | 0008 |     |      |      800 | A0008
+ 450 | 0009 | 450 | 0009 |      900 | A0009
+ 500 | 0010 |     |      |     1000 | A0010
+ 550 | 0011 |     |      |     1100 | A0011
+(12 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (uplt1 t1 LEFT JOIN uplt2 t2 ON t1.a = t2.b AND t1.c = t2.c) LEFT JOIN uplt1_e t3 ON (t1.a = t3.a AND ltrim(t3.c, 'A') = t1.c) WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+  a  |  c   |  b  |  c   | ?column? |   c   
+-----+------+-----+------+----------+-------
+   0 | 0000 |   0 | 0000 |        0 | A0000
+  50 | 0001 |     |      |      100 | A0001
+ 100 | 0002 |     |      |      200 | A0002
+ 150 | 0003 | 150 | 0003 |      300 | A0003
+ 200 | 0004 |     |      |      400 | A0004
+ 250 | 0005 |     |      |      500 | A0005
+ 300 | 0006 | 300 | 0006 |      600 | A0006
+ 350 | 0007 |     |      |      700 | A0007
+ 400 | 0008 |     |      |      800 | A0008
+ 450 | 0009 | 450 | 0009 |      900 | A0009
+ 500 | 0010 |     |      |     1000 | A0010
+ 550 | 0011 |     |      |     1100 | A0011
+(12 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (plt1 t1 LEFT JOIN plt2 t2 ON t1.a = t2.b AND t1.c = t2.c) LEFT JOIN plt1_e t3 ON (t2.b = t3.a AND t2.c = ltrim(t3.c, 'A')) WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+                                         QUERY PLAN                                         
+--------------------------------------------------------------------------------------------
+ Sort
+   Output: t1.a, t1.c, t2.b, t2.c, ((t3.a + t3.b)), t3.c
+   Sort Key: t1.a, t2.b, ((t3.a + t3.b))
+   ->  Result
+         Output: t1.a, t1.c, t2.b, t2.c, (t3.a + t3.b), t3.c
+         ->  Append
+               ->  Hash Right Join
+                     Output: t1.a, t1.c, t2.b, t2.c, t3.a, t3.b, t3.c
+                     Hash Cond: ((t3.a = t2.b) AND (ltrim(t3.c, 'A'::text) = t2.c))
+                     ->  Seq Scan on public.plt1_e_p1 t3
+                           Output: t3.a, t3.b, t3.c
+                     ->  Hash
+                           Output: t1.a, t1.c, t2.b, t2.c
+                           ->  Hash Right Join
+                                 Output: t1.a, t1.c, t2.b, t2.c
+                                 Hash Cond: ((t2.b = t1.a) AND (t2.c = t1.c))
+                                 ->  Seq Scan on public.plt2_p1 t2
+                                       Output: t2.b, t2.c
+                                 ->  Hash
+                                       Output: t1.a, t1.c
+                                       ->  Seq Scan on public.plt1_p1 t1
+                                             Output: t1.a, t1.c
+                                             Filter: ((t1.a % 25) = 0)
+               ->  Hash Right Join
+                     Output: t1_1.a, t1_1.c, t2_1.b, t2_1.c, t3_1.a, t3_1.b, t3_1.c
+                     Hash Cond: ((t3_1.a = t2_1.b) AND (ltrim(t3_1.c, 'A'::text) = t2_1.c))
+                     ->  Seq Scan on public.plt1_e_p2 t3_1
+                           Output: t3_1.a, t3_1.b, t3_1.c
+                     ->  Hash
+                           Output: t1_1.a, t1_1.c, t2_1.b, t2_1.c
+                           ->  Hash Right Join
+                                 Output: t1_1.a, t1_1.c, t2_1.b, t2_1.c
+                                 Hash Cond: ((t2_1.b = t1_1.a) AND (t2_1.c = t1_1.c))
+                                 ->  Seq Scan on public.plt2_p2 t2_1
+                                       Output: t2_1.b, t2_1.c
+                                 ->  Hash
+                                       Output: t1_1.a, t1_1.c
+                                       ->  Seq Scan on public.plt1_p2 t1_1
+                                             Output: t1_1.a, t1_1.c
+                                             Filter: ((t1_1.a % 25) = 0)
+               ->  Hash Right Join
+                     Output: t1_2.a, t1_2.c, t2_2.b, t2_2.c, t3_2.a, t3_2.b, t3_2.c
+                     Hash Cond: ((t3_2.a = t2_2.b) AND (ltrim(t3_2.c, 'A'::text) = t2_2.c))
+                     ->  Seq Scan on public.plt1_e_p3 t3_2
+                           Output: t3_2.a, t3_2.b, t3_2.c
+                     ->  Hash
+                           Output: t1_2.a, t1_2.c, t2_2.b, t2_2.c
+                           ->  Hash Right Join
+                                 Output: t1_2.a, t1_2.c, t2_2.b, t2_2.c
+                                 Hash Cond: ((t2_2.b = t1_2.a) AND (t2_2.c = t1_2.c))
+                                 ->  Seq Scan on public.plt2_p3 t2_2
+                                       Output: t2_2.b, t2_2.c
+                                 ->  Hash
+                                       Output: t1_2.a, t1_2.c
+                                       ->  Seq Scan on public.plt1_p3 t1_2
+                                             Output: t1_2.a, t1_2.c
+                                             Filter: ((t1_2.a % 25) = 0)
+(57 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (plt1 t1 LEFT JOIN plt2 t2 ON t1.a = t2.b AND t1.c = t2.c) LEFT JOIN plt1_e t3 ON (t2.b = t3.a AND t2.c = ltrim(t3.c, 'A')) WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+  a  |  c   |  b  |  c   | ?column? |   c   
+-----+------+-----+------+----------+-------
+   0 | 0000 |   0 | 0000 |        0 | A0000
+  50 | 0001 |     |      |          | 
+ 100 | 0002 |     |      |          | 
+ 150 | 0003 | 150 | 0003 |      300 | A0003
+ 200 | 0004 |     |      |          | 
+ 250 | 0005 |     |      |          | 
+ 300 | 0006 | 300 | 0006 |      600 | A0006
+ 350 | 0007 |     |      |          | 
+ 400 | 0008 |     |      |          | 
+ 450 | 0009 | 450 | 0009 |      900 | A0009
+ 500 | 0010 |     |      |          | 
+ 550 | 0011 |     |      |          | 
+(12 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (uplt1 t1 LEFT JOIN uplt2 t2 ON t1.a = t2.b AND t1.c = t2.c) LEFT JOIN uplt1_e t3 ON (t2.b = t3.a AND t2.c = ltrim(t3.c, 'A')) WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+  a  |  c   |  b  |  c   | ?column? |   c   
+-----+------+-----+------+----------+-------
+   0 | 0000 |   0 | 0000 |        0 | A0000
+  50 | 0001 |     |      |          | 
+ 100 | 0002 |     |      |          | 
+ 150 | 0003 | 150 | 0003 |      300 | A0003
+ 200 | 0004 |     |      |          | 
+ 250 | 0005 |     |      |          | 
+ 300 | 0006 | 300 | 0006 |      600 | A0006
+ 350 | 0007 |     |      |          | 
+ 400 | 0008 |     |      |          | 
+ 450 | 0009 | 450 | 0009 |      900 | A0009
+ 500 | 0010 |     |      |          | 
+ 550 | 0011 |     |      |          | 
+(12 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (plt1 t1 LEFT JOIN plt2 t2 ON t1.a = t2.b AND t1.c = t2.c) RIGHT JOIN plt1_e t3 ON (t1.a = t3.a AND ltrim(t3.c, 'A') = t1.c) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+                                               QUERY PLAN                                               
+--------------------------------------------------------------------------------------------------------
+ Sort
+   Output: t1.a, t1.c, t2.b, t2.c, ((t3.a + t3.b)), t3.c
+   Sort Key: t1.a, t2.b, ((t3.a + t3.b))
+   ->  Result
+         Output: t1.a, t1.c, t2.b, t2.c, (t3.a + t3.b), t3.c
+         ->  Append
+               ->  Hash Right Join
+                     Output: t3.a, t3.b, t3.c, t1.a, t1.c, t2.b, t2.c
+                     Hash Cond: ((t2.b = t1.a) AND (t2.c = t1.c))
+                     ->  Seq Scan on public.plt2_p1 t2
+                           Output: t2.b, t2.c
+                     ->  Hash
+                           Output: t3.a, t3.b, t3.c, t1.a, t1.c
+                           ->  Hash Right Join
+                                 Output: t3.a, t3.b, t3.c, t1.a, t1.c
+                                 Hash Cond: ((t1.c = ltrim(t3.c, 'A'::text)) AND (t1.a = t3.a))
+                                 ->  Seq Scan on public.plt1_p1 t1
+                                       Output: t1.a, t1.c
+                                 ->  Hash
+                                       Output: t3.a, t3.b, t3.c
+                                       ->  Seq Scan on public.plt1_e_p1 t3
+                                             Output: t3.a, t3.b, t3.c
+                                             Filter: ((t3.a % 25) = 0)
+               ->  Hash Right Join
+                     Output: t3_1.a, t3_1.b, t3_1.c, t1_1.a, t1_1.c, t2_1.b, t2_1.c
+                     Hash Cond: ((t2_1.b = t1_1.a) AND (t2_1.c = t1_1.c))
+                     ->  Seq Scan on public.plt2_p2 t2_1
+                           Output: t2_1.b, t2_1.c
+                     ->  Hash
+                           Output: t3_1.a, t3_1.b, t3_1.c, t1_1.a, t1_1.c
+                           ->  Hash Right Join
+                                 Output: t3_1.a, t3_1.b, t3_1.c, t1_1.a, t1_1.c
+                                 Hash Cond: ((t1_1.c = ltrim(t3_1.c, 'A'::text)) AND (t1_1.a = t3_1.a))
+                                 ->  Seq Scan on public.plt1_p2 t1_1
+                                       Output: t1_1.a, t1_1.c
+                                 ->  Hash
+                                       Output: t3_1.a, t3_1.b, t3_1.c
+                                       ->  Seq Scan on public.plt1_e_p2 t3_1
+                                             Output: t3_1.a, t3_1.b, t3_1.c
+                                             Filter: ((t3_1.a % 25) = 0)
+               ->  Hash Right Join
+                     Output: t3_2.a, t3_2.b, t3_2.c, t1_2.a, t1_2.c, t2_2.b, t2_2.c
+                     Hash Cond: ((t2_2.b = t1_2.a) AND (t2_2.c = t1_2.c))
+                     ->  Seq Scan on public.plt2_p3 t2_2
+                           Output: t2_2.b, t2_2.c
+                     ->  Hash
+                           Output: t3_2.a, t3_2.b, t3_2.c, t1_2.a, t1_2.c
+                           ->  Hash Right Join
+                                 Output: t3_2.a, t3_2.b, t3_2.c, t1_2.a, t1_2.c
+                                 Hash Cond: ((t1_2.c = ltrim(t3_2.c, 'A'::text)) AND (t1_2.a = t3_2.a))
+                                 ->  Seq Scan on public.plt1_p3 t1_2
+                                       Output: t1_2.a, t1_2.c
+                                 ->  Hash
+                                       Output: t3_2.a, t3_2.b, t3_2.c
+                                       ->  Seq Scan on public.plt1_e_p3 t3_2
+                                             Output: t3_2.a, t3_2.b, t3_2.c
+                                             Filter: ((t3_2.a % 25) = 0)
+(57 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (plt1 t1 LEFT JOIN plt2 t2 ON t1.a = t2.b AND t1.c = t2.c) RIGHT JOIN plt1_e t3 ON (t1.a = t3.a AND ltrim(t3.c, 'A') = t1.c) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+  a  |  c   |  b  |  c   | ?column? |   c   
+-----+------+-----+------+----------+-------
+   0 | 0000 |   0 | 0000 |        0 | A0000
+  50 | 0001 |     |      |      100 | A0001
+ 100 | 0002 |     |      |      200 | A0002
+ 150 | 0003 | 150 | 0003 |      300 | A0003
+ 200 | 0004 |     |      |      400 | A0004
+ 250 | 0005 |     |      |      500 | A0005
+ 300 | 0006 | 300 | 0006 |      600 | A0006
+ 350 | 0007 |     |      |      700 | A0007
+ 400 | 0008 |     |      |      800 | A0008
+ 450 | 0009 | 450 | 0009 |      900 | A0009
+ 500 | 0010 |     |      |     1000 | A0010
+ 550 | 0011 |     |      |     1100 | A0011
+(12 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (uplt1 t1 LEFT JOIN uplt2 t2 ON t1.a = t2.b AND t1.c = t2.c) RIGHT JOIN uplt1_e t3 ON (t1.a = t3.a AND ltrim(t3.c, 'A') = t1.c) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+  a  |  c   |  b  |  c   | ?column? |   c   
+-----+------+-----+------+----------+-------
+   0 | 0000 |   0 | 0000 |        0 | A0000
+  50 | 0001 |     |      |      100 | A0001
+ 100 | 0002 |     |      |      200 | A0002
+ 150 | 0003 | 150 | 0003 |      300 | A0003
+ 200 | 0004 |     |      |      400 | A0004
+ 250 | 0005 |     |      |      500 | A0005
+ 300 | 0006 | 300 | 0006 |      600 | A0006
+ 350 | 0007 |     |      |      700 | A0007
+ 400 | 0008 |     |      |      800 | A0008
+ 450 | 0009 | 450 | 0009 |      900 | A0009
+ 500 | 0010 |     |      |     1000 | A0010
+ 550 | 0011 |     |      |     1100 | A0011
+(12 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (plt1 t1 RIGHT JOIN plt2 t2 ON t1.a = t2.b AND t1.c = t2.c) RIGHT JOIN plt1_e t3 ON (t2.b = t3.a AND t2.c = ltrim(t3.c, 'A')) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+                                               QUERY PLAN                                               
+--------------------------------------------------------------------------------------------------------
+ Sort
+   Output: t1.a, t1.c, t2.b, t2.c, ((t3.a + t3.b)), t3.c
+   Sort Key: t1.a, t2.b, ((t3.a + t3.b))
+   ->  Result
+         Output: t1.a, t1.c, t2.b, t2.c, (t3.a + t3.b), t3.c
+         ->  Append
+               ->  Hash Right Join
+                     Output: t3.a, t3.b, t3.c, t2.b, t2.c, t1.a, t1.c
+                     Hash Cond: ((t1.a = t2.b) AND (t1.c = t2.c))
+                     ->  Seq Scan on public.plt1_p1 t1
+                           Output: t1.a, t1.c
+                     ->  Hash
+                           Output: t3.a, t3.b, t3.c, t2.b, t2.c
+                           ->  Hash Right Join
+                                 Output: t3.a, t3.b, t3.c, t2.b, t2.c
+                                 Hash Cond: ((t2.b = t3.a) AND (t2.c = ltrim(t3.c, 'A'::text)))
+                                 ->  Seq Scan on public.plt2_p1 t2
+                                       Output: t2.b, t2.c
+                                 ->  Hash
+                                       Output: t3.a, t3.b, t3.c
+                                       ->  Seq Scan on public.plt1_e_p1 t3
+                                             Output: t3.a, t3.b, t3.c
+                                             Filter: ((t3.a % 25) = 0)
+               ->  Hash Right Join
+                     Output: t3_1.a, t3_1.b, t3_1.c, t2_1.b, t2_1.c, t1_1.a, t1_1.c
+                     Hash Cond: ((t1_1.a = t2_1.b) AND (t1_1.c = t2_1.c))
+                     ->  Seq Scan on public.plt1_p2 t1_1
+                           Output: t1_1.a, t1_1.c
+                     ->  Hash
+                           Output: t3_1.a, t3_1.b, t3_1.c, t2_1.b, t2_1.c
+                           ->  Hash Right Join
+                                 Output: t3_1.a, t3_1.b, t3_1.c, t2_1.b, t2_1.c
+                                 Hash Cond: ((t2_1.b = t3_1.a) AND (t2_1.c = ltrim(t3_1.c, 'A'::text)))
+                                 ->  Seq Scan on public.plt2_p2 t2_1
+                                       Output: t2_1.b, t2_1.c
+                                 ->  Hash
+                                       Output: t3_1.a, t3_1.b, t3_1.c
+                                       ->  Seq Scan on public.plt1_e_p2 t3_1
+                                             Output: t3_1.a, t3_1.b, t3_1.c
+                                             Filter: ((t3_1.a % 25) = 0)
+               ->  Hash Right Join
+                     Output: t3_2.a, t3_2.b, t3_2.c, t2_2.b, t2_2.c, t1_2.a, t1_2.c
+                     Hash Cond: ((t1_2.a = t2_2.b) AND (t1_2.c = t2_2.c))
+                     ->  Seq Scan on public.plt1_p3 t1_2
+                           Output: t1_2.a, t1_2.c
+                     ->  Hash
+                           Output: t3_2.a, t3_2.b, t3_2.c, t2_2.b, t2_2.c
+                           ->  Hash Right Join
+                                 Output: t3_2.a, t3_2.b, t3_2.c, t2_2.b, t2_2.c
+                                 Hash Cond: ((t2_2.b = t3_2.a) AND (t2_2.c = ltrim(t3_2.c, 'A'::text)))
+                                 ->  Seq Scan on public.plt2_p3 t2_2
+                                       Output: t2_2.b, t2_2.c
+                                 ->  Hash
+                                       Output: t3_2.a, t3_2.b, t3_2.c
+                                       ->  Seq Scan on public.plt1_e_p3 t3_2
+                                             Output: t3_2.a, t3_2.b, t3_2.c
+                                             Filter: ((t3_2.a % 25) = 0)
+(57 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (plt1 t1 RIGHT JOIN plt2 t2 ON t1.a = t2.b AND t1.c = t2.c) RIGHT JOIN plt1_e t3 ON (t2.b = t3.a AND t2.c = ltrim(t3.c, 'A')) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+  a  |  c   |  b  |  c   | ?column? |   c   
+-----+------+-----+------+----------+-------
+   0 | 0000 |   0 | 0000 |        0 | A0000
+ 150 | 0003 | 150 | 0003 |      300 | A0003
+ 300 | 0006 | 300 | 0006 |      600 | A0006
+ 450 | 0009 | 450 | 0009 |      900 | A0009
+     |      |     |      |      100 | A0001
+     |      |     |      |      200 | A0002
+     |      |     |      |      400 | A0004
+     |      |     |      |      500 | A0005
+     |      |     |      |      700 | A0007
+     |      |     |      |      800 | A0008
+     |      |     |      |     1000 | A0010
+     |      |     |      |     1100 | A0011
+(12 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (uplt1 t1 RIGHT JOIN uplt2 t2 ON t1.a = t2.b AND t1.c = t2.c) RIGHT JOIN uplt1_e t3 ON (t2.b = t3.a AND t2.c = ltrim(t3.c, 'A')) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+  a  |  c   |  b  |  c   | ?column? |   c   
+-----+------+-----+------+----------+-------
+   0 | 0000 |   0 | 0000 |        0 | A0000
+ 150 | 0003 | 150 | 0003 |      300 | A0003
+ 300 | 0006 | 300 | 0006 |      600 | A0006
+ 450 | 0009 | 450 | 0009 |      900 | A0009
+     |      |     |      |      100 | A0001
+     |      |     |      |      200 | A0002
+     |      |     |      |      400 | A0004
+     |      |     |      |      500 | A0005
+     |      |     |      |      700 | A0007
+     |      |     |      |      800 | A0008
+     |      |     |      |     1000 | A0010
+     |      |     |      |     1100 | A0011
+(12 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM ((SELECT * FROM plt1 WHERE plt1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM plt2 WHERE plt2.b % 25 = 0) t2 ON (t1.a = t2.b AND t1.c = t2.c)) FULL JOIN (SELECT * FROM plt1_e WHERE plt1_e.a % 25 = 0) t3 ON (t1.a = t3.a AND ltrim(t3.c, 'A') = t1.c) ORDER BY t1.a, t2.b, t3.a + t3.b;
+                                                  QUERY PLAN                                                   
+---------------------------------------------------------------------------------------------------------------
+ Sort
+   Output: plt1_p1.a, plt1_p1.c, plt2_p1.b, plt2_p1.c, ((plt1_e_p1.a + plt1_e_p1.b)), plt1_e_p1.c
+   Sort Key: plt1_p1.a, plt2_p1.b, ((plt1_e_p1.a + plt1_e_p1.b))
+   ->  Result
+         Output: plt1_p1.a, plt1_p1.c, plt2_p1.b, plt2_p1.c, (plt1_e_p1.a + plt1_e_p1.b), plt1_e_p1.c
+         ->  Append
+               ->  Hash Full Join
+                     Output: plt1_p1.a, plt1_p1.c, plt2_p1.b, plt2_p1.c, plt1_e_p1.a, plt1_e_p1.b, plt1_e_p1.c
+                     Hash Cond: ((plt1_p1.a = plt1_e_p1.a) AND (plt1_p1.c = ltrim(plt1_e_p1.c, 'A'::text)))
+                     ->  Hash Full Join
+                           Output: plt1_p1.a, plt1_p1.c, plt2_p1.b, plt2_p1.c
+                           Hash Cond: ((plt1_p1.a = plt2_p1.b) AND (plt1_p1.c = plt2_p1.c))
+                           ->  Seq Scan on public.plt1_p1
+                                 Output: plt1_p1.a, plt1_p1.c
+                                 Filter: ((plt1_p1.a % 25) = 0)
+                           ->  Hash
+                                 Output: plt2_p1.b, plt2_p1.c
+                                 ->  Seq Scan on public.plt2_p1
+                                       Output: plt2_p1.b, plt2_p1.c
+                                       Filter: ((plt2_p1.b % 25) = 0)
+                     ->  Hash
+                           Output: plt1_e_p1.a, plt1_e_p1.b, plt1_e_p1.c
+                           ->  Seq Scan on public.plt1_e_p1
+                                 Output: plt1_e_p1.a, plt1_e_p1.b, plt1_e_p1.c
+                                 Filter: ((plt1_e_p1.a % 25) = 0)
+               ->  Hash Full Join
+                     Output: plt1_p2.a, plt1_p2.c, plt2_p2.b, plt2_p2.c, plt1_e_p2.a, plt1_e_p2.b, plt1_e_p2.c
+                     Hash Cond: ((plt1_p2.a = plt1_e_p2.a) AND (plt1_p2.c = ltrim(plt1_e_p2.c, 'A'::text)))
+                     ->  Hash Full Join
+                           Output: plt1_p2.a, plt1_p2.c, plt2_p2.b, plt2_p2.c
+                           Hash Cond: ((plt1_p2.a = plt2_p2.b) AND (plt1_p2.c = plt2_p2.c))
+                           ->  Seq Scan on public.plt1_p2
+                                 Output: plt1_p2.a, plt1_p2.c
+                                 Filter: ((plt1_p2.a % 25) = 0)
+                           ->  Hash
+                                 Output: plt2_p2.b, plt2_p2.c
+                                 ->  Seq Scan on public.plt2_p2
+                                       Output: plt2_p2.b, plt2_p2.c
+                                       Filter: ((plt2_p2.b % 25) = 0)
+                     ->  Hash
+                           Output: plt1_e_p2.a, plt1_e_p2.b, plt1_e_p2.c
+                           ->  Seq Scan on public.plt1_e_p2
+                                 Output: plt1_e_p2.a, plt1_e_p2.b, plt1_e_p2.c
+                                 Filter: ((plt1_e_p2.a % 25) = 0)
+               ->  Hash Full Join
+                     Output: plt1_p3.a, plt1_p3.c, plt2_p3.b, plt2_p3.c, plt1_e_p3.a, plt1_e_p3.b, plt1_e_p3.c
+                     Hash Cond: ((plt1_p3.a = plt1_e_p3.a) AND (plt1_p3.c = ltrim(plt1_e_p3.c, 'A'::text)))
+                     ->  Hash Full Join
+                           Output: plt1_p3.a, plt1_p3.c, plt2_p3.b, plt2_p3.c
+                           Hash Cond: ((plt1_p3.a = plt2_p3.b) AND (plt1_p3.c = plt2_p3.c))
+                           ->  Seq Scan on public.plt1_p3
+                                 Output: plt1_p3.a, plt1_p3.c
+                                 Filter: ((plt1_p3.a % 25) = 0)
+                           ->  Hash
+                                 Output: plt2_p3.b, plt2_p3.c
+                                 ->  Seq Scan on public.plt2_p3
+                                       Output: plt2_p3.b, plt2_p3.c
+                                       Filter: ((plt2_p3.b % 25) = 0)
+                     ->  Hash
+                           Output: plt1_e_p3.a, plt1_e_p3.b, plt1_e_p3.c
+                           ->  Seq Scan on public.plt1_e_p3
+                                 Output: plt1_e_p3.a, plt1_e_p3.b, plt1_e_p3.c
+                                 Filter: ((plt1_e_p3.a % 25) = 0)
+(63 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM ((SELECT * FROM plt1 WHERE plt1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM plt2 WHERE plt2.b % 25 = 0) t2 ON (t1.a = t2.b AND t1.c = t2.c)) FULL JOIN (SELECT * FROM plt1_e WHERE plt1_e.a % 25 = 0) t3 ON (t1.a = t3.a AND ltrim(t3.c, 'A') = t1.c) ORDER BY t1.a, t2.b, t3.a + t3.b;
+  a  |  c   |  b  |  c   | ?column? |   c   
+-----+------+-----+------+----------+-------
+   0 | 0000 |   0 | 0000 |        0 | A0000
+  50 | 0001 |     |      |      100 | A0001
+ 100 | 0002 |     |      |      200 | A0002
+ 150 | 0003 | 150 | 0003 |      300 | A0003
+ 200 | 0004 |     |      |      400 | A0004
+ 250 | 0005 |     |      |      500 | A0005
+ 300 | 0006 | 300 | 0006 |      600 | A0006
+ 350 | 0007 |     |      |      700 | A0007
+ 400 | 0008 |     |      |      800 | A0008
+ 450 | 0009 | 450 | 0009 |      900 | A0009
+ 500 | 0010 |     |      |     1000 | A0010
+ 550 | 0011 |     |      |     1100 | A0011
+     |      |  75 | 0001 |          | 
+     |      | 225 | 0004 |          | 
+     |      | 375 | 0007 |          | 
+     |      | 525 | 0010 |          | 
+(16 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM ((SELECT * FROM uplt1 WHERE uplt1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM uplt2 WHERE uplt2.b % 25 = 0) t2 ON (t1.a = t2.b AND t1.c = t2.c)) FULL JOIN (SELECT * FROM uplt1_e WHERE uplt1_e.a % 25 = 0) t3 ON (t1.a = t3.a AND ltrim(t3.c, 'A') = t1.c) ORDER BY t1.a, t2.b, t3.a + t3.b;
+  a  |  c   |  b  |  c   | ?column? |   c   
+-----+------+-----+------+----------+-------
+   0 | 0000 |   0 | 0000 |        0 | A0000
+  50 | 0001 |     |      |      100 | A0001
+ 100 | 0002 |     |      |      200 | A0002
+ 150 | 0003 | 150 | 0003 |      300 | A0003
+ 200 | 0004 |     |      |      400 | A0004
+ 250 | 0005 |     |      |      500 | A0005
+ 300 | 0006 | 300 | 0006 |      600 | A0006
+ 350 | 0007 |     |      |      700 | A0007
+ 400 | 0008 |     |      |      800 | A0008
+ 450 | 0009 | 450 | 0009 |      900 | A0009
+ 500 | 0010 |     |      |     1000 | A0010
+ 550 | 0011 |     |      |     1100 | A0011
+     |      |  75 | 0001 |          | 
+     |      | 225 | 0004 |          | 
+     |      | 375 | 0007 |          | 
+     |      | 525 | 0010 |          | 
+(16 rows)
+
+-- Semi-join
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.* FROM plt1 t1 WHERE t1.c IN (SELECT t1.c FROM plt2 t1, plt1_e t2 WHERE t1.c = ltrim(t2.c, 'A')) AND t1.a % 25 = 0 ORDER BY t1.a;
+                             QUERY PLAN                             
+--------------------------------------------------------------------
+ Sort
+   Output: t1.a, t1.b, t1.c
+   Sort Key: t1.a
+   ->  Append
+         ->  Nested Loop Semi Join
+               Output: t1.a, t1.b, t1.c
+               Join Filter: (t1.c = t1_3.c)
+               ->  Seq Scan on public.plt1_p1 t1
+                     Output: t1.a, t1.b, t1.c
+                     Filter: ((t1.a % 25) = 0)
+               ->  Hash Join
+                     Output: t1_3.c, t2.c
+                     Hash Cond: (t1_3.c = ltrim(t2.c, 'A'::text))
+                     ->  Seq Scan on public.plt2_p1 t1_3
+                           Output: t1_3.c
+                     ->  Hash
+                           Output: t2.c
+                           ->  Seq Scan on public.plt1_e_p1 t2
+                                 Output: t2.c
+         ->  Nested Loop Semi Join
+               Output: t1_1.a, t1_1.b, t1_1.c
+               Join Filter: (t1_1.c = t1_4.c)
+               ->  Seq Scan on public.plt1_p2 t1_1
+                     Output: t1_1.a, t1_1.b, t1_1.c
+                     Filter: ((t1_1.a % 25) = 0)
+               ->  Hash Join
+                     Output: t1_4.c, t2_1.c
+                     Hash Cond: (t1_4.c = ltrim(t2_1.c, 'A'::text))
+                     ->  Seq Scan on public.plt2_p2 t1_4
+                           Output: t1_4.c
+                     ->  Hash
+                           Output: t2_1.c
+                           ->  Seq Scan on public.plt1_e_p2 t2_1
+                                 Output: t2_1.c
+         ->  Nested Loop Semi Join
+               Output: t1_2.a, t1_2.b, t1_2.c
+               Join Filter: (t1_2.c = t1_5.c)
+               ->  Seq Scan on public.plt1_p3 t1_2
+                     Output: t1_2.a, t1_2.b, t1_2.c
+                     Filter: ((t1_2.a % 25) = 0)
+               ->  Hash Join
+                     Output: t1_5.c, t2_2.c
+                     Hash Cond: (t1_5.c = ltrim(t2_2.c, 'A'::text))
+                     ->  Seq Scan on public.plt2_p3 t1_5
+                           Output: t1_5.c
+                     ->  Hash
+                           Output: t2_2.c
+                           ->  Seq Scan on public.plt1_e_p3 t2_2
+                                 Output: t2_2.c
+(49 rows)
+
+SELECT t1.* FROM plt1 t1 WHERE t1.c IN (SELECT t1.c FROM plt2 t1, plt1_e t2 WHERE t1.c = ltrim(t2.c, 'A')) AND t1.a % 25 = 0 ORDER BY t1.a;
+  a  |  b  |  c   
+-----+-----+------
+   0 |   0 | 0000
+  50 |  50 | 0001
+ 100 | 100 | 0002
+ 150 | 150 | 0003
+ 200 | 200 | 0004
+ 250 | 250 | 0005
+ 300 | 300 | 0006
+ 350 | 350 | 0007
+ 400 | 400 | 0008
+ 450 | 450 | 0009
+ 500 | 500 | 0010
+ 550 | 550 | 0011
+(12 rows)
+
+SELECT t1.* FROM uplt1 t1 WHERE t1.c IN (SELECT t1.c FROM uplt2 t1, uplt1_e t2 WHERE t1.c = ltrim(t2.c, 'A')) AND t1.a % 25 = 0 ORDER BY t1.a;
+  a  |  b  |  c   
+-----+-----+------
+   0 |   0 | 0000
+  50 |  50 | 0001
+ 100 | 100 | 0002
+ 150 | 150 | 0003
+ 200 | 200 | 0004
+ 250 | 250 | 0005
+ 300 | 300 | 0006
+ 350 | 350 | 0007
+ 400 | 400 | 0008
+ 450 | 450 | 0009
+ 500 | 500 | 0010
+ 550 | 550 | 0011
+(12 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.* FROM plt1 t1 WHERE t1.c IN (SELECT t1.c FROM plt2 t1 WHERE t1.c IN (SELECT ltrim(t1.c, 'A') FROM plt1_e t1 WHERE t1.a % 25 = 0)) AND t1.a % 25 = 0 ORDER BY t1.a;
+                                   QUERY PLAN                                   
+--------------------------------------------------------------------------------
+ Sort
+   Output: t1.a, t1.b, t1.c
+   Sort Key: t1.a
+   ->  Append
+         ->  Nested Loop Semi Join
+               Output: t1.a, t1.b, t1.c
+               Join Filter: (t1.c = t1_3.c)
+               ->  Seq Scan on public.plt1_p1 t1
+                     Output: t1.a, t1.b, t1.c
+                     Filter: ((t1.a % 25) = 0)
+               ->  Hash Join
+                     Output: t1_3.c, t1_6.c
+                     Hash Cond: (t1_3.c = ltrim(t1_6.c, 'A'::text))
+                     ->  Seq Scan on public.plt2_p1 t1_3
+                           Output: t1_3.c
+                     ->  Hash
+                           Output: t1_6.c
+                           ->  HashAggregate
+                                 Output: t1_6.c
+                                 Group Key: ltrim(t1_6.c, 'A'::text)
+                                 ->  Seq Scan on public.plt1_e_p1 t1_6
+                                       Output: t1_6.c, ltrim(t1_6.c, 'A'::text)
+                                       Filter: ((t1_6.a % 25) = 0)
+         ->  Nested Loop Semi Join
+               Output: t1_1.a, t1_1.b, t1_1.c
+               Join Filter: (t1_1.c = t1_4.c)
+               ->  Seq Scan on public.plt1_p2 t1_1
+                     Output: t1_1.a, t1_1.b, t1_1.c
+                     Filter: ((t1_1.a % 25) = 0)
+               ->  Hash Join
+                     Output: t1_4.c, t1_7.c
+                     Hash Cond: (t1_4.c = ltrim(t1_7.c, 'A'::text))
+                     ->  Seq Scan on public.plt2_p2 t1_4
+                           Output: t1_4.c
+                     ->  Hash
+                           Output: t1_7.c
+                           ->  HashAggregate
+                                 Output: t1_7.c
+                                 Group Key: ltrim(t1_7.c, 'A'::text)
+                                 ->  Seq Scan on public.plt1_e_p2 t1_7
+                                       Output: t1_7.c, ltrim(t1_7.c, 'A'::text)
+                                       Filter: ((t1_7.a % 25) = 0)
+         ->  Nested Loop Semi Join
+               Output: t1_2.a, t1_2.b, t1_2.c
+               Join Filter: (t1_2.c = t1_5.c)
+               ->  Seq Scan on public.plt1_p3 t1_2
+                     Output: t1_2.a, t1_2.b, t1_2.c
+                     Filter: ((t1_2.a % 25) = 0)
+               ->  Hash Join
+                     Output: t1_5.c, t1_8.c
+                     Hash Cond: (t1_5.c = ltrim(t1_8.c, 'A'::text))
+                     ->  Seq Scan on public.plt2_p3 t1_5
+                           Output: t1_5.c
+                     ->  Hash
+                           Output: t1_8.c
+                           ->  HashAggregate
+                                 Output: t1_8.c
+                                 Group Key: ltrim(t1_8.c, 'A'::text)
+                                 ->  Seq Scan on public.plt1_e_p3 t1_8
+                                       Output: t1_8.c, ltrim(t1_8.c, 'A'::text)
+                                       Filter: ((t1_8.a % 25) = 0)
+(61 rows)
+
+SELECT t1.* FROM plt1 t1 WHERE t1.c IN (SELECT t1.c FROM plt2 t1 WHERE t1.c IN (SELECT ltrim(t1.c, 'A') FROM plt1_e t1 WHERE t1.a % 25 = 0)) AND t1.a % 25 = 0 ORDER BY t1.a;
+  a  |  b  |  c   
+-----+-----+------
+   0 |   0 | 0000
+  50 |  50 | 0001
+ 100 | 100 | 0002
+ 150 | 150 | 0003
+ 200 | 200 | 0004
+ 250 | 250 | 0005
+ 300 | 300 | 0006
+ 350 | 350 | 0007
+ 400 | 400 | 0008
+ 450 | 450 | 0009
+ 500 | 500 | 0010
+ 550 | 550 | 0011
+(12 rows)
+
+SELECT t1.* FROM uplt1 t1 WHERE t1.c IN (SELECT t1.c FROM uplt2 t1 WHERE t1.c IN (SELECT ltrim(t1.c, 'A') FROM uplt1_e t1 WHERE t1.a % 25 = 0)) AND t1.a % 25 = 0 ORDER BY t1.a;
+  a  |  b  |  c   
+-----+-----+------
+   0 |   0 | 0000
+  50 |  50 | 0001
+ 100 | 100 | 0002
+ 150 | 150 | 0003
+ 200 | 200 | 0004
+ 250 | 250 | 0005
+ 300 | 300 | 0006
+ 350 | 350 | 0007
+ 400 | 400 | 0008
+ 450 | 450 | 0009
+ 500 | 500 | 0010
+ 550 | 550 | 0011
+(12 rows)
+
+--
+-- negative testcases
+--
+-- joins where one of the relations is proven empty
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a = 1 AND t1.a = 2;
+            QUERY PLAN            
+----------------------------------
+ Result
+   Output: t1.a, t1.c, t2.b, t2.c
+   One-Time Filter: false
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 LEFT JOIN prt2 t2 ON t1.a = t2.b;
+              QUERY PLAN              
+--------------------------------------
+ Result
+   Output: prt1.a, prt1.c, t2.b, t2.c
+   One-Time Filter: false
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a % 25 = 0 ORDER BY t1.a, t2.b;
+                    QUERY PLAN                     
+---------------------------------------------------
+ Sort
+   Output: a, c, t2.b, t2.c
+   Sort Key: a, t2.b
+   ->  Hash Left Join
+         Output: a, c, t2.b, t2.c
+         Hash Cond: (t2.b = a)
+         ->  Append
+               ->  Seq Scan on public.prt2 t2
+                     Output: t2.b, t2.c
+                     Filter: ((t2.a % 25) = 0)
+               ->  Seq Scan on public.prt2_p1 t2_1
+                     Output: t2_1.b, t2_1.c
+                     Filter: ((t2_1.a % 25) = 0)
+               ->  Seq Scan on public.prt2_p2 t2_2
+                     Output: t2_2.b, t2_2.c
+                     Filter: ((t2_2.a % 25) = 0)
+               ->  Seq Scan on public.prt2_p3 t2_3
+                     Output: t2_3.b, t2_3.c
+                     Filter: ((t2_3.a % 25) = 0)
+         ->  Hash
+               Output: a, c
+               ->  Result
+                     Output: a, c
+                     One-Time Filter: false
+(24 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 FULL JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a % 25 = 0 ORDER BY t1.a, t2.b;
+                    QUERY PLAN                     
+---------------------------------------------------
+ Sort
+   Output: a, c, t2.b, t2.c
+   Sort Key: a, t2.b
+   ->  Hash Left Join
+         Output: a, c, t2.b, t2.c
+         Hash Cond: (t2.b = a)
+         ->  Append
+               ->  Seq Scan on public.prt2 t2
+                     Output: t2.b, t2.c
+                     Filter: ((t2.a % 25) = 0)
+               ->  Seq Scan on public.prt2_p1 t2_1
+                     Output: t2_1.b, t2_1.c
+                     Filter: ((t2_1.a % 25) = 0)
+               ->  Seq Scan on public.prt2_p2 t2_2
+                     Output: t2_2.b, t2_2.c
+                     Filter: ((t2_2.a % 25) = 0)
+               ->  Seq Scan on public.prt2_p3 t2_3
+                     Output: t2_3.b, t2_3.c
+                     Filter: ((t2_3.a % 25) = 0)
+         ->  Hash
+               Output: a, c
+               ->  Result
+                     Output: a, c
+                     One-Time Filter: false
+(24 rows)
+
+CREATE TABLE prt1_n (a int, b int, c varchar) PARTITION BY RANGE(c);
+CREATE TABLE prt1_n_p1 PARTITION OF prt1_n FOR VALUES FROM ('0000') TO ('0250');
+CREATE TABLE prt1_n_p2 PARTITION OF prt1_n FOR VALUES FROM ('0250') TO ('0500');
+INSERT INTO prt1_n SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 499, 2) i;
+ANALYZE prt1_n;
+ANALYZE prt1_n_p1;
+ANALYZE prt1_n_p2;
+CREATE TABLE prt2_n (a int, b int, c text) PARTITION BY LIST(c);
+CREATE TABLE prt2_n_p1 PARTITION OF prt2_n FOR VALUES IN ('0000', '0003', '0004', '0010', '0006', '0007');
+CREATE TABLE prt2_n_p2 PARTITION OF prt2_n FOR VALUES IN ('0001', '0005', '0002', '0009', '0008', '0011');
+INSERT INTO prt2_n SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i;
+ANALYZE prt2_n;
+ANALYZE prt2_n_p1;
+ANALYZE prt2_n_p2;
+CREATE TABLE prt3_n (a int, b int, c text) PARTITION BY LIST(c);
+CREATE TABLE prt3_n_p1 PARTITION OF prt3_n FOR VALUES IN ('0000', '0004', '0006', '0007');
+CREATE TABLE prt3_n_p2 PARTITION OF prt3_n FOR VALUES IN ('0001', '0002', '0008', '0010');
+CREATE TABLE prt3_n_p3 PARTITION OF prt3_n FOR VALUES IN ('0003', '0005', '0009', '0011');
+INSERT INTO prt2_n SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i;
+ANALYZE prt3_n;
+ANALYZE prt3_n_p1;
+ANALYZE prt3_n_p2;
+ANALYZE prt3_n_p3;
+CREATE TABLE prt4_n (a int, b int, c text) PARTITION BY RANGE(a);
+CREATE TABLE prt4_n_p1 PARTITION OF prt4_n FOR VALUES FROM (0) TO (300);
+CREATE TABLE prt4_n_p2 PARTITION OF prt4_n FOR VALUES FROM (300) TO (500);
+CREATE TABLE prt4_n_p3 PARTITION OF prt4_n FOR VALUES FROM (500) TO (600);
+INSERT INTO prt4_n SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 2) i;
+ANALYZE prt4_n;
+ANALYZE prt4_n_p1;
+ANALYZE prt4_n_p2;
+ANALYZE prt4_n_p3;
+-- partition-wise join can not be applied if the partition ranges differ
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt4_n t2 WHERE t1.a = t2.a;
+                  QUERY PLAN                  
+----------------------------------------------
+ Hash Join
+   Hash Cond: (t1.a = t2.a)
+   ->  Append
+         ->  Seq Scan on prt1 t1
+         ->  Seq Scan on prt1_p1 t1_1
+         ->  Seq Scan on prt1_p3 t1_2
+         ->  Seq Scan on prt1_p2 t1_3
+   ->  Hash
+         ->  Append
+               ->  Seq Scan on prt4_n t2
+               ->  Seq Scan on prt4_n_p1 t2_1
+               ->  Seq Scan on prt4_n_p2 t2_2
+               ->  Seq Scan on prt4_n_p3 t2_3
+(13 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 FULL JOIN prt4_n t2 ON t1.a = t2.a;
+                  QUERY PLAN                  
+----------------------------------------------
+ Hash Full Join
+   Hash Cond: (t1.a = t2.a)
+   ->  Append
+         ->  Seq Scan on prt1 t1
+         ->  Seq Scan on prt1_p1 t1_1
+         ->  Seq Scan on prt1_p3 t1_2
+         ->  Seq Scan on prt1_p2 t1_3
+   ->  Hash
+         ->  Append
+               ->  Seq Scan on prt4_n t2
+               ->  Seq Scan on prt4_n_p1 t2_1
+               ->  Seq Scan on prt4_n_p2 t2_2
+               ->  Seq Scan on prt4_n_p3 t2_3
+(13 rows)
+
+-- partition-wise join can not be applied if there are no equi-join conditions
+-- between partition keys
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 LEFT JOIN prt2 t2 ON (t1.a < t2.b);
+                       QUERY PLAN                        
+---------------------------------------------------------
+ Nested Loop Left Join
+   ->  Append
+         ->  Seq Scan on prt1 t1
+         ->  Seq Scan on prt1_p1 t1_1
+         ->  Seq Scan on prt1_p3 t1_2
+         ->  Seq Scan on prt1_p2 t1_3
+   ->  Append
+         ->  Seq Scan on prt2 t2
+               Filter: (t1.a < b)
+         ->  Index Scan using iprt2_p1_b on prt2_p1 t2_1
+               Index Cond: (t1.a < b)
+         ->  Index Scan using iprt2_p2_b on prt2_p2 t2_2
+               Index Cond: (t1.a < b)
+         ->  Index Scan using iprt2_p3_b on prt2_p3 t2_3
+               Index Cond: (t1.a < b)
+(15 rows)
+
+-- equi-join with join condition on partial keys does not qualify for
+-- partition-wise join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_m t1, prt2_m t2 WHERE t1.a = (t2.b + t2.a)/2 AND t1.a % 25 = 0;
+                  QUERY PLAN                  
+----------------------------------------------
+ Hash Join
+   Hash Cond: (((t2.b + t2.a) / 2) = t1.a)
+   ->  Append
+         ->  Seq Scan on prt2_m t2
+         ->  Seq Scan on prt2_m_p1 t2_1
+         ->  Seq Scan on prt2_m_p2 t2_2
+         ->  Seq Scan on prt2_m_p3 t2_3
+   ->  Hash
+         ->  Append
+               ->  Seq Scan on prt1_m t1
+                     Filter: ((a % 25) = 0)
+               ->  Seq Scan on prt1_m_p1 t1_1
+                     Filter: ((a % 25) = 0)
+               ->  Seq Scan on prt1_m_p2 t1_2
+                     Filter: ((a % 25) = 0)
+               ->  Seq Scan on prt1_m_p3 t1_3
+                     Filter: ((a % 25) = 0)
+(17 rows)
+
+-- equi-join between out-of-order partition key columns does not qualify for
+-- partition-wise join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_m t1 LEFT JOIN prt2_m t2 ON t1.a = t2.b WHERE t1.a % 25 = 0;
+                  QUERY PLAN                  
+----------------------------------------------
+ Hash Right Join
+   Hash Cond: (t2.b = t1.a)
+   ->  Append
+         ->  Seq Scan on prt2_m t2
+         ->  Seq Scan on prt2_m_p1 t2_1
+         ->  Seq Scan on prt2_m_p2 t2_2
+         ->  Seq Scan on prt2_m_p3 t2_3
+   ->  Hash
+         ->  Append
+               ->  Seq Scan on prt1_m t1
+                     Filter: ((a % 25) = 0)
+               ->  Seq Scan on prt1_m_p1 t1_1
+                     Filter: ((a % 25) = 0)
+               ->  Seq Scan on prt1_m_p2 t1_2
+                     Filter: ((a % 25) = 0)
+               ->  Seq Scan on prt1_m_p3 t1_3
+                     Filter: ((a % 25) = 0)
+(17 rows)
+
+-- equi-join between non-key columns does not qualify for partition-wise join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_m t1 LEFT JOIN prt2_m t2 ON t1.c = t2.c WHERE t1.a % 25 = 0;
+                  QUERY PLAN                  
+----------------------------------------------
+ Hash Right Join
+   Hash Cond: ((t2.c)::text = (t1.c)::text)
+   ->  Append
+         ->  Seq Scan on prt2_m t2
+         ->  Seq Scan on prt2_m_p1 t2_1
+         ->  Seq Scan on prt2_m_p2 t2_2
+         ->  Seq Scan on prt2_m_p3 t2_3
+   ->  Hash
+         ->  Append
+               ->  Seq Scan on prt1_m t1
+                     Filter: ((a % 25) = 0)
+               ->  Seq Scan on prt1_m_p1 t1_1
+                     Filter: ((a % 25) = 0)
+               ->  Seq Scan on prt1_m_p2 t1_2
+                     Filter: ((a % 25) = 0)
+               ->  Seq Scan on prt1_m_p3 t1_3
+                     Filter: ((a % 25) = 0)
+(17 rows)
+
+-- partition-wise join can not be applied for a join between list and range
+-- partitioned table
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_n t1, prt2_n t2 WHERE t1.c = t2.c;
+                  QUERY PLAN                  
+----------------------------------------------
+ Hash Join
+   Hash Cond: (t2.c = (t1.c)::text)
+   ->  Append
+         ->  Seq Scan on prt2_n t2
+         ->  Seq Scan on prt2_n_p1 t2_1
+         ->  Seq Scan on prt2_n_p2 t2_2
+   ->  Hash
+         ->  Append
+               ->  Seq Scan on prt1_n t1
+               ->  Seq Scan on prt1_n_p1 t1_1
+               ->  Seq Scan on prt1_n_p2 t1_2
+(11 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_n t1 LEFT JOIN prt2_n t2 ON (t1.c = t2.c);
+                  QUERY PLAN                  
+----------------------------------------------
+ Hash Right Join
+   Hash Cond: (t2.c = (t1.c)::text)
+   ->  Append
+         ->  Seq Scan on prt2_n t2
+         ->  Seq Scan on prt2_n_p1 t2_1
+         ->  Seq Scan on prt2_n_p2 t2_2
+   ->  Hash
+         ->  Append
+               ->  Seq Scan on prt1_n t1
+               ->  Seq Scan on prt1_n_p1 t1_1
+               ->  Seq Scan on prt1_n_p2 t1_2
+(11 rows)
+
+-- partition-wise join can not be applied between tables with different
+-- partition lists
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_n t1 RIGHT JOIN prt1 t2 ON (t1.c = t2.c);
+                  QUERY PLAN                  
+----------------------------------------------
+ Hash Left Join
+   Hash Cond: ((t2.c)::text = (t1.c)::text)
+   ->  Append
+         ->  Seq Scan on prt1 t2
+         ->  Seq Scan on prt1_p1 t2_1
+         ->  Seq Scan on prt1_p3 t2_2
+         ->  Seq Scan on prt1_p2 t2_3
+   ->  Hash
+         ->  Append
+               ->  Seq Scan on prt1_n t1
+               ->  Seq Scan on prt1_n_p1 t1_1
+               ->  Seq Scan on prt1_n_p2 t1_2
+(12 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_n t1 FULL JOIN prt1 t2 ON (t1.c = t2.c);
+                  QUERY PLAN                  
+----------------------------------------------
+ Hash Full Join
+   Hash Cond: ((t2.c)::text = (t1.c)::text)
+   ->  Append
+         ->  Seq Scan on prt1 t2
+         ->  Seq Scan on prt1_p1 t2_1
+         ->  Seq Scan on prt1_p3 t2_2
+         ->  Seq Scan on prt1_p2 t2_3
+   ->  Hash
+         ->  Append
+               ->  Seq Scan on prt1_n t1
+               ->  Seq Scan on prt1_n_p1 t1_1
+               ->  Seq Scan on prt1_n_p2 t1_2
+(12 rows)
+
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index f06cfa4..16e7f56 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -1,18 +1,19 @@
 SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%';
-         name         | setting 
-----------------------+---------
- enable_bitmapscan    | on
- enable_hashagg       | on
- enable_hashjoin      | on
- enable_indexonlyscan | on
- enable_indexscan     | on
- enable_material      | on
- enable_mergejoin     | on
- enable_nestloop      | on
- enable_seqscan       | on
- enable_sort          | on
- enable_tidscan       | on
-(11 rows)
+            name            | setting 
+----------------------------+---------
+ enable_bitmapscan          | on
+ enable_hashagg             | on
+ enable_hashjoin            | on
+ enable_indexonlyscan       | on
+ enable_indexscan           | on
+ enable_material            | on
+ enable_mergejoin           | on
+ enable_nestloop            | on
+ enable_partition_wise_join | on
+ enable_seqscan             | on
+ enable_sort                | on
+ enable_tidscan             | on
+(12 rows)
 
 CREATE TABLE foo2(fooid int, f2 int);
 INSERT INTO foo2 VALUES(1, 11);
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 8641769..b61ca3b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -99,8 +99,9 @@ test: select_parallel
 
 # ----------
 # Another group of parallel tests
+# TODO: merge partition_join and multi_level_partition_join
 # ----------
-test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb json_encoding indirect_toast equivclass
+test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb json_encoding indirect_toast equivclass partition_join multi_level_partition_join
 # ----------
 # Another group of parallel tests
 # NB: temp.sql does a reconnect which transiently uses 2 connections,
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 835cf35..5b167b6 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -169,3 +169,5 @@ test: with
 test: xml
 test: event_trigger
 test: stats
+test: partition_join
+test: multi_level_partition_join
diff --git a/src/test/regress/sql/multi_level_partition_join.sql b/src/test/regress/sql/multi_level_partition_join.sql
new file mode 100644
index 0000000..31f0281
--- /dev/null
+++ b/src/test/regress/sql/multi_level_partition_join.sql
@@ -0,0 +1,95 @@
+--
+-- multi-leveled partitions
+--
+CREATE TABLE prt1_l (a int, b int, c varchar) PARTITION BY RANGE(a);
+CREATE TABLE prt1_l_p1 PARTITION OF prt1_l FOR VALUES FROM (0) TO (250) PARTITION BY RANGE (b);
+CREATE TABLE prt1_l_p1_p1 PARTITION OF prt1_l_p1 FOR VALUES FROM (0) TO (100);
+CREATE TABLE prt1_l_p1_p2 PARTITION OF prt1_l_p1 FOR VALUES FROM (100) TO (250);
+CREATE TABLE prt1_l_p2 PARTITION OF prt1_l FOR VALUES FROM (250) TO (500) PARTITION BY RANGE (c);
+CREATE TABLE prt1_l_p2_p1 PARTITION OF prt1_l_p2 FOR VALUES FROM ('0250') TO ('0400');
+CREATE TABLE prt1_l_p2_p2 PARTITION OF prt1_l_p2 FOR VALUES FROM ('0400') TO ('0500');
+CREATE TABLE prt1_l_p3 PARTITION OF prt1_l FOR VALUES FROM (500) TO (600) PARTITION BY RANGE ((b + a));
+CREATE TABLE prt1_l_p3_p1 PARTITION OF prt1_l_p3 FOR VALUES FROM (1000) TO (1100);
+CREATE TABLE prt1_l_p3_p2 PARTITION OF prt1_l_p3 FOR VALUES FROM (1100) TO (1200);
+INSERT INTO prt1_l SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 2) i;
+ANALYZE prt1_l;
+ANALYZE prt1_l_p1;
+ANALYZE prt1_l_p1_p1;
+ANALYZE prt1_l_p1_p2;
+ANALYZE prt1_l_p2;
+ANALYZE prt1_l_p2_p1;
+ANALYZE prt1_l_p2_p2;
+ANALYZE prt1_l_p3;
+ANALYZE prt1_l_p3_p1;
+ANALYZE prt1_l_p3_p2;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE uprt1_l AS SELECT * FROM prt1_l;
+
+CREATE TABLE prt2_l (a int, b int, c varchar) PARTITION BY RANGE(b);
+CREATE TABLE prt2_l_p1 PARTITION OF prt2_l FOR VALUES FROM (0) TO (250) PARTITION BY RANGE (a);
+CREATE TABLE prt2_l_p1_p1 PARTITION OF prt2_l_p1 FOR VALUES FROM (0) TO (100);
+CREATE TABLE prt2_l_p1_p2 PARTITION OF prt2_l_p1 FOR VALUES FROM (100) TO (250);
+CREATE TABLE prt2_l_p2 PARTITION OF prt2_l FOR VALUES FROM (250) TO (500) PARTITION BY RANGE (c);
+CREATE TABLE prt2_l_p2_p1 PARTITION OF prt2_l_p2 FOR VALUES FROM ('0250') TO ('0400');
+CREATE TABLE prt2_l_p2_p2 PARTITION OF prt2_l_p2 FOR VALUES FROM ('0400') TO ('0500');
+CREATE TABLE prt2_l_p3 PARTITION OF prt2_l FOR VALUES FROM (500) TO (600) PARTITION BY RANGE ((a + b));
+CREATE TABLE prt2_l_p3_p1 PARTITION OF prt2_l_p3 FOR VALUES FROM (1000) TO (1100);
+CREATE TABLE prt2_l_p3_p2 PARTITION OF prt2_l_p3 FOR VALUES FROM (1100) TO (1200);
+INSERT INTO prt2_l SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 3) i;
+ANALYZE prt2_l;
+ANALYZE prt2_l_p1;
+ANALYZE prt2_l_p1_p1;
+ANALYZE prt2_l_p1_p2;
+ANALYZE prt2_l_p2;
+ANALYZE prt2_l_p2_p1;
+ANALYZE prt2_l_p2_p2;
+ANALYZE prt2_l_p3;
+ANALYZE prt2_l_p3_p1;
+ANALYZE prt2_l_p3_p2;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE uprt2_l AS SELECT * FROM prt2_l;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1.b = t2.a AND t1.c = t2.c AND t1.b + t1.a = t2.a + t2.b AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1.b = t2.a AND t1.c = t2.c AND t1.b + t1.a = t2.a + t2.b AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM uprt1_l t1, uprt2_l t2 WHERE t1.a = t2.b AND t1.b = t2.a AND t1.c = t2.c AND t1.b + t1.a = t2.a + t2.b AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 LEFT JOIN prt2_l t2 ON t1.a = t2.b AND t1.b = t2.a AND t1.c = t2.c AND t1.b + t1.a = t2.a + t2.b WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 LEFT JOIN prt2_l t2 ON t1.a = t2.b AND t1.b = t2.a AND t1.c = t2.c AND t1.b + t1.a = t2.a + t2.b WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM uprt1_l t1 LEFT JOIN uprt2_l t2 ON t1.a = t2.b AND t1.b = t2.a AND t1.c = t2.c AND t1.b + t1.a = t2.a + t2.b WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 RIGHT JOIN prt2_l t2 ON t1.a = t2.b AND t1.b = t2.a AND t1.c = t2.c AND t1.b + t1.a = t2.a + t2.b WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 RIGHT JOIN prt2_l t2 ON t1.a = t2.b AND t1.b = t2.a AND t1.c = t2.c AND t1.b + t1.a = t2.a + t2.b WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM uprt1_l t1 RIGHT JOIN uprt2_l t2 ON t1.a = t2.b AND t1.b = t2.a AND t1.c = t2.c AND t1.b + t1.a = t2.a + t2.b WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_l WHERE prt1_l.a % 25 = 0) t1 FULL JOIN (SELECT * FROM prt2_l WHERE prt2_l.b % 25 = 0) t2 ON (t1.a = t2.b AND t1.b = t2.a AND t1.c = t2.c AND t1.b + t1.a = t2.a + t2.b) ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_l WHERE prt1_l.a % 25 = 0) t1 FULL JOIN (SELECT * FROM prt2_l WHERE prt2_l.b % 25 = 0) t2 ON (t1.a = t2.b AND t1.b = t2.a AND t1.c = t2.c AND t1.b + t1.a = t2.a + t2.b) ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM uprt1_l t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM uprt2_l t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b AND t1.b = t2.a AND t1.c = t2.c AND t1.b + t1.a = t2.a + t2.b) ORDER BY t1.a, t2.b;
+
+-- lateral reference
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT * FROM prt1_l t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t2.c AS t2c, t2.b AS t2b, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM prt1_l t2 JOIN prt2_l t3 ON (t2.a = t3.b AND t2.b = t3.a AND t2.c = t3.c AND t2.b + t2.a = t3.a + t3.b)) ss
+			  ON t1.a = ss.t2a AND t1.b = ss.t2a AND t1.c = ss.t2c AND t1.b + t1.a = ss.t2a + ss.t2b WHERE t1.a % 25 = 0 ORDER BY t1.a;
+SELECT * FROM prt1_l t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t2.c AS t2c, t2.b AS t2b, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM prt1_l t2 JOIN prt2_l t3 ON (t2.a = t3.b AND t2.b = t3.a AND t2.c = t3.c AND t2.b + t2.a = t3.a + t3.b)) ss
+			  ON t1.a = ss.t2a AND t1.b = ss.t2a AND t1.c = ss.t2c AND t1.b + t1.a = ss.t2a + ss.t2b WHERE t1.a % 25 = 0 ORDER BY t1.a;
+SELECT * FROM uprt1_l t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t2.c AS t2c, t2.b AS t2b, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM uprt1_l t2 JOIN uprt2_l t3 ON (t2.a = t3.b AND t2.b = t3.a AND t2.c = t3.c AND t2.b + t2.a = t3.a + t3.b)) ss
+			  ON t1.a = ss.t2a AND t1.b = ss.t2a AND t1.c = ss.t2c AND t1.b + t1.a = ss.t2a + ss.t2b WHERE t1.a % 25 = 0 ORDER BY t1.a;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT * FROM prt1_l t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t2.c AS t2c, t2.b AS t2b, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM prt1_l t2 JOIN prt2_l t3 ON (t2.a = t3.b AND t2.b = t3.a AND t2.c = t3.c AND t2.b + t2.a = t3.a + t3.b)) ss
+			  ON t1.b = ss.t2a AND t1.b = ss.t2a AND t1.c = ss.t2c AND t1.b + t1.a = ss.t2a + ss.t2b WHERE t1.a % 25 = 0 ORDER BY t1.a;
+SELECT * FROM prt1_l t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t2.c AS t2c, t2.b AS t2b, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM prt1_l t2 JOIN prt2_l t3 ON (t2.a = t3.b AND t2.b = t3.a AND t2.c = t3.c AND t2.b + t2.a = t3.a + t3.b)) ss
+			  ON t1.b = ss.t2a AND t1.b = ss.t2a AND t1.c = ss.t2c AND t1.b + t1.a = ss.t2a + ss.t2b WHERE t1.a % 25 = 0 ORDER BY t1.a;
+SELECT * FROM uprt1_l t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t2.c AS t2c, t2.b AS t2b, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM uprt1_l t2 JOIN uprt2_l t3 ON (t2.a = t3.b AND t2.b = t3.a AND t2.c = t3.c AND t2.b + t2.a = t3.a + t3.b)) ss
+			  ON t1.b = ss.t2a AND t1.b = ss.t2a AND t1.c = ss.t2c AND t1.b + t1.a = ss.t2a + ss.t2b WHERE t1.a % 25 = 0 ORDER BY t1.a;
diff --git a/src/test/regress/sql/partition_join.sql b/src/test/regress/sql/partition_join.sql
new file mode 100644
index 0000000..9b2baeb
--- /dev/null
+++ b/src/test/regress/sql/partition_join.sql
@@ -0,0 +1,520 @@
+--
+-- PARTITION_JOIN
+-- Test partition-wise join between partitioned tables
+--
+
+-- Usually partition-wise join paths are chosen when data is large, which would
+-- take regression tests to run longer. So, weigh partition-wise joins cheaper
+-- to force those even for smaller data.
+SET partition_wise_plan_weight to 0.2;
+
+--
+-- partitioned by a single column
+--
+CREATE TABLE prt1 (a int, b int, c varchar) PARTITION BY RANGE(a);
+CREATE TABLE prt1_p1 PARTITION OF prt1 FOR VALUES FROM (0) TO (250);
+CREATE TABLE prt1_p3 PARTITION OF prt1 FOR VALUES FROM (500) TO (600);
+CREATE TABLE prt1_p2 PARTITION OF prt1 FOR VALUES FROM (250) TO (500);
+INSERT INTO prt1 SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 2) i;
+ANALYZE prt1;
+ANALYZE prt1_p1;
+ANALYZE prt1_p2;
+ANALYZE prt1_p3;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE uprt1 AS SELECT * FROM prt1;
+
+CREATE TABLE prt2 (a int, b int, c varchar) PARTITION BY RANGE(b);
+CREATE TABLE prt2_p1 PARTITION OF prt2 FOR VALUES FROM (0) TO (250);
+CREATE TABLE prt2_p2 PARTITION OF prt2 FOR VALUES FROM (250) TO (500);
+CREATE TABLE prt2_p3 PARTITION OF prt2 FOR VALUES FROM (500) TO (600);
+INSERT INTO prt2 SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 3) i;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+ANALYZE prt2;
+ANALYZE prt2_p1;
+ANALYZE prt2_p2;
+ANALYZE prt2_p3;
+CREATE TABLE uprt2 AS SELECT * FROM prt2;
+
+-- inner join
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM uprt1 t1, uprt2 t2 WHERE t1.a = t2.b AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+
+-- left outer join
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM uprt1 t1 LEFT JOIN uprt2 t2 ON t1.a = t2.b WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+
+-- right outer join
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM uprt1 t1 RIGHT JOIN uprt2 t2 ON t1.a = t2.b WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+
+-- full outer join
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE prt1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM prt2 WHERE prt2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE prt1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM prt2 WHERE prt2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM uprt1 t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM uprt2 t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b;
+
+-- Cases with non-nullable expressions in subquery results;
+-- make sure these go to null as expected
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 50 phv, * FROM prt1 WHERE prt1.a % 25 = 0) t1 FULL JOIN (SELECT 75 phv, * FROM prt2 WHERE prt2.b % 25 = 0) t2 ON (t1.a = t2.b) WHERE t1.phv = t1.b OR t2.phv = t2.b ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 50 phv, * FROM prt1 WHERE prt1.a % 25 = 0) t1 FULL JOIN (SELECT 75 phv, * FROM prt2 WHERE prt2.b % 25 = 0) t2 ON (t1.a = t2.b) WHERE t1.phv = t1.b OR t2.phv = t2.b ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 50 phv, * FROM uprt1 WHERE uprt1.a % 25 = 0) t1 FULL JOIN (SELECT 75 phv, * FROM uprt2 WHERE uprt2.b % 25 = 0) t2 ON (t1.a = t2.b) WHERE t1.phv = t1.b OR t2.phv = t2.b ORDER BY t1.a, t2.b;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t1.phv, t2.b, t2.c, t2.phv FROM (SELECT 25 phv, * FROM prt1 WHERE prt1.a % 25 = 0) t1 FULL JOIN (SELECT 50 phv, * FROM prt2 WHERE prt2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t1.phv, t2.b, t2.c, t2.phv FROM (SELECT 25 phv, * FROM prt1 WHERE prt1.a % 25 = 0) t1 FULL JOIN (SELECT 50 phv, * FROM prt2 WHERE prt2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t1.phv, t2.b, t2.c, t2.phv FROM (SELECT 25 phv, * FROM uprt1 WHERE uprt1.a % 25 = 0) t1 FULL JOIN (SELECT 50 phv, * FROM uprt2 WHERE uprt2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b;
+
+-- Join with pruned partitions from joining relations
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM uprt1 t1, uprt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM uprt1 WHERE a < 450) t1 LEFT JOIN (SELECT * FROM uprt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450) t1 RIGHT JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t2.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450) t1 RIGHT JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t2.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM uprt1 WHERE a < 450) t1 RIGHT JOIN (SELECT * FROM uprt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t2.a % 25 = 0 ORDER BY t1.a, t2.b;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450 AND a % 25 = 0) t1 FULL JOIN (SELECT * FROM prt2 WHERE b > 250 AND b % 25 = 0) t2 ON t1.a = t2.b ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450 AND a % 25 = 0) t1 FULL JOIN (SELECT * FROM prt2 WHERE b > 250 AND b % 25 = 0) t2 ON t1.a = t2.b ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM uprt1 WHERE a < 450 AND a % 25 = 0) t1 FULL JOIN (SELECT * FROM uprt2 WHERE b > 250 AND b % 25 = 0) t2 ON t1.a = t2.b ORDER BY t1.a, t2.b;
+
+-- Semi-join
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b % 25 = 0) AND t1.a % 25 = 0 ORDER BY t1.a;
+SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b % 25 = 0) AND t1.a % 25 = 0 ORDER BY t1.a;
+SELECT t1.* FROM uprt1 t1 WHERE t1.a IN (SELECT t1.b FROM uprt2 t1 WHERE t1.b % 25 = 0) AND t1.a % 25 = 0 ORDER BY t1.a;
+
+-- lateral reference
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT * FROM prt1 t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss
+			  ON t1.a = ss.t2a WHERE t1.a % 25 = 0 ORDER BY t1.a;
+SELECT * FROM prt1 t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss
+			  ON t1.a = ss.t2a WHERE t1.a % 25 = 0 ORDER BY t1.a;
+SELECT * FROM uprt1 t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM uprt1 t2 JOIN uprt2 t3 ON (t2.a = t3.b)) ss
+			  ON t1.a = ss.t2a WHERE t1.a % 25 = 0 ORDER BY t1.a;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT * FROM prt1 t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss
+			  ON t1.b = ss.t2a WHERE t1.a % 25 = 0 ORDER BY t1.a;
+SELECT * FROM prt1 t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss
+			  ON t1.b = ss.t2a WHERE t1.a % 25 = 0 ORDER BY t1.a;
+SELECT * FROM uprt1 t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM uprt1 t2 JOIN uprt2 t3 ON (t2.a = t3.b)) ss
+			  ON t1.b = ss.t2a WHERE t1.a % 25 = 0 ORDER BY t1.a;
+
+--
+-- partitioned by expression
+--
+CREATE TABLE prt1_e (a int, b int, c varchar) PARTITION BY RANGE(((a + b)/2));
+CREATE TABLE prt1_e_p1 PARTITION OF prt1_e FOR VALUES FROM (0) TO (250);
+CREATE TABLE prt1_e_p2 PARTITION OF prt1_e FOR VALUES FROM (250) TO (500);
+CREATE TABLE prt1_e_p3 PARTITION OF prt1_e FOR VALUES FROM (500) TO (600);
+INSERT INTO prt1_e SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 2) i;
+ANALYZE prt1_e;
+ANALYZE prt1_e_p1;
+ANALYZE prt1_e_p2;
+ANALYZE prt1_e_p3;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE uprt1_e AS SELECT * FROM prt1_e;
+
+CREATE TABLE prt2_e (a int, b int, c varchar) PARTITION BY RANGE(((b + a)/2));
+CREATE TABLE prt2_e_p1 PARTITION OF prt2_e FOR VALUES FROM (0) TO (250);
+CREATE TABLE prt2_e_p2 PARTITION OF prt2_e FOR VALUES FROM (250) TO (500);
+CREATE TABLE prt2_e_p3 PARTITION OF prt2_e FOR VALUES FROM (500) TO (600);
+INSERT INTO prt2_e SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 3) i;
+ANALYZE prt2_e;
+ANALYZE prt2_e_p1;
+ANALYZE prt2_e_p2;
+ANALYZE prt2_e_p3;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE uprt2_e AS SELECT * FROM prt2_e;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_e t1, prt2_e t2 WHERE (t1.a + t1.b)/2 = (t2.b + t2.a)/2 AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_e t1, prt2_e t2 WHERE (t1.a + t1.b)/2 = (t2.b + t2.a)/2 AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM uprt1_e t1, uprt2_e t2 WHERE (t1.a + t1.b)/2 = (t2.b + t2.a)/2 AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_e t1 LEFT JOIN prt2_e t2 ON (t1.a + t1.b)/2 = (t2.b + t2.a)/2 WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_e t1 LEFT JOIN prt2_e t2 ON (t1.a + t1.b)/2 = (t2.b + t2.a)/2 WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM uprt1_e t1 LEFT JOIN uprt2_e t2 ON (t1.a + t1.b)/2 = (t2.b + t2.a)/2 WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+
+--
+-- N-way join
+--
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t3 WHERE t1.a = t2.b AND t1.a = (t3.a + t3.b)/2 AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t3 WHERE t1.a = t2.b AND t1.a = (t3.a + t3.b)/2 AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM uprt1 t1, uprt2 t2, uprt1_e t3 WHERE t1.a = t2.b AND t1.a = (t3.a + t3.b)/2 AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) LEFT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) LEFT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (uprt1 t1 LEFT JOIN uprt2 t2 ON t1.a = t2.b) LEFT JOIN uprt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) LEFT JOIN prt1_e t3 ON (t2.b = (t3.a + t3.b)/2) WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) LEFT JOIN prt1_e t3 ON (t2.b = (t3.a + t3.b)/2) WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (uprt1 t1 LEFT JOIN uprt2 t2 ON t1.a = t2.b) LEFT JOIN uprt1_e t3 ON (t2.b = (t3.a + t3.b)/2) WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (uprt1 t1 LEFT JOIN uprt2 t2 ON t1.a = t2.b) RIGHT JOIN uprt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t2.b = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t2.b = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (uprt1 t1 RIGHT JOIN uprt2 t2 ON t1.a = t2.b) RIGHT JOIN uprt1_e t3 ON (t2.b = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM ((SELECT * FROM prt1 WHERE prt1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM prt2 WHERE prt2.b % 25 = 0) t2 ON (t1.a = t2.b)) FULL JOIN (SELECT * FROM prt1_e WHERE prt1_e.a % 25 = 0) t3 ON (t1.a = (t3.a + t3.b)/2) ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM ((SELECT * FROM prt1 WHERE prt1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM prt2 WHERE prt2.b % 25 = 0) t2 ON (t1.a = t2.b)) FULL JOIN (SELECT * FROM prt1_e WHERE prt1_e.a % 25 = 0) t3 ON (t1.a = (t3.a + t3.b)/2) ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM ((SELECT * FROM uprt1 WHERE uprt1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM uprt2 WHERE uprt2.b % 25 = 0) t2 ON (t1.a = t2.b)) FULL JOIN (SELECT * FROM uprt1_e WHERE uprt1_e.a % 25 = 0) t3 ON (t1.a = (t3.a + t3.b)/2) ORDER BY t1.a, t2.b, t3.a + t3.b;
+
+-- Cases with non-nullable expressions in subquery results;
+-- make sure these go to null as expected
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.phv, t2.b, t2.phv, t3.a + t3.b, t3.phv FROM ((SELECT 50 phv, * FROM prt1 WHERE prt1.a % 25 = 0) t1 FULL JOIN (SELECT 75 phv, * FROM prt2 WHERE prt2.b % 25 = 0) t2 ON (t1.a = t2.b)) FULL JOIN (SELECT 50 phv, * FROM prt1_e WHERE prt1_e.a % 25 = 0) t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.a = t1.phv OR t2.b = t2.phv OR (t3.a + t3.b)/2 = t3.phv ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.phv, t2.b, t2.phv, t3.a + t3.b, t3.phv FROM ((SELECT 50 phv, * FROM prt1 WHERE prt1.a % 25 = 0) t1 FULL JOIN (SELECT 75 phv, * FROM prt2 WHERE prt2.b % 25 = 0) t2 ON (t1.a = t2.b)) FULL JOIN (SELECT 50 phv, * FROM prt1_e WHERE prt1_e.a % 25 = 0) t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.a = t1.phv OR t2.b = t2.phv OR (t3.a + t3.b)/2 = t3.phv ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.phv, t2.b, t2.phv, t3.a + t3.b, t3.phv FROM ((SELECT 50 phv, * FROM uprt1 WHERE uprt1.a % 25 = 0) t1 FULL JOIN (SELECT 75 phv, * FROM uprt2 WHERE uprt2.b % 25 = 0) t2 ON (t1.a = t2.b)) FULL JOIN (SELECT 50 phv, * FROM uprt1_e WHERE uprt1_e.a % 25 = 0) t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.a = t1.phv OR t2.b = t2.phv OR (t3.a + t3.b)/2 = t3.phv ORDER BY t1.a, t2.b, t3.a + t3.b;
+
+-- Semi-join
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1, prt1_e t2 WHERE t1.b % 25 = 0 AND t1.b = (t2.a + t2.b)/2) AND t1.a % 25 = 0 ORDER BY t1.a;
+SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1, prt1_e t2 WHERE t1.b % 25 = 0 AND t1.b = (t2.a + t2.b)/2) AND t1.a % 25 = 0 ORDER BY t1.a;
+SELECT t1.* FROM uprt1 t1 WHERE t1.a IN (SELECT t1.b FROM uprt2 t1, uprt1_e t2 WHERE t1.b % 25 = 0 AND t1.b = (t2.a + t2.b)/2) AND t1.a % 25 = 0 ORDER BY t1.a;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.a %25 = 0)) AND t1.a % 25 = 0 ORDER BY t1.a;
+SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.a %25 = 0)) AND t1.a % 25 = 0 ORDER BY t1.a;
+SELECT t1.* FROM uprt1 t1 WHERE t1.a IN (SELECT t1.b FROM uprt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM uprt1_e t1 WHERE t1.a %25 = 0)) AND t1.a % 25 = 0 ORDER BY t1.a;
+
+-- test merge joins with and without using indexes
+SET enable_hashjoin TO off;
+SET enable_nestloop TO off;
+
+CREATE INDEX iprt1_p1_a on prt1_p1(a);
+CREATE INDEX iprt1_p2_a on prt1_p2(a);
+CREATE INDEX iprt1_p3_a on prt1_p3(a);
+CREATE INDEX iprt2_p1_b on prt2_p1(b);
+CREATE INDEX iprt2_p2_b on prt2_p2(b);
+CREATE INDEX iprt2_p3_b on prt2_p3(b);
+CREATE INDEX iprt1_e_p1_ab2 on prt1_e_p1(((a+b)/2));
+CREATE INDEX iprt1_e_p2_ab2 on prt1_e_p2(((a+b)/2));
+CREATE INDEX iprt1_e_p3_ab2 on prt1_e_p3(((a+b)/2));
+
+ANALYZE prt1;
+ANALYZE prt1_p1;
+ANALYZE prt1_p2;
+ANALYZE prt1_p3;
+ANALYZE prt2;
+ANALYZE prt2_p1;
+ANALYZE prt2_p2;
+ANALYZE prt2_p3;
+ANALYZE prt1_e;
+ANALYZE prt1_e_p1;
+ANALYZE prt1_e_p2;
+ANALYZE prt1_e_p3;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t2.b = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t2.b = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (uprt1 t1 RIGHT JOIN uprt2 t2 ON t1.a = t2.b) RIGHT JOIN uprt1_e t3 ON (t2.b = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.a %25 = 0)) AND t1.a % 25 = 0 ORDER BY t1.a;
+SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.a %25 = 0)) AND t1.a % 25 = 0 ORDER BY t1.a;
+SELECT t1.* FROM uprt1 t1 WHERE t1.a IN (SELECT t1.b FROM uprt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM uprt1_e t1 WHERE t1.a %25 = 0)) AND t1.a % 25 = 0 ORDER BY t1.a;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (uprt1 t1 LEFT JOIN uprt2 t2 ON t1.a = t2.b) RIGHT JOIN uprt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+
+SET enable_seqscan TO off;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t2.b = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t2.b = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (uprt1 t1 RIGHT JOIN uprt2 t2 ON t1.a = t2.b) RIGHT JOIN uprt1_e t3 ON (t2.b = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.a %25 = 0)) AND t1.a % 25 = 0 ORDER BY t1.a;
+SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.a %25 = 0)) AND t1.a % 25 = 0 ORDER BY t1.a;
+SELECT t1.* FROM uprt1 t1 WHERE t1.a IN (SELECT t1.b FROM uprt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM uprt1_e t1 WHERE t1.a %25 = 0)) AND t1.a % 25 = 0 ORDER BY t1.a;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (uprt1 t1 LEFT JOIN uprt2 t2 ON t1.a = t2.b) RIGHT JOIN uprt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+
+-- lateral references and parameterized paths
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT * FROM prt1 t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss
+			  ON t1.a = ss.t2a WHERE t1.a % 25 = 0 ORDER BY t1.a;
+SELECT * FROM prt1 t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss
+			  ON t1.a = ss.t2a WHERE t1.a % 25 = 0 ORDER BY t1.a;
+SELECT * FROM uprt1 t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM uprt1 t2 JOIN uprt2 t3 ON (t2.a = t3.b)) ss
+			  ON t1.a = ss.t2a WHERE t1.a % 25 = 0 ORDER BY t1.a;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT * FROM prt1 t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss
+			  ON t1.b = ss.t2a WHERE t1.a % 25 = 0 ORDER BY t1.a;
+SELECT * FROM prt1 t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss
+			  ON t1.b = ss.t2a WHERE t1.a % 25 = 0 ORDER BY t1.a;
+SELECT * FROM uprt1 t1 LEFT JOIN LATERAL
+			  (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.a) FROM uprt1 t2 JOIN uprt2 t3 ON (t2.a = t3.b)) ss
+			  ON t1.b = ss.t2a WHERE t1.a % 25 = 0 ORDER BY t1.a;
+
+RESET enable_hashjoin;
+RESET enable_nestloop;
+RESET enable_seqscan;
+
+--
+-- partitioned by multiple columns
+--
+CREATE TABLE prt1_m (a int, b int, c varchar) PARTITION BY RANGE(a, ((a + b)/2));
+CREATE TABLE prt1_m_p1 PARTITION OF prt1_m FOR VALUES FROM (0, 0) TO (250, 250);
+CREATE TABLE prt1_m_p2 PARTITION OF prt1_m FOR VALUES FROM (250, 250) TO (500, 500);
+CREATE TABLE prt1_m_p3 PARTITION OF prt1_m FOR VALUES FROM (500, 500) TO (600, 600);
+INSERT INTO prt1_m SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 2) i;
+ANALYZE prt1_m;
+ANALYZE prt1_m_p1;
+ANALYZE prt1_m_p2;
+ANALYZE prt1_m_p3;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE uprt1_m AS SELECT * FROM prt1_m;
+
+CREATE TABLE prt2_m (a int, b int, c varchar) PARTITION BY RANGE(((b + a)/2), b);
+CREATE TABLE prt2_m_p1 PARTITION OF prt2_m FOR VALUES FROM (0, 0) TO (250, 250);
+CREATE TABLE prt2_m_p2 PARTITION OF prt2_m FOR VALUES FROM (250, 250) TO (500, 500);
+CREATE TABLE prt2_m_p3 PARTITION OF prt2_m FOR VALUES FROM (500, 500) TO (600, 600);
+INSERT INTO prt2_m SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 3) i;
+ANALYZE prt2_m;
+ANALYZE prt2_m_p1;
+ANALYZE prt2_m_p2;
+ANALYZE prt2_m_p3;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE uprt2_m AS SELECT * FROM prt2_m;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_m t1 RIGHT JOIN prt2_m t2 ON t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2 WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_m t1 RIGHT JOIN prt2_m t2 ON t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2 WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM uprt1_m t1 RIGHT JOIN uprt2_m t2 ON t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2 WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_m WHERE prt1_m.a % 25 = 0) t1 FULL JOIN (SELECT * FROM prt2_m WHERE prt2_m.b % 25 = 0) t2 ON (t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2) ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_m WHERE prt1_m.a % 25 = 0) t1 FULL JOIN (SELECT * FROM prt2_m WHERE prt2_m.b % 25 = 0) t2 ON (t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2) ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM uprt1_m t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM uprt2_m t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2) ORDER BY t1.a, t2.b;
+
+--
+-- tests for list partitioned tables.
+--
+CREATE TABLE plt1 (a int, b int, c text) PARTITION BY LIST(c);
+CREATE TABLE plt1_p1 PARTITION OF plt1 FOR VALUES IN ('0000', '0003', '0004', '0010');
+CREATE TABLE plt1_p2 PARTITION OF plt1 FOR VALUES IN ('0001', '0005', '0002', '0009');
+CREATE TABLE plt1_p3 PARTITION OF plt1 FOR VALUES IN ('0006', '0007', '0008', '0011');
+INSERT INTO plt1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i;
+ANALYZE plt1;
+ANALYZE plt1_p1;
+ANALYZE plt1_p2;
+ANALYZE plt1_p3;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE uplt1 AS SELECT * FROM plt1;
+
+CREATE TABLE plt2 (a int, b int, c text) PARTITION BY LIST(c);
+CREATE TABLE plt2_p1 PARTITION OF plt2 FOR VALUES IN ('0000', '0003', '0004', '0010');
+CREATE TABLE plt2_p2 PARTITION OF plt2 FOR VALUES IN ('0001', '0005', '0002', '0009');
+CREATE TABLE plt2_p3 PARTITION OF plt2 FOR VALUES IN ('0006', '0007', '0008', '0011');
+INSERT INTO plt2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 3) i;
+ANALYZE plt2;
+ANALYZE plt2_p1;
+ANALYZE plt2_p2;
+ANALYZE plt2_p3;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE uplt2 AS SELECT * FROM plt2;
+
+--
+-- list partitioned by expression
+--
+CREATE TABLE plt1_e (a int, b int, c text) PARTITION BY LIST(ltrim(c, 'A'));
+CREATE TABLE plt1_e_p1 PARTITION OF plt1_e FOR VALUES IN ('0000', '0003', '0004', '0010');
+CREATE TABLE plt1_e_p2 PARTITION OF plt1_e FOR VALUES IN ('0001', '0005', '0002', '0009');
+CREATE TABLE plt1_e_p3 PARTITION OF plt1_e FOR VALUES IN ('0006', '0007', '0008', '0011');
+INSERT INTO plt1_e SELECT i, i, 'A' || to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i;
+ANALYZE plt1_e;
+ANALYZE plt1_e_p1;
+ANALYZE plt1_e_p2;
+ANALYZE plt1_e_p3;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE uplt1_e AS SELECT * FROM plt1_e;
+
+--
+-- N-way join
+--
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM plt1 t1, plt2 t2, plt1_e t3 WHERE t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c;
+SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM plt1 t1, plt2 t2, plt1_e t3 WHERE t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c;
+SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM uplt1 t1, uplt2 t2, uplt1_e t3 WHERE t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (plt1 t1 LEFT JOIN plt2 t2 ON t1.a = t2.b AND t1.c = t2.c) LEFT JOIN plt1_e t3 ON (t1.a = t3.a AND ltrim(t3.c, 'A') = t1.c) WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (plt1 t1 LEFT JOIN plt2 t2 ON t1.a = t2.b AND t1.c = t2.c) LEFT JOIN plt1_e t3 ON (t1.a = t3.a AND ltrim(t3.c, 'A') = t1.c) WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (uplt1 t1 LEFT JOIN uplt2 t2 ON t1.a = t2.b AND t1.c = t2.c) LEFT JOIN uplt1_e t3 ON (t1.a = t3.a AND ltrim(t3.c, 'A') = t1.c) WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (plt1 t1 LEFT JOIN plt2 t2 ON t1.a = t2.b AND t1.c = t2.c) LEFT JOIN plt1_e t3 ON (t2.b = t3.a AND t2.c = ltrim(t3.c, 'A')) WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (plt1 t1 LEFT JOIN plt2 t2 ON t1.a = t2.b AND t1.c = t2.c) LEFT JOIN plt1_e t3 ON (t2.b = t3.a AND t2.c = ltrim(t3.c, 'A')) WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (uplt1 t1 LEFT JOIN uplt2 t2 ON t1.a = t2.b AND t1.c = t2.c) LEFT JOIN uplt1_e t3 ON (t2.b = t3.a AND t2.c = ltrim(t3.c, 'A')) WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (plt1 t1 LEFT JOIN plt2 t2 ON t1.a = t2.b AND t1.c = t2.c) RIGHT JOIN plt1_e t3 ON (t1.a = t3.a AND ltrim(t3.c, 'A') = t1.c) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (plt1 t1 LEFT JOIN plt2 t2 ON t1.a = t2.b AND t1.c = t2.c) RIGHT JOIN plt1_e t3 ON (t1.a = t3.a AND ltrim(t3.c, 'A') = t1.c) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (uplt1 t1 LEFT JOIN uplt2 t2 ON t1.a = t2.b AND t1.c = t2.c) RIGHT JOIN uplt1_e t3 ON (t1.a = t3.a AND ltrim(t3.c, 'A') = t1.c) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (plt1 t1 RIGHT JOIN plt2 t2 ON t1.a = t2.b AND t1.c = t2.c) RIGHT JOIN plt1_e t3 ON (t2.b = t3.a AND t2.c = ltrim(t3.c, 'A')) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (plt1 t1 RIGHT JOIN plt2 t2 ON t1.a = t2.b AND t1.c = t2.c) RIGHT JOIN plt1_e t3 ON (t2.b = t3.a AND t2.c = ltrim(t3.c, 'A')) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (uplt1 t1 RIGHT JOIN uplt2 t2 ON t1.a = t2.b AND t1.c = t2.c) RIGHT JOIN uplt1_e t3 ON (t2.b = t3.a AND t2.c = ltrim(t3.c, 'A')) WHERE t3.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM ((SELECT * FROM plt1 WHERE plt1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM plt2 WHERE plt2.b % 25 = 0) t2 ON (t1.a = t2.b AND t1.c = t2.c)) FULL JOIN (SELECT * FROM plt1_e WHERE plt1_e.a % 25 = 0) t3 ON (t1.a = t3.a AND ltrim(t3.c, 'A') = t1.c) ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM ((SELECT * FROM plt1 WHERE plt1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM plt2 WHERE plt2.b % 25 = 0) t2 ON (t1.a = t2.b AND t1.c = t2.c)) FULL JOIN (SELECT * FROM plt1_e WHERE plt1_e.a % 25 = 0) t3 ON (t1.a = t3.a AND ltrim(t3.c, 'A') = t1.c) ORDER BY t1.a, t2.b, t3.a + t3.b;
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM ((SELECT * FROM uplt1 WHERE uplt1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM uplt2 WHERE uplt2.b % 25 = 0) t2 ON (t1.a = t2.b AND t1.c = t2.c)) FULL JOIN (SELECT * FROM uplt1_e WHERE uplt1_e.a % 25 = 0) t3 ON (t1.a = t3.a AND ltrim(t3.c, 'A') = t1.c) ORDER BY t1.a, t2.b, t3.a + t3.b;
+
+-- Semi-join
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.* FROM plt1 t1 WHERE t1.c IN (SELECT t1.c FROM plt2 t1, plt1_e t2 WHERE t1.c = ltrim(t2.c, 'A')) AND t1.a % 25 = 0 ORDER BY t1.a;
+SELECT t1.* FROM plt1 t1 WHERE t1.c IN (SELECT t1.c FROM plt2 t1, plt1_e t2 WHERE t1.c = ltrim(t2.c, 'A')) AND t1.a % 25 = 0 ORDER BY t1.a;
+SELECT t1.* FROM uplt1 t1 WHERE t1.c IN (SELECT t1.c FROM uplt2 t1, uplt1_e t2 WHERE t1.c = ltrim(t2.c, 'A')) AND t1.a % 25 = 0 ORDER BY t1.a;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.* FROM plt1 t1 WHERE t1.c IN (SELECT t1.c FROM plt2 t1 WHERE t1.c IN (SELECT ltrim(t1.c, 'A') FROM plt1_e t1 WHERE t1.a % 25 = 0)) AND t1.a % 25 = 0 ORDER BY t1.a;
+SELECT t1.* FROM plt1 t1 WHERE t1.c IN (SELECT t1.c FROM plt2 t1 WHERE t1.c IN (SELECT ltrim(t1.c, 'A') FROM plt1_e t1 WHERE t1.a % 25 = 0)) AND t1.a % 25 = 0 ORDER BY t1.a;
+SELECT t1.* FROM uplt1 t1 WHERE t1.c IN (SELECT t1.c FROM uplt2 t1 WHERE t1.c IN (SELECT ltrim(t1.c, 'A') FROM uplt1_e t1 WHERE t1.a % 25 = 0)) AND t1.a % 25 = 0 ORDER BY t1.a;
+
+--
+-- negative testcases
+--
+
+-- joins where one of the relations is proven empty
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a = 1 AND t1.a = 2;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 LEFT JOIN prt2 t2 ON t1.a = t2.b;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a % 25 = 0 ORDER BY t1.a, t2.b;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 FULL JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a % 25 = 0 ORDER BY t1.a, t2.b;
+
+CREATE TABLE prt1_n (a int, b int, c varchar) PARTITION BY RANGE(c);
+CREATE TABLE prt1_n_p1 PARTITION OF prt1_n FOR VALUES FROM ('0000') TO ('0250');
+CREATE TABLE prt1_n_p2 PARTITION OF prt1_n FOR VALUES FROM ('0250') TO ('0500');
+INSERT INTO prt1_n SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 499, 2) i;
+ANALYZE prt1_n;
+ANALYZE prt1_n_p1;
+ANALYZE prt1_n_p2;
+
+CREATE TABLE prt2_n (a int, b int, c text) PARTITION BY LIST(c);
+CREATE TABLE prt2_n_p1 PARTITION OF prt2_n FOR VALUES IN ('0000', '0003', '0004', '0010', '0006', '0007');
+CREATE TABLE prt2_n_p2 PARTITION OF prt2_n FOR VALUES IN ('0001', '0005', '0002', '0009', '0008', '0011');
+INSERT INTO prt2_n SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i;
+ANALYZE prt2_n;
+ANALYZE prt2_n_p1;
+ANALYZE prt2_n_p2;
+
+CREATE TABLE prt3_n (a int, b int, c text) PARTITION BY LIST(c);
+CREATE TABLE prt3_n_p1 PARTITION OF prt3_n FOR VALUES IN ('0000', '0004', '0006', '0007');
+CREATE TABLE prt3_n_p2 PARTITION OF prt3_n FOR VALUES IN ('0001', '0002', '0008', '0010');
+CREATE TABLE prt3_n_p3 PARTITION OF prt3_n FOR VALUES IN ('0003', '0005', '0009', '0011');
+INSERT INTO prt2_n SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i;
+ANALYZE prt3_n;
+ANALYZE prt3_n_p1;
+ANALYZE prt3_n_p2;
+ANALYZE prt3_n_p3;
+
+CREATE TABLE prt4_n (a int, b int, c text) PARTITION BY RANGE(a);
+CREATE TABLE prt4_n_p1 PARTITION OF prt4_n FOR VALUES FROM (0) TO (300);
+CREATE TABLE prt4_n_p2 PARTITION OF prt4_n FOR VALUES FROM (300) TO (500);
+CREATE TABLE prt4_n_p3 PARTITION OF prt4_n FOR VALUES FROM (500) TO (600);
+INSERT INTO prt4_n SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 2) i;
+ANALYZE prt4_n;
+ANALYZE prt4_n_p1;
+ANALYZE prt4_n_p2;
+ANALYZE prt4_n_p3;
+
+-- partition-wise join can not be applied if the partition ranges differ
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt4_n t2 WHERE t1.a = t2.a;
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 FULL JOIN prt4_n t2 ON t1.a = t2.a;
+
+-- partition-wise join can not be applied if there are no equi-join conditions
+-- between partition keys
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 LEFT JOIN prt2 t2 ON (t1.a < t2.b);
+
+-- equi-join with join condition on partial keys does not qualify for
+-- partition-wise join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_m t1, prt2_m t2 WHERE t1.a = (t2.b + t2.a)/2 AND t1.a % 25 = 0;
+
+-- equi-join between out-of-order partition key columns does not qualify for
+-- partition-wise join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_m t1 LEFT JOIN prt2_m t2 ON t1.a = t2.b WHERE t1.a % 25 = 0;
+
+-- equi-join between non-key columns does not qualify for partition-wise join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_m t1 LEFT JOIN prt2_m t2 ON t1.c = t2.c WHERE t1.a % 25 = 0;
+
+-- partition-wise join can not be applied for a join between list and range
+-- partitioned table
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_n t1, prt2_n t2 WHERE t1.c = t2.c;
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_n t1 LEFT JOIN prt2_n t2 ON (t1.c = t2.c);
+
+-- partition-wise join can not be applied between tables with different
+-- partition lists
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_n t1 RIGHT JOIN prt1 t2 ON (t1.c = t2.c);
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_n t1 FULL JOIN prt1 t2 ON (t1.c = t2.c);
