pg_dp_join_v5.patch

binary/octet-stream

Filename: pg_dp_join_v5.patch
Type: binary/octet-stream
Part: 1
Message: Re: Partition-wise join for join between (declaratively) partitioned tables

Patch

Format: unified
Series: patch v5
File+
contrib/postgres_fdw/deparse.c 7 9
contrib/postgres_fdw/postgres_fdw.c 4 4
src/backend/catalog/partition.c 226 0
src/backend/foreign/foreign.c 3 3
src/backend/optimizer/path/allpaths.c 407 52
src/backend/optimizer/path/costsize.c 1 0
src/backend/optimizer/path/equivclass.c 3 1
src/backend/optimizer/path/joinpath.c 46 5
src/backend/optimizer/path/joinrels.c 502 6
src/backend/optimizer/path/pathkeys.c 13 1
src/backend/optimizer/plan/createplan.c 257 20
src/backend/optimizer/plan/planner.c 1 1
src/backend/optimizer/plan/subselect.c 11 2
src/backend/optimizer/prep/prepunion.c 127 57
src/backend/optimizer/README 53 0
src/backend/optimizer/util/pathnode.c 313 0
src/backend/optimizer/util/placeholder.c 28 3
src/backend/optimizer/util/plancat.c 15 0
src/backend/optimizer/util/relnode.c 356 69
src/backend/postmaster/startup.c 0 1
src/backend/utils/adt/selfuncs.c 3 1
src/backend/utils/misc/guc.c 9 0
src/include/catalog/partition.h 40 0
src/include/nodes/nodes.h 1 0
src/include/nodes/relation.h 61 0
src/include/optimizer/cost.h 1 0
src/include/optimizer/pathnode.h 12 0
src/include/optimizer/paths.h 7 0
src/include/optimizer/prep.h 4 1
src/test/regress/expected/multi_level_partition_join.out 458 0
src/test/regress/expected/partition_join.out 5043 0
src/test/regress/expected/rangefuncs.out 15 14
src/test/regress/parallel_schedule 2 1
src/test/regress/serial_schedule 2 0
src/test/regress/sql/multi_level_partition_join.sql 95 0
src/test/regress/sql/partition_join.sql 600 0
diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index 691658f..287c7d5 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -770,7 +770,7 @@ deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel,
 	deparse_expr_cxt context;
 
 	/* We handle relations for foreign tables and joins between those */
-	Assert(rel->reloptkind == RELOPT_JOINREL ||
+	Assert(IS_JOIN_REL(rel) ||
 		   rel->reloptkind == RELOPT_BASEREL ||
 		   rel->reloptkind == RELOPT_OTHER_MEMBER_REL);
 
@@ -824,7 +824,7 @@ deparseSelectSql(List *tlist, List **retrieved_attrs, deparse_expr_cxt *context)
 	 */
 	appendStringInfoString(buf, "SELECT ");
 
-	if (foreignrel->reloptkind == RELOPT_JOINREL)
+	if (IS_JOIN_REL(foreignrel))
 	{
 		/* For a join relation use the input tlist */
 		deparseExplicitTargetList(tlist, retrieved_attrs, context);
@@ -852,8 +852,7 @@ deparseSelectSql(List *tlist, List **retrieved_attrs, deparse_expr_cxt *context)
 	 * Construct FROM clause
 	 */
 	appendStringInfoString(buf, " FROM ");
-	deparseFromExprForRel(buf, root, foreignrel,
-						  (foreignrel->reloptkind == RELOPT_JOINREL),
+	deparseFromExprForRel(buf, root, foreignrel, IS_JOIN_REL(foreignrel),
 						  context->params_list);
 }
 
@@ -988,7 +987,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
@@ -1024,8 +1023,7 @@ deparseLockingClause(deparse_expr_cxt *context)
 				}
 
 				/* Add the relation alias if we are here for a join relation */
-				if (rel->reloptkind == RELOPT_JOINREL &&
-					rc->strength != LCS_NONE)
+				if (IS_JOIN_REL(rel) && rc->strength != LCS_NONE)
 					appendStringInfo(buf, " OF %s%d", REL_ALIAS_PREFIX, relid);
 			}
 		}
@@ -1162,7 +1160,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;
@@ -1867,7 +1865,7 @@ deparseExpr(Expr *node, deparse_expr_cxt *context)
 static void
 deparseVar(Var *node, deparse_expr_cxt *context)
 {
-	bool		qualify_col = (context->foreignrel->reloptkind == RELOPT_JOINREL);
+	bool		qualify_col = IS_JOIN_REL(context->foreignrel);
 
 	if (bms_is_member(node->varno, context->foreignrel->relids) &&
 		node->varlevelsup == 0)
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index daf0438..594292a 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -1170,7 +1170,7 @@ postgresGetForeignPlan(PlannerInfo *root,
 			local_exprs = lappend(local_exprs, rinfo->clause);
 	}
 
-	if (foreignrel->reloptkind == RELOPT_JOINREL)
+	if (IS_JOIN_REL(foreignrel))
 	{
 		/* For a join relation, get the conditions from fdw_private structure */
 		remote_conds = fpinfo->remote_conds;
@@ -1228,7 +1228,7 @@ postgresGetForeignPlan(PlannerInfo *root,
 							 remote_conds,
 							 retrieved_attrs,
 							 makeInteger(fpinfo->fetch_size));
-	if (foreignrel->reloptkind == RELOPT_JOINREL)
+	if (IS_JOIN_REL(foreignrel))
 		fdw_private = lappend(fdw_private,
 							  makeString(fpinfo->relation_name->data));
 
@@ -2505,7 +2505,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))
 			fdw_scan_tlist = build_tlist_to_deparse(foreignrel);
 		else
 			fdw_scan_tlist = NIL;
@@ -2586,7 +2586,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))
 		{
 			/* Clamp retrieved rows estimates to at most foreignrel->tuples. */
 			retrieved_rows = Min(retrieved_rows, foreignrel->tuples);
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 1a60563..dc2b34b 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"
@@ -36,8 +37,10 @@
 #include "nodes/nodeFuncs.h"
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
+#include "optimizer/cost.h"
 #include "optimizer/planmain.h"
 #include "optimizer/var.h"
+#include "rewrite/rewriteManip.h"
 #include "storage/lmgr.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
@@ -2419,6 +2422,64 @@ make_range_bound(PartitionKey key, List *val, bool inclusive, bool lower)
 }
 
 /*
+ * Return a copy of input BoundCollection structure containg nparts number of
+ * partitions. The data types of bounds are described by given partition key
+ * specificiation.
+ */
+static BoundCollection
+copy_bounds(BoundCollection src_bounds, PartitionKey key, int nparts)
+{
+	BoundCollection dst_bounds;
+	int		i;
+
+	dst_bounds = (BoundCollection) palloc(sizeof(BoundCollectionData));
+
+	if (src_bounds->listinfo)
+	{
+		ListInfo *dst_li = (ListInfo *) palloc(sizeof(ListInfo));
+		ListInfo *src_li = src_bounds->listinfo;
+
+		Assert(!src_bounds->rangeinfo);
+		dst_bounds->rangeinfo = NULL;
+
+		/* Copy the ListInfo structure. */
+		dst_li->nvalues = src_li->nvalues;
+		dst_li->has_null = src_li->has_null;
+		dst_li->null_index = src_li->null_index;
+
+		dst_li->values = (Datum *) palloc(sizeof(Datum) * dst_li->nvalues);
+		dst_li->indexes = (int *) palloc(sizeof(int) * dst_li->nvalues);
+		for (i = 0; i < dst_li->nvalues; i++)
+		{
+			dst_li->values[i] = datumCopy(src_li->values[i],
+													key->tcinfo->typbyval[0],
+													key->tcinfo->typlen[0]);
+			dst_li->indexes[i] = src_li->indexes[i];
+		}
+
+		dst_bounds->listinfo = dst_li;
+	}
+	else
+	{
+		RangeInfo *dst_ri = (RangeInfo *) palloc(sizeof(RangeInfo));
+		RangeInfo *src_ri = src_bounds->rangeinfo;
+
+		Assert(!src_bounds->listinfo && src_bounds->rangeinfo);
+		dst_bounds->listinfo = NULL;
+
+		/* Copy RangeInfo structure. */
+		dst_ri = (RangeInfo *) palloc(sizeof(RangeInfo));
+		dst_ri->ranges = (PartitionRange **) palloc(sizeof(PartitionRange *) * nparts);
+		for (i = 0; i < nparts; i++)
+			dst_ri->ranges[i] = copy_range(src_ri->ranges[i], key);
+
+		dst_bounds->rangeinfo = dst_ri;
+	}
+
+	return dst_bounds;
+}
+
+/*
  * Make and return a copy of input PartitionRange.
  */
 static PartitionRange *
@@ -2654,3 +2715,168 @@ tuple_leftof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound)
 
 	return cmpval < 0;
 }
+
+/*
+ * find_partition_scheme
+ * 		Find the "canonical" partition scheme for the given base table.
+ *
+ * The function searches the list of canonical partition schemes for one that
+ * exactly matches the partitioning properties of the given relation. If it
+ * does not find one, the function creates a canonical partition scheme
+ * structure and adds it to the list.
+ *
+ * For an unpartitioned 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;
+	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;
+
+	/* 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++)
+		{
+			/*
+			 * It suffices to check the OID of support function as it always has
+			 * two arguments and returns boolean. 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->tcinfo->typid[cnt_pks] != part_scheme->key_types[cnt_pks] ||
+				part_key->tcinfo->typmod[cnt_pks] != part_scheme->key_typmods[cnt_pks] ||
+				part_key->tcinfo->typcoll[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->bounds,
+									part_scheme->bounds, nparts))
+			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->bounds = copy_bounds(part_desc->bounds, part_key,
+									  part_scheme->nparts);
+
+	/* Store partition key information. */
+	part_scheme->partnatts = part_key->partnatts;
+
+	part_scheme->partopfamily = (Oid *) palloc(sizeof(Oid) * partnatts);
+	part_scheme->partopcintype = (Oid *) palloc(sizeof(Oid) * partnatts);
+	part_scheme->key_types = (Oid *) palloc(sizeof(Oid) * partnatts);
+	part_scheme->key_typmods = (int32 *) palloc(sizeof(int32) * partnatts);
+	part_scheme->key_collations = (Oid *) palloc(sizeof(Oid) * partnatts);
+
+	for (cnt_pks = 0; cnt_pks < partnatts; cnt_pks++)
+	{
+		part_scheme->partopfamily[cnt_pks] = part_key->partopfamily[cnt_pks];
+		part_scheme->partopcintype[cnt_pks] = part_key->partopcintype[cnt_pks];
+		part_scheme->key_types[cnt_pks] = part_key->tcinfo->typid[cnt_pks];
+		part_scheme->key_typmods[cnt_pks] = part_key->tcinfo->typmod[cnt_pks];
+		part_scheme->key_collations[cnt_pks] = part_key->tcinfo->typcoll[cnt_pks];
+	}
+
+	/* 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 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 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/optimizer/README b/src/backend/optimizer/README
index 775bcc3..f203dc5 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
+and creates join paths for those using the same techniques described above.
+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 and choose the cheapest one 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 99b6bc8..fbacb3c 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,30 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
 	double	   *parent_attrsizes;
 	int			nattrs;
 	ListCell   *l;
+	Oid		   *part_oids = NULL;
+	int			nparts = 0;
+
+	/* Fetch the number of partitions of a partitioned table and their Oids. */
+	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 +928,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
 		Node	   *childqual;
 		ListCell   *parentvars;
 		ListCell   *childvars;
+		int			cnt_parts;
 
 		/* append_rel_list contains all append rels; ignore others */
 		if (appinfo->parent_relid != parentRTindex)
@@ -912,8 +942,101 @@ 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 location 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;
+			}
+		}
+
+		/*
+		 * 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.
+		 * For a partitioned tables, individual partitions can participate in
+		 * the pair-wise joins. We need attr_needed data for buiding 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 do we here.
+		 */
+		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. Partition-wise join technique may join this
+		 * child with a child of another partitioned table, such that this
+		 * child forms the nullable side of the outer join. In such a case, we
+		 * will need the targetlist of this child, even if it's deemed empty.
+		 * Hence set the targetlist before bailing out in case the child is
+		 * proven empty.
+		 *
+		 * 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,
+								   list_make1(appinfo));
+
 		/*
 		 * We have to copy the parent's targetlist and quals to the child,
 		 * with appropriate substitution of variables.  However, only the
@@ -931,7 +1054,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);
+													 list_make1(appinfo));
 		childqual = eval_const_expressions(root, (Node *)
 										   make_ands_explicit(childquals));
 		if (childqual && IsA(childqual, Const) &&
@@ -960,24 +1083,11 @@ 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.
-		 */
+		/* CE failed, so finish copying/modifying join quals. */
 		childrel->joininfo = (List *)
 			adjust_appendrel_attrs(root,
 								   (Node *) rel->joininfo,
-								   appinfo);
-		childrel->reltarget->exprs = (List *)
-			adjust_appendrel_attrs(root,
-								   (Node *) rel->reltarget->exprs,
-								   appinfo);
+								   list_make1(appinfo));
 
 		/*
 		 * We have to make child entries in the EquivalenceClass data
@@ -992,14 +1102,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 +1182,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 +1234,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 +1246,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 +1280,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 +1428,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 +1449,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 +1477,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 +1518,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 +1557,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 +1568,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 +1616,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);
+		}
 	}
 }
 
@@ -2188,6 +2382,10 @@ 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.  Similarly, create append paths for joinrels
+		 * which used partition-wise join technique.  We can not do this
+		 * earlier because the paths can get added to a relation representing
+		 * join between children at multiple times within
 		 * join_search_one_level.  After that, we're done creating paths for
 		 * the joinrel, so run set_cheapest().
 		 */
@@ -2195,6 +2393,9 @@ standard_join_search(PlannerInfo *root, int levels_needed, List *initial_rels)
 		{
 			rel = (RelOptInfo *) lfirst(lc);
 
+			/* Create Append/MergeAppend 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);
 
@@ -2858,6 +3059,160 @@ remove_unused_subquery_outputs(Query *subquery, RelOptInfo *rel)
 	}
 }
 
+/* Fraction of to base cost on. Probably we should turn this into a GUC? */
+#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 for
+ * parent-join, and required child-joins (Otherwise, add_path might delete a
+ * path that some Append/MergeAppend path has a reference to.)
+ */
+void
+generate_partition_wise_join_paths(PlannerInfo *root, RelOptInfo *rel)
+{
+	List   *sampled_children = NIL;
+	List   *sampled_child_nos = NIL;
+	int		cnt_part;
+	int		num_part_to_plan;
+	int		num_parts;
+	bool	partition_join_path = false;
+	int		num_dummy_parts;
+	ListCell   *lc;
+
+	/* Handle only join relations. */
+	if (!IS_JOIN_REL(rel))
+		return;
+
+	/*
+	 * If partition-wise join technique was not used for any of the join
+	 * orders, 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;
+
+	/* Sample the child-joins with higher sizes. */
+	for (cnt_part = 0; cnt_part < num_parts; cnt_part++)
+	{
+		RelOptInfo *child_rel = rel->part_rels[cnt_part];
+		ListCell   *insert_after;
+
+		if (IS_DUMMY_REL(child_rel))
+		{
+			num_dummy_parts++;
+			continue;
+		}
+
+		insert_after = NULL;
+
+		/*
+		 * Add this relation to the list of samples ordered by the increasing
+		 * number of rows at appropriate place.
+		 */
+		foreach (lc, sampled_child_nos)
+		{
+			int	child_no = lfirst_int(lc);
+			RelOptInfo *old_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 <= old_childrel->rows)
+				insert_after = lc;
+			else
+				break;
+		}
+
+		/*
+		 * If we have collected required number of child-joins and current
+		 * child-join has lesser number of rows than all the child-joins
+		 * collected so far, ignore it in this phase.
+		 */
+		if (insert_after == list_tail(sampled_child_nos) &&
+			list_length(sampled_child_nos) == num_part_to_plan)
+			continue;
+
+		if (insert_after)
+			lappend_cell_int(sampled_child_nos, insert_after, cnt_part);
+		else
+			sampled_child_nos = lcons_int(cnt_part, sampled_child_nos);
+
+		/* Trim down list to the required number of children. */
+		if (list_length(sampled_child_nos) > num_part_to_plan)
+		{
+			/*
+			 * List is trimmed every time it grows after adding one child. So,
+			 * it can have at most one extra element.
+			 */
+			Assert(list_length(sampled_child_nos) == num_part_to_plan + 1);
+
+			list_delete_cell(sampled_child_nos,
+							 list_nth_cell(sampled_child_nos,
+										   list_length(sampled_child_nos) - 1),
+							 list_nth_cell(sampled_child_nos,
+										   list_length(sampled_child_nos) - 2));
+		}
+
+		Assert(list_length(sampled_child_nos) <= num_part_to_plan);
+	}
+
+	/* Create paths for all the sampled child-joins. */
+	foreach (lc, sampled_child_nos)
+	{
+		int	child_no = lfirst_int(lc);
+
+		/* Create paths for this child. */
+		add_paths_to_child_joinrel(root, rel, child_no);
+
+#ifdef OPTIMIZER_DEBUG
+		debug_print_rel(root, rel);
+#endif
+		sampled_children = lappend(sampled_children, rel->part_rels[child_no]);
+	}
+
+	/*
+	 * 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 2a49639..a23da1c 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -126,6 +126,7 @@ bool		enable_nestloop = true;
 bool		enable_material = true;
 bool		enable_mergejoin = true;
 bool		enable_hashjoin = true;
+bool		enable_partition_wise_join = true;
 
 typedef struct
 {
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 0e50ad5..fc40b69 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -2062,7 +2062,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
@@ -2366,6 +2366,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;
+	else if (rel->reloptkind == RELOPT_OTHER_JOINREL)
+		relids = rel->top_parent_relids;
 	else
 		relids = rel->relids;
 
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index cc7384f..fae15de 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 *sjinfo = (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, sjinfo->min_righthand) &&
-			!bms_overlap(joinrel->relids, sjinfo->min_lefthand))
+		if (bms_overlap(joinrelids, sjinfo->min_righthand) &&
+			!bms_overlap(joinrelids, sjinfo->min_lefthand))
 			extra.param_source_rels = bms_join(extra.param_source_rels,
 										   bms_difference(root->all_baserels,
 													 sjinfo->min_righthand));
 
 		/* full joins constrain both sides symmetrically */
 		if (sjinfo->jointype == JOIN_FULL &&
-			bms_overlap(joinrel->relids, sjinfo->min_lefthand) &&
-			!bms_overlap(joinrel->relids, sjinfo->min_righthand))
+			bms_overlap(joinrelids, sjinfo->min_lefthand) &&
+			!bms_overlap(joinrelids, sjinfo->min_righthand))
 			extra.param_source_rels = bms_join(extra.param_source_rels,
 										   bms_difference(root->all_baserels,
 													  sjinfo->min_lefthand));
@@ -279,6 +302,24 @@ 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, it can be considered to be
+	 * parameterized by the outer relation. We will be able to 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..b4220eb 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,20 @@ 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,
+										 List *append_rel_infos1,
+										 List *append_rel_infos2);
+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 +742,30 @@ 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 joining given input relations to the given joinrel. 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,462 @@ restriction_is_constant_false(List *restrictlist, bool only_pushed_down)
 	}
 	return false;
 }
+
+/*
+ * 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 in three phases (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. Paths for remaining child-joins are created in the
+ * next phase.
+ *
+ * 3. Create merge/append plan to combining plans for every child-join.
+ *
+ * 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 by 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 child-joins for PartitionJoinPaths
+	 * later.
+	 */
+	partitioned_join = (PartitionedJoin *) palloc(sizeof(PartitionedJoin));
+	partitioned_join->rel1 = rel1;
+	partitioned_join->rel2 = rel2;
+	partitioned_join->restrictlist = parent_restrictlist;
+	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;
+		List	   *join_appinfos;
+		List	   *appinfos1;
+		List	   *appinfos2;
+
+		/* 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;
+
+		/* Get parent-child mapping for translating nodes. */
+		appinfos1 = find_appinfos_by_relids(root, child_rel1->relids);
+		appinfos2 = find_appinfos_by_relids(root, child_rel2->relids);
+		join_appinfos = list_concat(appinfos1, appinfos2);
+
+		/*
+		 * Construct restrictions applicable to the child join from
+		 * those applicable to the parent join.
+		 */
+		child_restrictlist = (List *) adjust_join_appendrel_attrs(root,
+												   (Node *)parent_restrictlist,
+																join_appinfos);
+
+		/*
+		 * Construct SpecialJoinInfo from parent join relations's
+		 * SpecialJoinInfo.
+		 */
+		child_sjinfo = build_child_join_sjinfo(root, parent_sjinfo, appinfos1,
+											   appinfos2);
+
+		/*
+		 * Set estimates of the child-joinrel's size.
+		 */
+		set_joinrel_size_estimates(root, child_joinrel, child_rel1, child_rel2,
+								   child_sjinfo, child_restrictlist);
+
+		/* The list is not needed anymore. */
+		list_free(join_appinfos);
+
+		/*
+		 * 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);
+	}
+}
+
+/*
+ * 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 considering join between
+ * corresponding children of every pair of joining parent relation 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;
+		List	   *join_appinfos;
+		List	   *appinfos1;
+		List	   *appinfos2;
+
+		/*
+		 * 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);
+
+		/* Get parent-child mapping for translating nodes. */
+		appinfos1 = find_appinfos_by_relids(root, child_rel1->relids);
+		appinfos2 = find_appinfos_by_relids(root, child_rel2->relids);
+		join_appinfos = list_concat(appinfos1, appinfos2);
+
+		/*
+		 * Construct SpecialJoinInfo from parent join relations's
+		 * SpecialJoinInfo.
+		 */
+		child_sjinfo = build_child_join_sjinfo(root, pj->sjinfo, appinfos1,
+											   appinfos2);
+
+		/*
+		 * Construct restrictions applicable to the child join from
+		 * those applicable to the parent join.
+		 */
+		child_restrictlist = (List *) adjust_join_appendrel_attrs(root,
+													 (Node *) pj->restrictlist,
+																join_appinfos);
+
+		/* The list is not needed anymore. */
+		list_free(join_appinfos);
+
+		/* 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);
+
+		/*
+		 * In case the child is partitioned, add partition-wise join paths for
+		 * it.
+		 */
+		generate_partition_wise_join_paths(root, child_joinrel);
+	}
+
+	set_cheapest(child_joinrel);
+}
+
+/*
+ * Construct the SpecialJoinInfo for the join between children by translating
+ * SpecialJoinInfo for the join between parents.
+ */
+static SpecialJoinInfo *
+build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo,
+							List *append_rel_infos1, List *append_rel_infos2)
+{
+	SpecialJoinInfo *sjinfo = copyObject(parent_sjinfo);
+
+	sjinfo->min_lefthand = adjust_child_relids(sjinfo->min_lefthand,
+											   append_rel_infos1);
+	sjinfo->min_righthand = adjust_child_relids(sjinfo->min_righthand,
+												append_rel_infos2);
+	sjinfo->syn_lefthand = adjust_child_relids(sjinfo->syn_lefthand,
+											   append_rel_infos1);
+	sjinfo->syn_righthand = adjust_child_relids(sjinfo->syn_righthand,
+												append_rel_infos2);
+
+	/* Replace the Var nodes of parent with those of children in expressions. */
+	sjinfo->semi_rhs_exprs = (List *) adjust_join_appendrel_attrs(root,
+											   (Node *) sjinfo->semi_rhs_exprs,
+															append_rel_infos2);
+	return sjinfo;
+}
+
+/*
+ * Replace parent relids by child relids in the given relid set.
+ */
+Relids
+adjust_child_relids(Relids relids, List *append_rel_infos)
+{
+	ListCell	*lc;
+	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..6996590 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -1088,12 +1088,24 @@ 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 from equivalence classes. A potential join partner of
+	 * parent also indicates potential join partner of the child. By using
+	 * parent relids we eliminate duplicates arising out of many 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 +1145,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 32f4031..d7397b3 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -31,6 +31,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"
@@ -43,6 +44,7 @@
 #include "parser/parse_clause.h"
 #include "parser/parsetree.h"
 #include "utils/lsyscache.h"
+#include "utils/memutils.h"
 
 
 /*
@@ -146,6 +148,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,
@@ -242,7 +247,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);
@@ -368,12 +374,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))
@@ -1514,7 +1516,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);
 
@@ -3524,6 +3526,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
@@ -3531,10 +3535,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 */
@@ -3580,34 +3584,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
@@ -3945,6 +3953,226 @@ create_hashjoin_plan(PlannerInfo *root,
 	return join_plan;
 }
 
+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;
+}
+
+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;
+
+		/* Switch to the child_join context to plan for the child join */
+		old_context = MemoryContextSwitchTo(child_context);
+
+		add_paths_to_child_joinrel(root, joinrel, cnt_parts);
+		child_join = joinrel->part_rels[cnt_parts];
+
+		/* Skip empty child. */
+		if (IS_DUMMY_REL(child_join))
+		{
+			MemoryContextSwitchTo(old_context);
+			MemoryContextResetAndDeleteChildren(child_context);
+
+			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;
+			}
+		}
+
+		/*
+		 * Switch to the original context so that we can copy the plan in the
+		 * same context as rest of the plan tree.
+		 */
+		MemoryContextSwitchTo(old_context);
+		child_plan = copyObject(child_plan);
+
+		child_plans = lappend(child_plans, child_plan);
+
+		/* Reset the child_join memory context to reclaim the memory. */
+		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;
+}
+
 
 /*****************************************************************************
  *
@@ -3971,6 +4199,8 @@ replace_nestloop_params(PlannerInfo *root, Node *expr)
 static Node *
 replace_nestloop_params_mutator(Node *node, PlannerInfo *root)
 {
+	MemoryContext old_context;
+
 	if (node == NULL)
 		return NULL;
 	if (IsA(node, Var))
@@ -3999,10 +4229,13 @@ replace_nestloop_params_mutator(Node *node, PlannerInfo *root)
 			}
 		}
 		/* No, so add it */
+		old_context = MemoryContextSwitchTo(root->planner_cxt);
 		nlp = makeNode(NestLoopParam);
 		nlp->paramno = param->paramid;
 		nlp->paramval = var;
 		root->curOuterParams = lappend(root->curOuterParams, nlp);
+		MemoryContextSwitchTo(old_context);
+
 		/* And return the replacement Param */
 		return (Node *) param;
 	}
@@ -4062,10 +4295,13 @@ replace_nestloop_params_mutator(Node *node, PlannerInfo *root)
 			}
 		}
 		/* No, so add it */
+		old_context = MemoryContextSwitchTo(root->planner_cxt);
 		nlp = makeNode(NestLoopParam);
 		nlp->paramno = param->paramid;
 		nlp->paramval = (Var *) phv;
 		root->curOuterParams = lappend(root->curOuterParams, nlp);
+		MemoryContextSwitchTo(old_context);
+
 		/* And return the replacement Param */
 		return (Node *) param;
 	}
@@ -5337,11 +5573,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;
@@ -5452,10 +5688,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) */
@@ -5476,9 +5712,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;
@@ -5488,7 +5725,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 f657ffc..3c0898a 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1108,7 +1108,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/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 263ba45..6f3270a 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -96,6 +96,7 @@ assign_param_for_var(PlannerInfo *root, Var *var)
 	ListCell   *ppl;
 	PlannerParamItem *pitem;
 	Index		levelsup;
+	MemoryContext	old_context;
 
 	/* Find the query level the Var belongs to */
 	for (levelsup = var->varlevelsup; levelsup > 0; levelsup--)
@@ -124,7 +125,9 @@ assign_param_for_var(PlannerInfo *root, Var *var)
 		}
 	}
 
-	/* Nope, so make a new one */
+
+	/* Nope, so make a new one in the planner context */
+	old_context = MemoryContextSwitchTo(root->planner_cxt);
 	var = (Var *) copyObject(var);
 	var->varlevelsup = 0;
 
@@ -134,6 +137,8 @@ assign_param_for_var(PlannerInfo *root, Var *var)
 
 	root->plan_params = lappend(root->plan_params, pitem);
 
+	MemoryContextSwitchTo(old_context);
+
 	return pitem->paramId;
 }
 
@@ -204,6 +209,7 @@ assign_param_for_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv)
 	ListCell   *ppl;
 	PlannerParamItem *pitem;
 	Index		levelsup;
+	MemoryContext old_context;
 
 	/* Find the query level the PHV belongs to */
 	for (levelsup = phv->phlevelsup; levelsup > 0; levelsup--)
@@ -223,7 +229,8 @@ assign_param_for_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv)
 		}
 	}
 
-	/* Nope, so make a new one */
+	/* Nope, so make a new one in the planner's context. */
+	old_context = MemoryContextSwitchTo(root->planner_cxt);
 	phv = (PlaceHolderVar *) copyObject(phv);
 	if (phv->phlevelsup != 0)
 	{
@@ -237,6 +244,8 @@ assign_param_for_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv)
 
 	root->plan_params = lappend(root->plan_params, pitem);
 
+	MemoryContextSwitchTo(old_context);
+
 	return pitem->paramId;
 }
 
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 193b2c9..1ce142e 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);
 static List *expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
@@ -1828,10 +1827,11 @@ 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
@@ -1841,15 +1841,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
@@ -1858,11 +1861,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;
@@ -1880,17 +1892,73 @@ 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;
+}
+
+/*
+ * adjust_join_appendrel_attrs
+ *
+ *	    Replace the parent references in the given node by the child references
+ *	    specified by the list of AppendRelInfo.
+ *
+ * This function is a wrapper around adjust_appendrel_attrs() which handles
+ * only one AppendRelInfo at a time.
+ */
+
+Node *
+adjust_join_appendrel_attrs(PlannerInfo *root, Node *node,
+						  List *append_rel_infos)
+{
+	return adjust_appendrel_attrs(root, node, append_rel_infos);
+}
+
 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;
+
+		/*
+		 * Find an appinfo, parent in which matches the Var. If none found, set
+		 * it to the last one. Rest of the code takes care of non-matching
+		 * appinfos.
+		 */
+		foreach (lc, appinfos)
+		{
+			appinfo = lfirst(lc);
+
+			if (var->varno == appinfo->parent_relid)
+				break;
+		}
 
 		if (var->varlevelsup == context->sublevels_up &&
 			var->varno == appinfo->parent_relid)
@@ -1981,32 +2049,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))
@@ -2019,9 +2113,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(bms_copy(phv->phrels),
+											  context->appinfos);
 		return (Node *) phv;
 	}
 	/* Shouldn't need to handle planner auxiliary nodes here */
@@ -2052,24 +2145,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(bms_copy(oldinfo->clause_relids),
+													 context->appinfos);
+		newinfo->required_relids = adjust_child_relids(bms_copy(oldinfo->required_relids),
+													 context->appinfos);
+		newinfo->outer_relids = adjust_child_relids(bms_copy(oldinfo->outer_relids),
+													 context->appinfos);
+		newinfo->nullable_relids = adjust_child_relids(bms_copy(oldinfo->nullable_relids),
+													   context->appinfos);
+		newinfo->left_relids = adjust_child_relids(bms_copy(oldinfo->left_relids),
+												   context->appinfos);
+		newinfo->right_relids = adjust_child_relids(bms_copy(oldinfo->right_relids),
+													context->appinfos);
 
 		/*
 		 * Reset cached derivative fields, since these might need to have
@@ -2118,23 +2205,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
@@ -2251,5 +2321,5 @@ 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));
 }
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index abb7507..8510775 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,162 @@ 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 costs of the list of paths for
+ * representative child-joins.
+ */
+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;
+
+	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 costs of the list of paths for
+ * representative child-joins.
+ */
+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;
+
+	return pathnode;
+}
+/*
  * create_projection_path
  *	  Creates a pathnode that represents performing a projection.
  *
@@ -3209,3 +3367,158 @@ 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;
+
+	/*
+	 * If the path is not parameterized by parent of the given relation, 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;
+	new_ppi = makeNode(ParamPathInfo);
+	new_ppi->ppi_req_outer = adjust_child_relids(bms_copy(old_ppi->ppi_req_outer),
+													 child_aris);
+	new_ppi->ppi_rows = old_ppi->ppi_rows;
+	new_ppi->ppi_clauses = (List *) adjust_join_appendrel_attrs(root,
+													 (Node *) old_ppi->ppi_clauses,
+													 child_aris);
+
+	/* Adjust the path target. */
+	new_path->pathtarget = copy_pathtarget(new_path->pathtarget);
+	new_path->pathtarget->exprs = (List *) adjust_join_appendrel_attrs(root,
+													(Node *) new_path->pathtarget->exprs,
+													 child_aris);
+	new_path->param_info = new_ppi;
+
+	/*
+	 * 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 = (List *) adjust_join_appendrel_attrs(root,
+															(Node *) 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->indexquals = (List *) adjust_join_appendrel_attrs(root,
+														(Node *) ipath->indexquals,
+														child_aris);
+				ipath->indexquals = (List *) adjust_join_appendrel_attrs(root,
+														(Node *) ipath->indexorderbys,
+														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..98e95e0 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_join_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 f8bfa4b..a0e17d5 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -411,6 +411,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);
 
+	/*
+	 * 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 deef560..be08b6e 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
@@ -47,6 +54,11 @@ 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);
 
 
 /*
@@ -137,6 +149,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 +330,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 +429,11 @@ build_join_rel(PlannerInfo *root,
 	 * Nope, so make one.
 	 */
 	joinrel = makeNode(RelOptInfo);
+
+	Assert(!IS_OTHER_REL(outer_rel->reloptkind) &&
+		   !IS_OTHER_REL(inner_rel->reloptkind));
 	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 +479,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 +511,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 +557,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 +578,132 @@ 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);
+	List	   *join_appinfos;
+
+	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->reloptkind) &&
+		   IS_OTHER_REL(inner_rel->reloptkind));
+
+	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);
+
+	/* Translate joininfo. */
+	join_appinfos = find_appinfos_by_relids(root, joinrel->relids);
+	joinrel->joininfo = (List *) adjust_join_appendrel_attrs(root,
+											 (Node *) parent_joinrel->joininfo,
+																join_appinfos);
+
+	/*
+	 * 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);
+
+	pfree(join_appinfos);
+
+	return joinrel;
+}
+
+/*
  * min_join_parameterization
  *
  * Determine the minimum possible parameterization of a joinrel, that is, the
@@ -609,9 +758,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 +782,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];
 		}
 	}
@@ -1320,3 +1499,111 @@ 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;
+	}
+}
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 56943f2..16b2eac 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -3412,7 +3412,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 ce4eef9..edc7e58 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -877,6 +877,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,
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 81a4b91..19b7744 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -46,8 +46,43 @@ typedef struct PartitionDescData
 	BoundCollection		bounds;		/* collection of list or range bounds */
 } PartitionDescData;
 
+/*
+ * 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 */
+	BoundCollection bounds; /* 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 PartitionDescData *PartitionDesc;
 typedef struct PartitionTreeNodeData *PartitionTreeNode;
+typedef struct PartitionSchemeData *PartitionScheme;
+
+/* Include here to avoid circular dependency with relation.h. */
+struct PlannerInfo;
 
 /* relcache support for partition key information */
 extern void RelationBuildPartitionKey(Relation relation);
@@ -84,4 +119,9 @@ extern int get_partition_for_tuple(PartitionTreeNode ptnode,
 					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 2f9e7d3..94bce51 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..18f1e5c 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(reloptkind) \
+	((reloptkind) == RELOPT_OTHER_MEMBER_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.
@@ -1785,6 +1832,20 @@ 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;
+	List		   *restrictlist;
+} 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..1069726 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -66,6 +66,7 @@ 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 clamp_row_est(double nrows);
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 71d9154..44dc801 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
@@ -267,5 +274,10 @@ 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 void add_join_rel(PlannerInfo *root, RelOptInfo *joinrel);
 
 #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..58df2f4 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,7 +61,7 @@ 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);
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..2fcf779
--- /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 START (0) END (250) PARTITION BY RANGE (b);
+CREATE TABLE prt1_l_p1_p1 PARTITION OF prt1_l_p1 FOR VALUES START (0) END (100);
+CREATE TABLE prt1_l_p1_p2 PARTITION OF prt1_l_p1 FOR VALUES START (100) END (250);
+CREATE TABLE prt1_l_p2 PARTITION OF prt1_l FOR VALUES START (250) END (500) PARTITION BY RANGE (c);
+CREATE TABLE prt1_l_p2_p1 PARTITION OF prt1_l_p2 FOR VALUES START ('0250') END ('0400');
+CREATE TABLE prt1_l_p2_p2 PARTITION OF prt1_l_p2 FOR VALUES START ('0400') END ('0500');
+CREATE TABLE prt1_l_p3 PARTITION OF prt1_l FOR VALUES START (500) END (600) PARTITION BY RANGE ((b + a));
+CREATE TABLE prt1_l_p3_p1 PARTITION OF prt1_l_p3 FOR VALUES START (1000) END (1100);
+CREATE TABLE prt1_l_p3_p2 PARTITION OF prt1_l_p3 FOR VALUES START (1100) END (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 START (0) END (250) PARTITION BY RANGE (a);
+CREATE TABLE prt2_l_p1_p1 PARTITION OF prt2_l_p1 FOR VALUES START (0) END (100);
+CREATE TABLE prt2_l_p1_p2 PARTITION OF prt2_l_p1 FOR VALUES START (100) END (250);
+CREATE TABLE prt2_l_p2 PARTITION OF prt2_l FOR VALUES START (250) END (500) PARTITION BY RANGE (c);
+CREATE TABLE prt2_l_p2_p1 PARTITION OF prt2_l_p2 FOR VALUES START ('0250') END ('0400');
+CREATE TABLE prt2_l_p2_p2 PARTITION OF prt2_l_p2 FOR VALUES START ('0400') END ('0500');
+CREATE TABLE prt2_l_p3 PARTITION OF prt2_l FOR VALUES START (500) END (600) PARTITION BY RANGE ((a + b));
+CREATE TABLE prt2_l_p3_p1 PARTITION OF prt2_l_p3 FOR VALUES START (1000) END (1100);
+CREATE TABLE prt2_l_p3_p2 PARTITION OF prt2_l_p3 FOR VALUES START (1100) END (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..eb2015c
--- /dev/null
+++ b/src/test/regress/expected/partition_join.out
@@ -0,0 +1,5043 @@
+--
+-- PARTITION_JOIN
+-- Test partition-wise join between partitioned tables
+--
+--
+-- 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 START (0) END (250);
+CREATE TABLE prt1_p3 PARTITION OF prt1 FOR VALUES START (500) END (600);
+CREATE TABLE prt1_p2 PARTITION OF prt1 FOR VALUES START (250) END (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 START (0) END (250);
+CREATE TABLE prt2_p2 PARTITION OF prt2 FOR VALUES START (250) END (500);
+CREATE TABLE prt2_p3 PARTITION OF prt2 FOR VALUES START (500) END (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                        
+---------------------------------------------------------
+ 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.a = t1_3.b)
+               ->  Seq Scan on public.prt1_p1 t1
+                     Output: t1.a, t1.b, t1.c
+                     Filter: ((t1.a % 25) = 0)
+               ->  Materialize
+                     Output: t1_3.b
+                     ->  Seq Scan on public.prt2_p1 t1_3
+                           Output: t1_3.b
+                           Filter: ((t1_3.b % 25) = 0)
+         ->  Nested Loop Semi Join
+               Output: t1_2.a, t1_2.b, t1_2.c
+               Join Filter: (t1_2.a = t1_4.b)
+               ->  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
+                     ->  Seq Scan on public.prt2_p2 t1_4
+                           Output: t1_4.b
+                           Filter: ((t1_4.b % 25) = 0)
+         ->  Nested Loop Semi Join
+               Output: t1_1.a, t1_1.b, t1_1.c
+               Join Filter: (t1_1.a = t1_5.b)
+               ->  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
+                     ->  Seq Scan on public.prt2_p3 t1_5
+                           Output: t1_5.b
+                           Filter: ((t1_5.b % 25) = 0)
+(37 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 START (0) END (250);
+CREATE TABLE prt1_e_p2 PARTITION OF prt1_e FOR VALUES START (250) END (500);
+CREATE TABLE prt1_e_p3 PARTITION OF prt1_e FOR VALUES START (500) END (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 START (0) END (250);
+CREATE TABLE prt2_e_p2 PARTITION OF prt2_e FOR VALUES START (250) END (500);
+CREATE TABLE prt2_e_p3 PARTITION OF prt2_e FOR VALUES START (500) END (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                                
+-------------------------------------------------------------------------
+ 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.a = t1_3.b)
+               ->  Seq Scan on public.prt1_p1 t1
+                     Output: t1.a, t1.b, t1.c
+                     Filter: ((t1.a % 25) = 0)
+               ->  Materialize
+                     Output: t1_3.b, t2.a, t2.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)
+         ->  Nested Loop Semi Join
+               Output: t1_2.a, t1_2.b, t1_2.c
+               Join Filter: (t1_2.a = t1_4.b)
+               ->  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, t2_1.a, t2_1.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)
+         ->  Nested Loop Semi Join
+               Output: t1_1.a, t1_1.b, t1_1.c
+               Join Filter: (t1_1.a = t1_5.b)
+               ->  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, t2_2.a, t2_2.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)
+(58 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                                
+-------------------------------------------------------------------------
+ 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.a = t1_3.b)
+               ->  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
+                     ->  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)
+         ->  Nested Loop Semi Join
+               Output: t1_2.a, t1_2.b, t1_2.c
+               Join Filter: (t1_2.a = t1_4.b)
+               ->  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
+                     ->  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)
+         ->  Nested Loop Semi Join
+               Output: t1_1.a, t1_1.b, t1_1.c
+               Join Filter: (t1_1.a = t1_5.b)
+               ->  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
+                     ->  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)
+(58 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_a on prt1(a);
+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_b on prt2(b);
+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_ab2 on prt1_e(((a+b)/2));
+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                                 
+---------------------------------------------------------------------------
+ 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
+         ->  Index Scan using iprt1_a 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.a)
+               ->  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.a)
+               ->  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.a)
+               ->  Index Scan using iprt2_p3_b on public.prt2_p3 t3_2
+                     Output: t3_2.a, t3_2.b
+(41 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
+         ->  Index Scan using iprt1_a 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
+(41 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 START (0, 0) END (250, 250);
+CREATE TABLE prt1_m_p2 PARTITION OF prt1_m FOR VALUES START (250, 250) END (500, 500);
+CREATE TABLE prt1_m_p3 PARTITION OF prt1_m FOR VALUES START (500, 500) END (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 START (0, 0) END (250, 250);
+CREATE TABLE prt2_m_p2 PARTITION OF prt2_m FOR VALUES START (250, 250) END (500, 500);
+CREATE TABLE prt2_m_p3 PARTITION OF prt2_m FOR VALUES START (500, 500) END (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;
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM plt1 t1, plt2 t2 WHERE t1.c = t2.c AND t1.a = t2.a 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.c = t1.c) AND (t2.a = t1.a))
+               ->  Seq Scan on public.plt2_p1 t2
+                     Output: t2.b, t2.c, t2.a
+               ->  Hash
+                     Output: t1.a, t1.c
+                     ->  Seq Scan on public.plt1_p1 t1
+                           Output: t1.a, t1.c
+                           Filter: ((t1.a % 25) = 0)
+         ->  Hash Join
+               Output: t1_1.a, t1_1.c, t2_1.b, t2_1.c
+               Hash Cond: ((t2_1.c = t1_1.c) AND (t2_1.a = t1_1.a))
+               ->  Seq Scan on public.plt2_p2 t2_1
+                     Output: t2_1.b, t2_1.c, t2_1.a
+               ->  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 Join
+               Output: t1_2.a, t1_2.c, t2_2.b, t2_2.c
+               Hash Cond: ((t2_2.c = t1_2.c) AND (t2_2.a = t1_2.a))
+               ->  Seq Scan on public.plt2_p3 t2_2
+                     Output: t2_2.b, t2_2.c, t2_2.a
+               ->  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)
+(34 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM plt1 t1, plt2 t2 WHERE t1.c = t2.c AND t1.a = t2.a AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+ 150 | 0003 | 150 | 0003
+ 300 | 0006 | 300 | 0006
+ 450 | 0009 | 450 | 0009
+(4 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM uplt1 t1, uplt2 t2 WHERE t1.c = t2.c AND t1.a = t2.a AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+ 150 | 0003 | 150 | 0003
+ 300 | 0006 | 300 | 0006
+ 450 | 0009 | 450 | 0009
+(4 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM plt1 t1 LEFT JOIN plt2 t2 ON t1.a = t2.a AND t1.c = t2.c 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.a = t1.a) AND (t2.c = t1.c))
+               ->  Seq Scan on public.plt2_p1 t2
+                     Output: t2.b, t2.c, t2.a
+               ->  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
+               Hash Cond: ((t2_1.a = 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, t2_1.a
+               ->  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
+               Hash Cond: ((t2_2.a = 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, t2_2.a
+               ->  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)
+(34 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM plt1 t1 LEFT JOIN plt2 t2 ON t1.a = t2.a AND t1.c = t2.c WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+  50 | 0001 |     | 
+ 100 | 0002 |     | 
+ 150 | 0003 | 150 | 0003
+ 200 | 0004 |     | 
+ 250 | 0005 |     | 
+ 300 | 0006 | 300 | 0006
+ 350 | 0007 |     | 
+ 400 | 0008 |     | 
+ 450 | 0009 | 450 | 0009
+ 500 | 0010 |     | 
+ 550 | 0011 |     | 
+(12 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM uplt1 t1 LEFT JOIN uplt2 t2 ON t1.a = t2.a AND t1.c = t2.c WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+  50 | 0001 |     | 
+ 100 | 0002 |     | 
+ 150 | 0003 | 150 | 0003
+ 200 | 0004 |     | 
+ 250 | 0005 |     | 
+ 300 | 0006 | 300 | 0006
+ 350 | 0007 |     | 
+ 400 | 0008 |     | 
+ 450 | 0009 | 450 | 0009
+ 500 | 0010 |     | 
+ 550 | 0011 |     | 
+(12 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM plt1 t1 RIGHT JOIN plt2 t2 ON t1.a = t2.b AND t1.c = t2.c 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.c = t2.c))
+                     ->  Seq Scan on public.plt1_p1 t1
+                           Output: t1.a, t1.c
+                     ->  Hash
+                           Output: t2.b, t2.c
+                           ->  Seq Scan on public.plt2_p1 t2
+                                 Output: t2.b, t2.c
+                                 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.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
+                           ->  Seq Scan on public.plt2_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_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: t2_2.b, t2_2.c
+                           ->  Seq Scan on public.plt2_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 plt1 t1 RIGHT JOIN plt2 t2 ON t1.a = t2.b AND t1.c = t2.c WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+ 150 | 0003 | 150 | 0003
+ 300 | 0006 | 300 | 0006
+ 450 | 0009 | 450 | 0009
+     |      |  75 | 0001
+     |      | 225 | 0004
+     |      | 375 | 0007
+     |      | 525 | 0010
+(8 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM uplt1 t1 RIGHT JOIN uplt2 t2 ON t1.a = t2.b AND t1.c = t2.c WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+ 150 | 0003 | 150 | 0003
+ 300 | 0006 | 300 | 0006
+ 450 | 0009 | 450 | 0009
+     |      |  75 | 0001
+     |      | 225 | 0004
+     |      | 375 | 0007
+     |      | 525 | 0010
+(8 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.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) ORDER BY t1.a, t2.b;
+                                   QUERY PLAN                                   
+--------------------------------------------------------------------------------
+ Sort
+   Output: plt1_p1.a, plt1_p1.c, plt2_p1.b, plt2_p1.c
+   Sort Key: plt1_p1.a, plt2_p1.b
+   ->  Append
+         ->  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 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 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)
+(37 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.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) ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+  50 | 0001 |     | 
+ 100 | 0002 |     | 
+ 150 | 0003 | 150 | 0003
+ 200 | 0004 |     | 
+ 250 | 0005 |     | 
+ 300 | 0006 | 300 | 0006
+ 350 | 0007 |     | 
+ 400 | 0008 |     | 
+ 450 | 0009 | 450 | 0009
+ 500 | 0010 |     | 
+ 550 | 0011 |     | 
+     |      |  75 | 0001
+     |      | 225 | 0004
+     |      | 375 | 0007
+     |      | 525 | 0010
+(16 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM uplt1 t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM uplt2 t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b AND t1.c = t2.c) ORDER BY t1.a, t2.b;
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+  50 | 0001 |     | 
+ 100 | 0002 |     | 
+ 150 | 0003 | 150 | 0003
+ 200 | 0004 |     | 
+ 250 | 0005 |     | 
+ 300 | 0006 | 300 | 0006
+ 350 | 0007 |     | 
+ 400 | 0008 |     | 
+ 450 | 0009 | 450 | 0009
+ 500 | 0010 |     | 
+ 550 | 0011 |     | 
+     |      |  75 | 0001
+     |      | 225 | 0004
+     |      | 375 | 0007
+     |      | 525 | 0010
+(16 rows)
+
+-- Cases with non-nullable expressions in subquery results;
+-- make sure these go to null as expected
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT sum(t1.a), t1.c, avg(t2.b), t2.c FROM (SELECT 50 phv, * FROM plt1 WHERE plt1.a % 25 = 0) t1 FULL JOIN (SELECT 75 phv, * FROM plt2 WHERE plt2.b % 25 = 0) t2 ON (t1.c = t2.c AND t1.a = t2.b) GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+                                         QUERY PLAN                                         
+--------------------------------------------------------------------------------------------
+ GroupAggregate
+   Output: sum(plt1_p1.a), plt1_p1.c, avg(plt2_p1.b), plt2_p1.c
+   Group Key: plt1_p1.c, plt2_p1.c
+   ->  Sort
+         Output: plt1_p1.c, plt2_p1.c, plt1_p1.a, plt2_p1.b
+         Sort Key: plt1_p1.c, plt2_p1.c
+         ->  Result
+               Output: plt1_p1.c, plt2_p1.c, plt1_p1.a, plt2_p1.b
+               ->  Append
+                     ->  Hash Full Join
+                           Output: plt1_p1.a, plt1_p1.c, plt2_p1.b, plt2_p1.c
+                           Hash Cond: ((plt1_p1.c = plt2_p1.c) AND (plt1_p1.a = plt2_p1.b))
+                           ->  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 Full Join
+                           Output: plt1_p2.a, plt1_p2.c, plt2_p2.b, plt2_p2.c
+                           Hash Cond: ((plt1_p2.c = plt2_p2.c) AND (plt1_p2.a = plt2_p2.b))
+                           ->  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 Full Join
+                           Output: plt1_p3.a, plt1_p3.c, plt2_p3.b, plt2_p3.c
+                           Hash Cond: ((plt1_p3.c = plt2_p3.c) AND (plt1_p3.a = plt2_p3.b))
+                           ->  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)
+(42 rows)
+
+SELECT sum(t1.a), t1.c, avg(t2.b), t2.c FROM (SELECT 50 phv, * FROM plt1 WHERE plt1.a % 25 = 0) t1 FULL JOIN (SELECT 75 phv, * FROM plt2 WHERE plt2.b % 25 = 0) t2 ON (t1.c = t2.c AND t1.a = t2.b) GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+ sum |  c   |          avg           |  c   
+-----+------+------------------------+------
+   0 | 0000 | 0.00000000000000000000 | 0000
+  50 | 0001 |                        | 
+ 100 | 0002 |                        | 
+ 150 | 0003 |   150.0000000000000000 | 0003
+ 200 | 0004 |                        | 
+ 250 | 0005 |                        | 
+ 300 | 0006 |   300.0000000000000000 | 0006
+ 350 | 0007 |                        | 
+ 400 | 0008 |                        | 
+ 450 | 0009 |   450.0000000000000000 | 0009
+ 500 | 0010 |                        | 
+ 550 | 0011 |                        | 
+     |      |    75.0000000000000000 | 0001
+     |      |   225.0000000000000000 | 0004
+     |      |   375.0000000000000000 | 0007
+     |      |   525.0000000000000000 | 0010
+(16 rows)
+
+SELECT sum(t1.a), t1.c, avg(t2.b), t2.c FROM (SELECT 50 phv, * FROM uplt1 WHERE uplt1.a % 25 = 0) t1 FULL JOIN (SELECT 75 phv, * FROM uplt2 WHERE uplt2.b % 25 = 0) t2 ON (t1.c = t2.c AND t1.a = t2.b) GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+ sum |  c   |          avg           |  c   
+-----+------+------------------------+------
+   0 | 0000 | 0.00000000000000000000 | 0000
+  50 | 0001 |                        | 
+ 100 | 0002 |                        | 
+ 150 | 0003 |   150.0000000000000000 | 0003
+ 200 | 0004 |                        | 
+ 250 | 0005 |                        | 
+ 300 | 0006 |   300.0000000000000000 | 0006
+ 350 | 0007 |                        | 
+ 400 | 0008 |                        | 
+ 450 | 0009 |   450.0000000000000000 | 0009
+ 500 | 0010 |                        | 
+ 550 | 0011 |                        | 
+     |      |    75.0000000000000000 | 0001
+     |      |   225.0000000000000000 | 0004
+     |      |   375.0000000000000000 | 0007
+     |      |   525.0000000000000000 | 0010
+(16 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT sum(t1.a), t1.c, sum(t1.phv), avg(t2.b), t2.c, avg(t2.phv) FROM (SELECT 25 phv, * FROM plt1 WHERE plt1.a % 25 = 0) t1 FULL JOIN (SELECT 50 phv, * FROM plt2 WHERE plt2.b % 25 = 0) t2 ON (t1.c = t2.c AND t1.a = t2.b) GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+                                         QUERY PLAN                                         
+--------------------------------------------------------------------------------------------
+ GroupAggregate
+   Output: sum(plt1_p1.a), plt1_p1.c, sum((25)), avg(plt2_p1.b), plt2_p1.c, avg((50))
+   Group Key: plt1_p1.c, plt2_p1.c
+   ->  Sort
+         Output: plt1_p1.c, plt2_p1.c, plt1_p1.a, (25), plt2_p1.b, (50)
+         Sort Key: plt1_p1.c, plt2_p1.c
+         ->  Result
+               Output: plt1_p1.c, plt2_p1.c, plt1_p1.a, (25), plt2_p1.b, (50)
+               ->  Append
+                     ->  Hash Full Join
+                           Output: plt1_p1.a, plt1_p1.c, plt2_p1.b, plt2_p1.c, (25), (50)
+                           Hash Cond: ((plt1_p1.c = plt2_p1.c) AND (plt1_p1.a = plt2_p1.b))
+                           ->  Seq Scan on public.plt1_p1
+                                 Output: plt1_p1.a, plt1_p1.c, 25
+                                 Filter: ((plt1_p1.a % 25) = 0)
+                           ->  Hash
+                                 Output: plt2_p1.b, plt2_p1.c, (50)
+                                 ->  Seq Scan on public.plt2_p1
+                                       Output: plt2_p1.b, plt2_p1.c, 50
+                                       Filter: ((plt2_p1.b % 25) = 0)
+                     ->  Hash Full Join
+                           Output: plt1_p2.a, plt1_p2.c, plt2_p2.b, plt2_p2.c, (25), (50)
+                           Hash Cond: ((plt1_p2.c = plt2_p2.c) AND (plt1_p2.a = plt2_p2.b))
+                           ->  Seq Scan on public.plt1_p2
+                                 Output: plt1_p2.a, plt1_p2.c, 25
+                                 Filter: ((plt1_p2.a % 25) = 0)
+                           ->  Hash
+                                 Output: plt2_p2.b, plt2_p2.c, (50)
+                                 ->  Seq Scan on public.plt2_p2
+                                       Output: plt2_p2.b, plt2_p2.c, 50
+                                       Filter: ((plt2_p2.b % 25) = 0)
+                     ->  Hash Full Join
+                           Output: plt1_p3.a, plt1_p3.c, plt2_p3.b, plt2_p3.c, (25), (50)
+                           Hash Cond: ((plt1_p3.c = plt2_p3.c) AND (plt1_p3.a = plt2_p3.b))
+                           ->  Seq Scan on public.plt1_p3
+                                 Output: plt1_p3.a, plt1_p3.c, 25
+                                 Filter: ((plt1_p3.a % 25) = 0)
+                           ->  Hash
+                                 Output: plt2_p3.b, plt2_p3.c, (50)
+                                 ->  Seq Scan on public.plt2_p3
+                                       Output: plt2_p3.b, plt2_p3.c, 50
+                                       Filter: ((plt2_p3.b % 25) = 0)
+(42 rows)
+
+SELECT sum(t1.a), t1.c, sum(t1.phv), avg(t2.b), t2.c, avg(t2.phv) FROM (SELECT 25 phv, * FROM plt1 WHERE plt1.a % 25 = 0) t1 FULL JOIN (SELECT 50 phv, * FROM plt2 WHERE plt2.b % 25 = 0) t2 ON (t1.c = t2.c AND t1.a = t2.b) GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+ sum |  c   | sum |          avg           |  c   |         avg         
+-----+------+-----+------------------------+------+---------------------
+   0 | 0000 |  25 | 0.00000000000000000000 | 0000 | 50.0000000000000000
+  50 | 0001 |  25 |                        |      |                    
+ 100 | 0002 |  25 |                        |      |                    
+ 150 | 0003 |  25 |   150.0000000000000000 | 0003 | 50.0000000000000000
+ 200 | 0004 |  25 |                        |      |                    
+ 250 | 0005 |  25 |                        |      |                    
+ 300 | 0006 |  25 |   300.0000000000000000 | 0006 | 50.0000000000000000
+ 350 | 0007 |  25 |                        |      |                    
+ 400 | 0008 |  25 |                        |      |                    
+ 450 | 0009 |  25 |   450.0000000000000000 | 0009 | 50.0000000000000000
+ 500 | 0010 |  25 |                        |      |                    
+ 550 | 0011 |  25 |                        |      |                    
+     |      |     |    75.0000000000000000 | 0001 | 50.0000000000000000
+     |      |     |   225.0000000000000000 | 0004 | 50.0000000000000000
+     |      |     |   375.0000000000000000 | 0007 | 50.0000000000000000
+     |      |     |   525.0000000000000000 | 0010 | 50.0000000000000000
+(16 rows)
+
+SELECT sum(t1.a), t1.c, sum(t1.phv), avg(t2.b), t2.c, avg(t2.phv) FROM (SELECT 25 phv, * FROM uplt1 WHERE uplt1.a % 25 = 0) t1 FULL JOIN (SELECT 50 phv, * FROM uplt2 WHERE uplt2.b % 25 = 0) t2 ON (t1.c = t2.c AND t1.a = t2.b) GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+ sum |  c   | sum |          avg           |  c   |         avg         
+-----+------+-----+------------------------+------+---------------------
+   0 | 0000 |  25 | 0.00000000000000000000 | 0000 | 50.0000000000000000
+  50 | 0001 |  25 |                        |      |                    
+ 100 | 0002 |  25 |                        |      |                    
+ 150 | 0003 |  25 |   150.0000000000000000 | 0003 | 50.0000000000000000
+ 200 | 0004 |  25 |                        |      |                    
+ 250 | 0005 |  25 |                        |      |                    
+ 300 | 0006 |  25 |   300.0000000000000000 | 0006 | 50.0000000000000000
+ 350 | 0007 |  25 |                        |      |                    
+ 400 | 0008 |  25 |                        |      |                    
+ 450 | 0009 |  25 |   450.0000000000000000 | 0009 | 50.0000000000000000
+ 500 | 0010 |  25 |                        |      |                    
+ 550 | 0011 |  25 |                        |      |                    
+     |      |     |    75.0000000000000000 | 0001 | 50.0000000000000000
+     |      |     |   225.0000000000000000 | 0004 | 50.0000000000000000
+     |      |     |   375.0000000000000000 | 0007 | 50.0000000000000000
+     |      |     |   525.0000000000000000 | 0010 | 50.0000000000000000
+(16 rows)
+
+-- Join with pruned partitions from joining relations
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT sum(t1.a), t1.c, avg(t2.b), t2.c FROM plt1 t1, plt2 t2 WHERE t1.c = t2.c AND t1.c NOT IN ('0001', '0005', '0002', '0009') AND t2.c NOT IN ('0000', '0003', '0004', '0010') GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+                                          QUERY PLAN                                           
+-----------------------------------------------------------------------------------------------
+ Sort
+   Output: (sum(t1.a)), t1.c, (avg(t2.b)), t2.c
+   Sort Key: t1.c
+   ->  HashAggregate
+         Output: sum(t1.a), t1.c, avg(t2.b), t2.c
+         Group Key: t1.c, t2.c
+         ->  Result
+               Output: t1.c, t2.c, t1.a, t2.b
+               ->  Append
+                     ->  Hash Join
+                           Output: t1.a, t1.c, t2.b, t2.c
+                           Hash Cond: (t1.c = t2.c)
+                           ->  Seq Scan on public.plt1_p3 t1
+                                 Output: t1.a, t1.c
+                                 Filter: (t1.c <> ALL ('{0001,0005,0002,0009}'::text[]))
+                           ->  Hash
+                                 Output: t2.b, t2.c
+                                 ->  Seq Scan on public.plt2_p3 t2
+                                       Output: t2.b, t2.c
+                                       Filter: (t2.c <> ALL ('{0000,0003,0004,0010}'::text[]))
+(20 rows)
+
+SELECT sum(t1.a), t1.c, avg(t2.b), t2.c FROM plt1 t1, plt2 t2 WHERE t1.c = t2.c AND t1.c NOT IN ('0001', '0005', '0002', '0009') AND t2.c NOT IN ('0000', '0003', '0004', '0010') GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+  sum   |  c   |         avg          |  c   
+--------+------+----------------------+------
+ 137700 | 0006 | 324.0000000000000000 | 0006
+ 158950 | 0007 | 375.0000000000000000 | 0007
+ 169600 | 0008 | 424.5000000000000000 | 0008
+ 229600 | 0011 | 574.5000000000000000 | 0011
+(4 rows)
+
+SELECT sum(t1.a), t1.c, avg(t2.b), t2.c FROM uplt1 t1, uplt2 t2 WHERE t1.c = t2.c AND t1.c NOT IN ('0001', '0005', '0002', '0009') AND t2.c NOT IN ('0000', '0003', '0004', '0010') GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+  sum   |  c   |         avg          |  c   
+--------+------+----------------------+------
+ 137700 | 0006 | 324.0000000000000000 | 0006
+ 158950 | 0007 | 375.0000000000000000 | 0007
+ 169600 | 0008 | 424.5000000000000000 | 0008
+ 229600 | 0011 | 574.5000000000000000 | 0011
+(4 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT sum(t1.a), t1.c, sum(t2.b), t2.c FROM (SELECT * FROM plt1 t1 WHERE t1.c NOT IN ('0001', '0005', '0002', '0009')) t1 LEFT JOIN (SELECT * FROM plt2 t2 WHERE t2.c NOT IN ('0000', '0003', '0004', '0010')) t2 ON t1.c = t2.c GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+                                          QUERY PLAN                                           
+-----------------------------------------------------------------------------------------------
+ Sort
+   Output: (sum(t1.a)), t1.c, (sum(b)), c
+   Sort Key: t1.c, c
+   ->  HashAggregate
+         Output: sum(t1.a), t1.c, sum(b), c
+         Group Key: t1.c, c
+         ->  Result
+               Output: t1.c, c, t1.a, b
+               ->  Append
+                     ->  Hash Left Join
+                           Output: t1.a, t1.c, b, c
+                           Hash Cond: (t1.c = c)
+                           ->  Seq Scan on public.plt1_p1 t1
+                                 Output: t1.a, t1.c
+                                 Filter: (t1.c <> ALL ('{0001,0005,0002,0009}'::text[]))
+                           ->  Hash
+                                 Output: b, c
+                                 ->  Result
+                                       Output: b, c
+                                       One-Time Filter: false
+                     ->  Hash Left Join
+                           Output: t1_1.a, t1_1.c, t2.b, t2.c
+                           Hash Cond: (t1_1.c = t2.c)
+                           ->  Seq Scan on public.plt1_p3 t1_1
+                                 Output: t1_1.a, t1_1.c
+                                 Filter: (t1_1.c <> ALL ('{0001,0005,0002,0009}'::text[]))
+                           ->  Hash
+                                 Output: t2.b, t2.c
+                                 ->  Seq Scan on public.plt2_p3 t2
+                                       Output: t2.b, t2.c
+                                       Filter: (t2.c <> ALL ('{0000,0003,0004,0010}'::text[]))
+(31 rows)
+
+SELECT sum(t1.a), t1.c, sum(t2.b), t2.c FROM (SELECT * FROM plt1 t1 WHERE t1.c NOT IN ('0001', '0005', '0002', '0009')) t1 LEFT JOIN (SELECT * FROM plt2 t2 WHERE t2.c NOT IN ('0000', '0003', '0004', '0010')) t2 ON t1.c = t2.c GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+  sum   |  c   |  sum   |  c   
+--------+------+--------+------
+    600 | 0000 |        | 
+   4350 | 0003 |        | 
+   5600 | 0004 |        | 
+ 137700 | 0006 | 137700 | 0006
+ 158950 | 0007 | 159375 | 0007
+ 169600 | 0008 | 169800 | 0008
+  13100 | 0010 |        | 
+ 229600 | 0011 | 229800 | 0011
+(8 rows)
+
+SELECT sum(t1.a), t1.c, sum(t2.b), t2.c FROM (SELECT * FROM uplt1 t1 WHERE t1.c NOT IN ('0001', '0005', '0002', '0009')) t1 LEFT JOIN (SELECT * FROM uplt2 t2 WHERE t2.c NOT IN ('0000', '0003', '0004', '0010')) t2 ON t1.c = t2.c GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+  sum   |  c   |  sum   |  c   
+--------+------+--------+------
+    600 | 0000 |        | 
+   4350 | 0003 |        | 
+   5600 | 0004 |        | 
+ 137700 | 0006 | 137700 | 0006
+ 158950 | 0007 | 159375 | 0007
+ 169600 | 0008 | 169800 | 0008
+  13100 | 0010 |        | 
+ 229600 | 0011 | 229800 | 0011
+(8 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT sum(t1.a), t1.c, sum(t2.b), t2.c FROM (SELECT * FROM plt1 t1 WHERE t1.c NOT IN ('0001', '0005', '0002', '0009')) t1 RIGHT JOIN (SELECT * FROM plt2 t2 WHERE t2.c NOT IN ('0000', '0003', '0004', '0010')) t2 ON t1.c = t2.c GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+                                           QUERY PLAN                                            
+-------------------------------------------------------------------------------------------------
+ Sort
+   Output: (sum(a)), c, (sum(t2.b)), t2.c
+   Sort Key: c, t2.c
+   ->  HashAggregate
+         Output: sum(a), c, sum(t2.b), t2.c
+         Group Key: c, t2.c
+         ->  Result
+               Output: c, t2.c, a, t2.b
+               ->  Append
+                     ->  Hash Left Join
+                           Output: t2.b, t2.c, a, c
+                           Hash Cond: (t2.c = c)
+                           ->  Seq Scan on public.plt2_p2 t2
+                                 Output: t2.b, t2.c
+                                 Filter: (t2.c <> ALL ('{0000,0003,0004,0010}'::text[]))
+                           ->  Hash
+                                 Output: a, c
+                                 ->  Result
+                                       Output: a, c
+                                       One-Time Filter: false
+                     ->  Hash Right Join
+                           Output: t2_1.b, t2_1.c, t1.a, t1.c
+                           Hash Cond: (t1.c = t2_1.c)
+                           ->  Seq Scan on public.plt1_p3 t1
+                                 Output: t1.a, t1.c
+                                 Filter: (t1.c <> ALL ('{0001,0005,0002,0009}'::text[]))
+                           ->  Hash
+                                 Output: t2_1.b, t2_1.c
+                                 ->  Seq Scan on public.plt2_p3 t2_1
+                                       Output: t2_1.b, t2_1.c
+                                       Filter: (t2_1.c <> ALL ('{0000,0003,0004,0010}'::text[]))
+(31 rows)
+
+SELECT sum(t1.a), t1.c, sum(t2.b), t2.c FROM (SELECT * FROM plt1 t1 WHERE t1.c NOT IN ('0001', '0005', '0002', '0009')) t1 RIGHT JOIN (SELECT * FROM plt2 t2 WHERE t2.c NOT IN ('0000', '0003', '0004', '0010')) t2 ON t1.c = t2.c GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+  sum   |  c   |  sum   |  c   
+--------+------+--------+------
+ 137700 | 0006 | 137700 | 0006
+ 158950 | 0007 | 159375 | 0007
+ 169600 | 0008 | 169800 | 0008
+ 229600 | 0011 | 229800 | 0011
+        |      |   1275 | 0001
+        |      |   1992 | 0002
+        |      |   4392 | 0005
+        |      |   8058 | 0009
+(8 rows)
+
+SELECT sum(t1.a), t1.c, sum(t2.b), t2.c FROM (SELECT * FROM uplt1 t1 WHERE t1.c NOT IN ('0001', '0005', '0002', '0009')) t1 RIGHT JOIN (SELECT * FROM uplt2 t2 WHERE t2.c NOT IN ('0000', '0003', '0004', '0010')) t2 ON t1.c = t2.c GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+  sum   |  c   |  sum   |  c   
+--------+------+--------+------
+ 137700 | 0006 | 137700 | 0006
+ 158950 | 0007 | 159375 | 0007
+ 169600 | 0008 | 169800 | 0008
+ 229600 | 0011 | 229800 | 0011
+        |      |   1275 | 0001
+        |      |   1992 | 0002
+        |      |   4392 | 0005
+        |      |   8058 | 0009
+(8 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT sum(t1.a), t1.c, sum(t2.b), t2.c FROM (SELECT * FROM plt1 t1 WHERE t1.c NOT IN ('0001', '0005', '0002', '0009')) t1 FULL JOIN (SELECT * FROM plt2 t2 WHERE t2.c NOT IN ('0000', '0003', '0004', '0010')) t2 ON t1.c = t2.c GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+                                           QUERY PLAN                                            
+-------------------------------------------------------------------------------------------------
+ Sort
+   Output: (sum(t1.a)), t1.c, (sum(b)), c
+   Sort Key: t1.c, c
+   ->  HashAggregate
+         Output: sum(t1.a), t1.c, sum(b), c
+         Group Key: t1.c, c
+         ->  Result
+               Output: t1.c, c, t1.a, b
+               ->  Append
+                     ->  Hash Full Join
+                           Output: t1.a, t1.c, b, c
+                           Hash Cond: (t1.c = c)
+                           ->  Seq Scan on public.plt1_p1 t1
+                                 Output: t1.a, t1.c
+                                 Filter: (t1.c <> ALL ('{0001,0005,0002,0009}'::text[]))
+                           ->  Hash
+                                 Output: b, c
+                                 ->  Result
+                                       Output: b, c
+                                       One-Time Filter: false
+                     ->  Hash Full Join
+                           Output: a, c, t2.b, t2.c
+                           Hash Cond: (t2.c = c)
+                           ->  Seq Scan on public.plt2_p2 t2
+                                 Output: t2.b, t2.c
+                                 Filter: (t2.c <> ALL ('{0000,0003,0004,0010}'::text[]))
+                           ->  Hash
+                                 Output: a, c
+                                 ->  Result
+                                       Output: a, c
+                                       One-Time Filter: false
+                     ->  Hash Full Join
+                           Output: t1_1.a, t1_1.c, t2_1.b, t2_1.c
+                           Hash Cond: (t1_1.c = t2_1.c)
+                           ->  Seq Scan on public.plt1_p3 t1_1
+                                 Output: t1_1.a, t1_1.c
+                                 Filter: (t1_1.c <> ALL ('{0001,0005,0002,0009}'::text[]))
+                           ->  Hash
+                                 Output: t2_1.b, t2_1.c
+                                 ->  Seq Scan on public.plt2_p3 t2_1
+                                       Output: t2_1.b, t2_1.c
+                                       Filter: (t2_1.c <> ALL ('{0000,0003,0004,0010}'::text[]))
+(42 rows)
+
+SELECT sum(t1.a), t1.c, sum(t2.b), t2.c FROM (SELECT * FROM plt1 t1 WHERE t1.c NOT IN ('0001', '0005', '0002', '0009')) t1 FULL JOIN (SELECT * FROM plt2 t2 WHERE t2.c NOT IN ('0000', '0003', '0004', '0010')) t2 ON t1.c = t2.c GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+  sum   |  c   |  sum   |  c   
+--------+------+--------+------
+    600 | 0000 |        | 
+   4350 | 0003 |        | 
+   5600 | 0004 |        | 
+ 137700 | 0006 | 137700 | 0006
+ 158950 | 0007 | 159375 | 0007
+ 169600 | 0008 | 169800 | 0008
+  13100 | 0010 |        | 
+ 229600 | 0011 | 229800 | 0011
+        |      |   1275 | 0001
+        |      |   1992 | 0002
+        |      |   4392 | 0005
+        |      |   8058 | 0009
+(12 rows)
+
+SELECT sum(t1.a), t1.c, sum(t2.b), t2.c FROM (SELECT * FROM uplt1 t1 WHERE t1.c NOT IN ('0001', '0005', '0002', '0009')) t1 FULL JOIN (SELECT * FROM uplt2 t2 WHERE t2.c NOT IN ('0000', '0003', '0004', '0010')) t2 ON t1.c = t2.c GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+  sum   |  c   |  sum   |  c   
+--------+------+--------+------
+    600 | 0000 |        | 
+   4350 | 0003 |        | 
+   5600 | 0004 |        | 
+ 137700 | 0006 | 137700 | 0006
+ 158950 | 0007 | 159375 | 0007
+ 169600 | 0008 | 169800 | 0008
+  13100 | 0010 |        | 
+ 229600 | 0011 | 229800 | 0011
+        |      |   1275 | 0001
+        |      |   1992 | 0002
+        |      |   4392 | 0005
+        |      |   8058 | 0009
+(12 rows)
+
+-- Semi-join
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.* FROM plt1 t1 WHERE t1.c IN (SELECT t1.c FROM plt2 t1 WHERE t1.b % 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)
+               ->  Materialize
+                     Output: t1_3.c
+                     ->  Seq Scan on public.plt2_p1 t1_3
+                           Output: t1_3.c
+                           Filter: ((t1_3.b % 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)
+               ->  Materialize
+                     Output: t1_4.c
+                     ->  Seq Scan on public.plt2_p2 t1_4
+                           Output: t1_4.c
+                           Filter: ((t1_4.b % 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)
+               ->  Materialize
+                     Output: t1_5.c
+                     ->  Seq Scan on public.plt2_p3 t1_5
+                           Output: t1_5.c
+                           Filter: ((t1_5.b % 25) = 0)
+(37 rows)
+
+SELECT t1.* FROM plt1 t1 WHERE t1.c IN (SELECT t1.c FROM plt2 t1 WHERE t1.b % 25 = 0) AND t1.a % 25 = 0 ORDER BY t1.a;
+  a  |  b  |  c   
+-----+-----+------
+   0 |   0 | 0000
+  50 |  50 | 0001
+ 150 | 150 | 0003
+ 200 | 200 | 0004
+ 300 | 300 | 0006
+ 350 | 350 | 0007
+ 450 | 450 | 0009
+ 500 | 500 | 0010
+(8 rows)
+
+SELECT t1.* FROM uplt1 t1 WHERE t1.c IN (SELECT t1.c FROM uplt2 t1 WHERE t1.b % 25 = 0) AND t1.a % 25 = 0 ORDER BY t1.a;
+  a  |  b  |  c   
+-----+-----+------
+   0 |   0 | 0000
+  50 |  50 | 0001
+ 150 | 150 | 0003
+ 200 | 200 | 0004
+ 300 | 300 | 0006
+ 350 | 350 | 0007
+ 450 | 450 | 0009
+ 500 | 500 | 0010
+(8 rows)
+
+--
+-- 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;
+CREATE TABLE plt2_e (a int, b int, c text) PARTITION BY LIST(ltrim(c, 'A'));
+CREATE TABLE plt2_e_p1 PARTITION OF plt2_e FOR VALUES IN ('0000', '0003', '0004', '0010');
+CREATE TABLE plt2_e_p2 PARTITION OF plt2_e FOR VALUES IN ('0001', '0005', '0002', '0009');
+CREATE TABLE plt2_e_p3 PARTITION OF plt2_e FOR VALUES IN ('0006', '0007', '0008', '0011');
+INSERT INTO plt2_e SELECT i, i, 'A' || to_char(i/50, 'FM0000') FROM generate_series(0, 599, 3) i;
+ANALYZE plt2_e;
+ANALYZE plt2_e_p1;
+ANALYZE plt2_e_p2;
+ANALYZE plt2_e_p3;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE uplt2_e AS SELECT * FROM plt2_e;
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM plt1_e t1 LEFT JOIN plt2_e t2 ON t1.a = t2.b AND ltrim(t1.c, 'A') = ltrim(t2.c, 'A') 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 (ltrim(t2.c, 'A'::text) = ltrim(t1.c, 'A'::text)))
+               ->  Seq Scan on public.plt2_e_p1 t2
+                     Output: t2.b, t2.c
+               ->  Hash
+                     Output: t1.a, t1.c
+                     ->  Seq Scan on public.plt1_e_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
+               Hash Cond: ((t2_1.b = t1_1.a) AND (ltrim(t2_1.c, 'A'::text) = ltrim(t1_1.c, 'A'::text)))
+               ->  Seq Scan on public.plt2_e_p2 t2_1
+                     Output: t2_1.b, t2_1.c
+               ->  Hash
+                     Output: t1_1.a, t1_1.c
+                     ->  Seq Scan on public.plt1_e_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
+               Hash Cond: ((t2_2.b = t1_2.a) AND (ltrim(t2_2.c, 'A'::text) = ltrim(t1_2.c, 'A'::text)))
+               ->  Seq Scan on public.plt2_e_p3 t2_2
+                     Output: t2_2.b, t2_2.c
+               ->  Hash
+                     Output: t1_2.a, t1_2.c
+                     ->  Seq Scan on public.plt1_e_p3 t1_2
+                           Output: t1_2.a, t1_2.c
+                           Filter: ((t1_2.a % 25) = 0)
+(34 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM plt1_e t1 LEFT JOIN plt2_e t2 ON t1.a = t2.b AND ltrim(t1.c, 'A') = ltrim(t2.c, 'A') WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |   c   |  b  |   c   
+-----+-------+-----+-------
+   0 | A0000 |   0 | A0000
+  50 | A0001 |     | 
+ 100 | A0002 |     | 
+ 150 | A0003 | 150 | A0003
+ 200 | A0004 |     | 
+ 250 | A0005 |     | 
+ 300 | A0006 | 300 | A0006
+ 350 | A0007 |     | 
+ 400 | A0008 |     | 
+ 450 | A0009 | 450 | A0009
+ 500 | A0010 |     | 
+ 550 | A0011 |     | 
+(12 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM uplt1_e t1 LEFT JOIN uplt2_e t2 ON t1.a = t2.b AND ltrim(t1.c, 'A') = ltrim(t2.c, 'A') WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+  a  |   c   |  b  |   c   
+-----+-------+-----+-------
+   0 | A0000 |   0 | A0000
+  50 | A0001 |     | 
+ 100 | A0002 |     | 
+ 150 | A0003 | 150 | A0003
+ 200 | A0004 |     | 
+ 250 | A0005 |     | 
+ 300 | A0006 | 300 | A0006
+ 350 | A0007 |     | 
+ 400 | A0008 |     | 
+ 450 | A0009 | 450 | A0009
+ 500 | A0010 |     | 
+ 550 | A0011 |     | 
+(12 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM plt1_e WHERE plt1_e.a % 25 = 0) t1 FULL JOIN (SELECT * FROM plt2_e WHERE plt2_e.b % 25 = 0) t2 ON (t1.a = t2.b AND ltrim(t1.c, 'A') = ltrim(t2.c, 'A')) ORDER BY t1.a, t2.b;
+                                                         QUERY PLAN                                                         
+----------------------------------------------------------------------------------------------------------------------------
+ Sort
+   Output: plt1_e_p1.a, plt1_e_p1.c, plt2_e_p1.b, plt2_e_p1.c
+   Sort Key: plt1_e_p1.a, plt2_e_p1.b
+   ->  Append
+         ->  Hash Full Join
+               Output: plt1_e_p1.a, plt1_e_p1.c, plt2_e_p1.b, plt2_e_p1.c
+               Hash Cond: ((plt1_e_p1.a = plt2_e_p1.b) AND (ltrim(plt1_e_p1.c, 'A'::text) = ltrim(plt2_e_p1.c, 'A'::text)))
+               ->  Seq Scan on public.plt1_e_p1
+                     Output: plt1_e_p1.a, plt1_e_p1.c
+                     Filter: ((plt1_e_p1.a % 25) = 0)
+               ->  Hash
+                     Output: plt2_e_p1.b, plt2_e_p1.c
+                     ->  Seq Scan on public.plt2_e_p1
+                           Output: plt2_e_p1.b, plt2_e_p1.c
+                           Filter: ((plt2_e_p1.b % 25) = 0)
+         ->  Hash Full Join
+               Output: plt1_e_p2.a, plt1_e_p2.c, plt2_e_p2.b, plt2_e_p2.c
+               Hash Cond: ((plt1_e_p2.a = plt2_e_p2.b) AND (ltrim(plt1_e_p2.c, 'A'::text) = ltrim(plt2_e_p2.c, 'A'::text)))
+               ->  Seq Scan on public.plt1_e_p2
+                     Output: plt1_e_p2.a, plt1_e_p2.c
+                     Filter: ((plt1_e_p2.a % 25) = 0)
+               ->  Hash
+                     Output: plt2_e_p2.b, plt2_e_p2.c
+                     ->  Seq Scan on public.plt2_e_p2
+                           Output: plt2_e_p2.b, plt2_e_p2.c
+                           Filter: ((plt2_e_p2.b % 25) = 0)
+         ->  Hash Full Join
+               Output: plt1_e_p3.a, plt1_e_p3.c, plt2_e_p3.b, plt2_e_p3.c
+               Hash Cond: ((plt1_e_p3.a = plt2_e_p3.b) AND (ltrim(plt1_e_p3.c, 'A'::text) = ltrim(plt2_e_p3.c, 'A'::text)))
+               ->  Seq Scan on public.plt1_e_p3
+                     Output: plt1_e_p3.a, plt1_e_p3.c
+                     Filter: ((plt1_e_p3.a % 25) = 0)
+               ->  Hash
+                     Output: plt2_e_p3.b, plt2_e_p3.c
+                     ->  Seq Scan on public.plt2_e_p3
+                           Output: plt2_e_p3.b, plt2_e_p3.c
+                           Filter: ((plt2_e_p3.b % 25) = 0)
+(37 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM plt1_e WHERE plt1_e.a % 25 = 0) t1 FULL JOIN (SELECT * FROM plt2_e WHERE plt2_e.b % 25 = 0) t2 ON (t1.a = t2.b AND ltrim(t1.c, 'A') = ltrim(t2.c, 'A')) ORDER BY t1.a, t2.b;
+  a  |   c   |  b  |   c   
+-----+-------+-----+-------
+   0 | A0000 |   0 | A0000
+  50 | A0001 |     | 
+ 100 | A0002 |     | 
+ 150 | A0003 | 150 | A0003
+ 200 | A0004 |     | 
+ 250 | A0005 |     | 
+ 300 | A0006 | 300 | A0006
+ 350 | A0007 |     | 
+ 400 | A0008 |     | 
+ 450 | A0009 | 450 | A0009
+ 500 | A0010 |     | 
+ 550 | A0011 |     | 
+     |       |  75 | A0001
+     |       | 225 | A0004
+     |       | 375 | A0007
+     |       | 525 | A0010
+(16 rows)
+
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM uplt1_e t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM uplt2_e t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b AND ltrim(t1.c, 'A') = ltrim(t2.c, 'A')) ORDER BY t1.a, t2.b;
+  a  |   c   |  b  |   c   
+-----+-------+-----+-------
+   0 | A0000 |   0 | A0000
+  50 | A0001 |     | 
+ 100 | A0002 |     | 
+ 150 | A0003 | 150 | A0003
+ 200 | A0004 |     | 
+ 250 | A0005 |     | 
+ 300 | A0006 | 300 | A0006
+ 350 | A0007 |     | 
+ 400 | A0008 |     | 
+ 450 | A0009 | 450 | A0009
+ 500 | A0010 |     | 
+ 550 | A0011 |     | 
+     |       |  75 | A0001
+     |       | 225 | A0004
+     |       | 375 | A0007
+     |       | 525 | A0010
+(16 rows)
+
+--
+-- 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 START ('0000') END ('0250');
+CREATE TABLE prt1_n_p2 PARTITION OF prt1_n FOR VALUES START ('0250') END ('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 START (0) END (300);
+CREATE TABLE prt4_n_p2 PARTITION OF prt4_n FOR VALUES START (300) END (500);
+CREATE TABLE prt4_n_p3 PARTITION OF prt4_n FOR VALUES START (500) END (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..e5895ce
--- /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 START (0) END (250) PARTITION BY RANGE (b);
+CREATE TABLE prt1_l_p1_p1 PARTITION OF prt1_l_p1 FOR VALUES START (0) END (100);
+CREATE TABLE prt1_l_p1_p2 PARTITION OF prt1_l_p1 FOR VALUES START (100) END (250);
+CREATE TABLE prt1_l_p2 PARTITION OF prt1_l FOR VALUES START (250) END (500) PARTITION BY RANGE (c);
+CREATE TABLE prt1_l_p2_p1 PARTITION OF prt1_l_p2 FOR VALUES START ('0250') END ('0400');
+CREATE TABLE prt1_l_p2_p2 PARTITION OF prt1_l_p2 FOR VALUES START ('0400') END ('0500');
+CREATE TABLE prt1_l_p3 PARTITION OF prt1_l FOR VALUES START (500) END (600) PARTITION BY RANGE ((b + a));
+CREATE TABLE prt1_l_p3_p1 PARTITION OF prt1_l_p3 FOR VALUES START (1000) END (1100);
+CREATE TABLE prt1_l_p3_p2 PARTITION OF prt1_l_p3 FOR VALUES START (1100) END (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 START (0) END (250) PARTITION BY RANGE (a);
+CREATE TABLE prt2_l_p1_p1 PARTITION OF prt2_l_p1 FOR VALUES START (0) END (100);
+CREATE TABLE prt2_l_p1_p2 PARTITION OF prt2_l_p1 FOR VALUES START (100) END (250);
+CREATE TABLE prt2_l_p2 PARTITION OF prt2_l FOR VALUES START (250) END (500) PARTITION BY RANGE (c);
+CREATE TABLE prt2_l_p2_p1 PARTITION OF prt2_l_p2 FOR VALUES START ('0250') END ('0400');
+CREATE TABLE prt2_l_p2_p2 PARTITION OF prt2_l_p2 FOR VALUES START ('0400') END ('0500');
+CREATE TABLE prt2_l_p3 PARTITION OF prt2_l FOR VALUES START (500) END (600) PARTITION BY RANGE ((a + b));
+CREATE TABLE prt2_l_p3_p1 PARTITION OF prt2_l_p3 FOR VALUES START (1000) END (1100);
+CREATE TABLE prt2_l_p3_p2 PARTITION OF prt2_l_p3 FOR VALUES START (1100) END (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..b4945df
--- /dev/null
+++ b/src/test/regress/sql/partition_join.sql
@@ -0,0 +1,600 @@
+--
+-- PARTITION_JOIN
+-- Test partition-wise join between partitioned tables
+--
+
+--
+-- 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 START (0) END (250);
+CREATE TABLE prt1_p3 PARTITION OF prt1 FOR VALUES START (500) END (600);
+CREATE TABLE prt1_p2 PARTITION OF prt1 FOR VALUES START (250) END (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 START (0) END (250);
+CREATE TABLE prt2_p2 PARTITION OF prt2 FOR VALUES START (250) END (500);
+CREATE TABLE prt2_p3 PARTITION OF prt2 FOR VALUES START (500) END (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 + t2.b) % 120 = 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 + t2.b) % 120 = 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 + t2.b) % 120 = 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 OR t2.b is null 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 START (0) END (250);
+CREATE TABLE prt1_e_p2 PARTITION OF prt1_e FOR VALUES START (250) END (500);
+CREATE TABLE prt1_e_p3 PARTITION OF prt1_e FOR VALUES START (500) END (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 START (0) END (250);
+CREATE TABLE prt2_e_p2 PARTITION OF prt2_e FOR VALUES START (250) END (500);
+CREATE TABLE prt2_e_p3 PARTITION OF prt2_e FOR VALUES START (500) END (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_a on prt1(a);
+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_b on prt2(b);
+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_ab2 on prt1_e(((a+b)/2));
+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 START (0, 0) END (250, 250);
+CREATE TABLE prt1_m_p2 PARTITION OF prt1_m FOR VALUES START (250, 250) END (500, 500);
+CREATE TABLE prt1_m_p3 PARTITION OF prt1_m FOR VALUES START (500, 500) END (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 START (0, 0) END (250, 250);
+CREATE TABLE prt2_m_p2 PARTITION OF prt2_m FOR VALUES START (250, 250) END (500, 500);
+CREATE TABLE prt2_m_p3 PARTITION OF prt2_m FOR VALUES START (500, 500) END (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;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM plt1 t1, plt2 t2 WHERE t1.c = t2.c AND t1.a = t2.a AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM plt1 t1, plt2 t2 WHERE t1.c = t2.c AND t1.a = t2.a AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM uplt1 t1, uplt2 t2 WHERE t1.c = t2.c AND t1.a = t2.a 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 plt1 t1 LEFT JOIN plt2 t2 ON t1.a = t2.a AND t1.c = t2.c WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM plt1 t1 LEFT JOIN plt2 t2 ON t1.a = t2.a AND t1.c = t2.c WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM uplt1 t1 LEFT JOIN uplt2 t2 ON t1.a = t2.a AND t1.c = t2.c 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 plt1 t1 RIGHT JOIN plt2 t2 ON t1.a = t2.b AND t1.c = t2.c WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM plt1 t1 RIGHT JOIN plt2 t2 ON t1.a = t2.b AND t1.c = t2.c WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM uplt1 t1 RIGHT JOIN uplt2 t2 ON t1.a = t2.b AND t1.c = t2.c 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 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) ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.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) ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM uplt1 t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM uplt2 t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b AND t1.c = t2.c) 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 sum(t1.a), t1.c, avg(t2.b), t2.c FROM (SELECT 50 phv, * FROM plt1 WHERE plt1.a % 25 = 0) t1 FULL JOIN (SELECT 75 phv, * FROM plt2 WHERE plt2.b % 25 = 0) t2 ON (t1.c = t2.c AND t1.a = t2.b) GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+SELECT sum(t1.a), t1.c, avg(t2.b), t2.c FROM (SELECT 50 phv, * FROM plt1 WHERE plt1.a % 25 = 0) t1 FULL JOIN (SELECT 75 phv, * FROM plt2 WHERE plt2.b % 25 = 0) t2 ON (t1.c = t2.c AND t1.a = t2.b) GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+SELECT sum(t1.a), t1.c, avg(t2.b), t2.c FROM (SELECT 50 phv, * FROM uplt1 WHERE uplt1.a % 25 = 0) t1 FULL JOIN (SELECT 75 phv, * FROM uplt2 WHERE uplt2.b % 25 = 0) t2 ON (t1.c = t2.c AND t1.a = t2.b) GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT sum(t1.a), t1.c, sum(t1.phv), avg(t2.b), t2.c, avg(t2.phv) FROM (SELECT 25 phv, * FROM plt1 WHERE plt1.a % 25 = 0) t1 FULL JOIN (SELECT 50 phv, * FROM plt2 WHERE plt2.b % 25 = 0) t2 ON (t1.c = t2.c AND t1.a = t2.b) GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+SELECT sum(t1.a), t1.c, sum(t1.phv), avg(t2.b), t2.c, avg(t2.phv) FROM (SELECT 25 phv, * FROM plt1 WHERE plt1.a % 25 = 0) t1 FULL JOIN (SELECT 50 phv, * FROM plt2 WHERE plt2.b % 25 = 0) t2 ON (t1.c = t2.c AND t1.a = t2.b) GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+SELECT sum(t1.a), t1.c, sum(t1.phv), avg(t2.b), t2.c, avg(t2.phv) FROM (SELECT 25 phv, * FROM uplt1 WHERE uplt1.a % 25 = 0) t1 FULL JOIN (SELECT 50 phv, * FROM uplt2 WHERE uplt2.b % 25 = 0) t2 ON (t1.c = t2.c AND t1.a = t2.b) GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+
+-- Join with pruned partitions from joining relations
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT sum(t1.a), t1.c, avg(t2.b), t2.c FROM plt1 t1, plt2 t2 WHERE t1.c = t2.c AND t1.c NOT IN ('0001', '0005', '0002', '0009') AND t2.c NOT IN ('0000', '0003', '0004', '0010') GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+SELECT sum(t1.a), t1.c, avg(t2.b), t2.c FROM plt1 t1, plt2 t2 WHERE t1.c = t2.c AND t1.c NOT IN ('0001', '0005', '0002', '0009') AND t2.c NOT IN ('0000', '0003', '0004', '0010') GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+SELECT sum(t1.a), t1.c, avg(t2.b), t2.c FROM uplt1 t1, uplt2 t2 WHERE t1.c = t2.c AND t1.c NOT IN ('0001', '0005', '0002', '0009') AND t2.c NOT IN ('0000', '0003', '0004', '0010') GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT sum(t1.a), t1.c, sum(t2.b), t2.c FROM (SELECT * FROM plt1 t1 WHERE t1.c NOT IN ('0001', '0005', '0002', '0009')) t1 LEFT JOIN (SELECT * FROM plt2 t2 WHERE t2.c NOT IN ('0000', '0003', '0004', '0010')) t2 ON t1.c = t2.c GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+SELECT sum(t1.a), t1.c, sum(t2.b), t2.c FROM (SELECT * FROM plt1 t1 WHERE t1.c NOT IN ('0001', '0005', '0002', '0009')) t1 LEFT JOIN (SELECT * FROM plt2 t2 WHERE t2.c NOT IN ('0000', '0003', '0004', '0010')) t2 ON t1.c = t2.c GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+SELECT sum(t1.a), t1.c, sum(t2.b), t2.c FROM (SELECT * FROM uplt1 t1 WHERE t1.c NOT IN ('0001', '0005', '0002', '0009')) t1 LEFT JOIN (SELECT * FROM uplt2 t2 WHERE t2.c NOT IN ('0000', '0003', '0004', '0010')) t2 ON t1.c = t2.c GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT sum(t1.a), t1.c, sum(t2.b), t2.c FROM (SELECT * FROM plt1 t1 WHERE t1.c NOT IN ('0001', '0005', '0002', '0009')) t1 RIGHT JOIN (SELECT * FROM plt2 t2 WHERE t2.c NOT IN ('0000', '0003', '0004', '0010')) t2 ON t1.c = t2.c GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+SELECT sum(t1.a), t1.c, sum(t2.b), t2.c FROM (SELECT * FROM plt1 t1 WHERE t1.c NOT IN ('0001', '0005', '0002', '0009')) t1 RIGHT JOIN (SELECT * FROM plt2 t2 WHERE t2.c NOT IN ('0000', '0003', '0004', '0010')) t2 ON t1.c = t2.c GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+SELECT sum(t1.a), t1.c, sum(t2.b), t2.c FROM (SELECT * FROM uplt1 t1 WHERE t1.c NOT IN ('0001', '0005', '0002', '0009')) t1 RIGHT JOIN (SELECT * FROM uplt2 t2 WHERE t2.c NOT IN ('0000', '0003', '0004', '0010')) t2 ON t1.c = t2.c GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT sum(t1.a), t1.c, sum(t2.b), t2.c FROM (SELECT * FROM plt1 t1 WHERE t1.c NOT IN ('0001', '0005', '0002', '0009')) t1 FULL JOIN (SELECT * FROM plt2 t2 WHERE t2.c NOT IN ('0000', '0003', '0004', '0010')) t2 ON t1.c = t2.c GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+SELECT sum(t1.a), t1.c, sum(t2.b), t2.c FROM (SELECT * FROM plt1 t1 WHERE t1.c NOT IN ('0001', '0005', '0002', '0009')) t1 FULL JOIN (SELECT * FROM plt2 t2 WHERE t2.c NOT IN ('0000', '0003', '0004', '0010')) t2 ON t1.c = t2.c GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+SELECT sum(t1.a), t1.c, sum(t2.b), t2.c FROM (SELECT * FROM uplt1 t1 WHERE t1.c NOT IN ('0001', '0005', '0002', '0009')) t1 FULL JOIN (SELECT * FROM uplt2 t2 WHERE t2.c NOT IN ('0000', '0003', '0004', '0010')) t2 ON t1.c = t2.c GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c;
+
+-- Semi-join
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.* FROM plt1 t1 WHERE t1.c IN (SELECT t1.c FROM plt2 t1 WHERE t1.b % 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.b % 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.b % 25 = 0) AND t1.a % 25 = 0 ORDER BY t1.a;
+
+--
+-- 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;
+
+CREATE TABLE plt2_e (a int, b int, c text) PARTITION BY LIST(ltrim(c, 'A'));
+CREATE TABLE plt2_e_p1 PARTITION OF plt2_e FOR VALUES IN ('0000', '0003', '0004', '0010');
+CREATE TABLE plt2_e_p2 PARTITION OF plt2_e FOR VALUES IN ('0001', '0005', '0002', '0009');
+CREATE TABLE plt2_e_p3 PARTITION OF plt2_e FOR VALUES IN ('0006', '0007', '0008', '0011');
+INSERT INTO plt2_e SELECT i, i, 'A' || to_char(i/50, 'FM0000') FROM generate_series(0, 599, 3) i;
+ANALYZE plt2_e;
+ANALYZE plt2_e_p1;
+ANALYZE plt2_e_p2;
+ANALYZE plt2_e_p3;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE uplt2_e AS SELECT * FROM plt2_e;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM plt1_e t1 LEFT JOIN plt2_e t2 ON t1.a = t2.b AND ltrim(t1.c, 'A') = ltrim(t2.c, 'A') WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM plt1_e t1 LEFT JOIN plt2_e t2 ON t1.a = t2.b AND ltrim(t1.c, 'A') = ltrim(t2.c, 'A') WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM uplt1_e t1 LEFT JOIN uplt2_e t2 ON t1.a = t2.b AND ltrim(t1.c, 'A') = ltrim(t2.c, 'A') 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 plt1_e WHERE plt1_e.a % 25 = 0) t1 FULL JOIN (SELECT * FROM plt2_e WHERE plt2_e.b % 25 = 0) t2 ON (t1.a = t2.b AND ltrim(t1.c, 'A') = ltrim(t2.c, 'A')) ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM plt1_e WHERE plt1_e.a % 25 = 0) t1 FULL JOIN (SELECT * FROM plt2_e WHERE plt2_e.b % 25 = 0) t2 ON (t1.a = t2.b AND ltrim(t1.c, 'A') = ltrim(t2.c, 'A')) ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM uplt1_e t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM uplt2_e t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b AND ltrim(t1.c, 'A') = ltrim(t2.c, 'A')) ORDER BY t1.a, t2.b;
+
+--
+-- 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 START ('0000') END ('0250');
+CREATE TABLE prt1_n_p2 PARTITION OF prt1_n FOR VALUES START ('0250') END ('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 START (0) END (300);
+CREATE TABLE prt4_n_p2 PARTITION OF prt4_n FOR VALUES START (300) END (500);
+CREATE TABLE prt4_n_p3 PARTITION OF prt4_n FOR VALUES START (500) END (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);