diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 2e4b670..06fc90d 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -999,21 +999,59 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
 		if (rel->has_eclass_joins || has_useful_pathkeys(root, rel))
 			add_child_rel_equivalences(root, appinfo, rel, childrel);
 		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.
+		 * For a partitioned tables, individual partitions can participate in
+		 * the pair-wise joins. We need attr_needed data for building pair-wise
+		 * join relations. Partitions should have same layout as the parent
+		 * table and hence should not need any translation, but we still use
+		 * the translated_vars to keep the code in sync with the inheritance
+		 * code.
 		 */
+		if (rel->part_info)
+		{
+			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;
+				}
+			}
+		}
 
 		/*
 		 * Compute the child's size.
 		 */
 		set_rel_size(root, childrel, childRTindex, childRTE);
 
 		/*
 		 * It is possible that constraint exclusion detected a contradiction
 		 * within a child subquery, even though we didn't prove one above. If
 		 * so, we can skip this child.
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 8c1dccc..3ab6a9d 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -119,20 +119,21 @@ bool		enable_seqscan = true;
 bool		enable_indexscan = true;
 bool		enable_indexonlyscan = true;
 bool		enable_bitmapscan = true;
 bool		enable_tidscan = true;
 bool		enable_sort = true;
 bool		enable_hashagg = true;
 bool		enable_nestloop = true;
 bool		enable_material = true;
 bool		enable_mergejoin = true;
 bool		enable_hashjoin = true;
+bool		enable_partition_wise_join = true;
 
 typedef struct
 {
 	PlannerInfo *root;
 	QualCost	total;
 } cost_qual_eval_context;
 
 static List *extract_nonindex_conditions(List *qual_clauses, List *indexquals);
 static MergeScanSelCache *cached_scansel(PlannerInfo *root,
 			   RestrictInfo *rinfo,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 01d4fea..e485e01 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -7,38 +7,56 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
  * IDENTIFICATION
  *	  src/backend/optimizer/path/joinrels.c
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
+#include "optimizer/clauses.h"
 #include "optimizer/joininfo.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
+#include "optimizer/cost.h"
 #include "utils/memutils.h"
 
 
 static void make_rels_by_clause_joins(PlannerInfo *root,
 						  RelOptInfo *old_rel,
 						  ListCell *other_rels);
 static void make_rels_by_clauseless_joins(PlannerInfo *root,
 							  RelOptInfo *old_rel,
 							  ListCell *other_rels);
 static bool has_join_restriction(PlannerInfo *root, RelOptInfo *rel);
 static bool has_legal_joinclause(PlannerInfo *root, RelOptInfo *rel);
 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 bool have_partkey_equi_join(RelOptInfo *rel1, RelOptInfo *rel2,
+						   SpecialJoinInfo *sjinfo, List *restrictlist);
+static PartitionOptInfo *build_joinrel_part_info(RelOptInfo *rel1,
+							   RelOptInfo *rel2, SpecialJoinInfo *sjinfo,
+							   List *restrictlist);
+static void add_append_paths_to_joinrel(RelOptInfo *joinrel);
+static void match_expr_to_partition_keys(RestrictInfo *rinfo, Expr *expr,
+						  RelOptInfo *rel, List **pkclauses);
+static bool partkey_types_match(PartitionOptInfo *part_info1,
+					PartitionOptInfo *part_info2);
+static bool have_same_partition_lists(PartitionOptInfo *part_info1,
+					  PartitionOptInfo *part_info2);
+static bool have_same_partition_bounds(PartitionOptInfo *part_info1,
+					  PartitionOptInfo *part_info2);
+static bool compare_partition_bounds(PartitionOptInfo *part_info,
+					PartitionRangeBound *bound1, PartitionRangeBound *bound2);
 
 
 /*
  * join_search_one_level
  *	  Consider ways to produce join relations containing exactly 'level'
  *	  jointree items.  (This is one step of the dynamic-programming method
  *	  embodied in standard_join_search.)  Join rel nodes for each feasible
  *	  combination of lower-level rels are created and returned in a list.
  *	  Implementation paths are created for each such joinrel, too.
  *
@@ -652,20 +670,21 @@ join_is_legal(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
  */
 RelOptInfo *
 make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
 {
 	Relids		joinrelids;
 	SpecialJoinInfo *sjinfo;
 	bool		reversed;
 	SpecialJoinInfo sjinfo_data;
 	RelOptInfo *joinrel;
 	List	   *restrictlist;
+	PartitionOptInfo	*part_info;
 
 	/* We should never try to join two overlapping sets of rels. */
 	Assert(!bms_overlap(rel1->relids, rel2->relids));
 
 	/* Construct Relids set that identifies the joinrel. */
 	joinrelids = bms_union(rel1->relids, rel2->relids);
 
 	/* Check validity and determine join type. */
 	if (!join_is_legal(root, rel1, rel2, joinrelids,
 					   &sjinfo, &reversed))
@@ -862,25 +881,372 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
 			add_paths_to_joinrel(root, joinrel, rel1, rel2,
 								 JOIN_ANTI, sjinfo,
 								 restrictlist);
 			break;
 		default:
 			/* other values not expected here */
 			elog(ERROR, "unrecognized join type: %d", (int) sjinfo->jointype);
 			break;
 	}
 
+	/*
+	 * If both the relations are partitioned in the same way, and there is an
+	 * equi-join clause on partition key, try joining the partitions. Store the
+	 * partitioning scheme in joinrel for further joins.
+	 */
+	part_info = build_joinrel_part_info(rel1, rel2, sjinfo, restrictlist);
+	joinrel->part_info = part_info;
+	if (part_info)
+	{
+		PartitionOptInfo *part_info1 = rel1->part_info;
+		PartitionOptInfo *part_info2 = rel2->part_info;
+		int		nparts = part_info->nparts;
+		int		cnt_parts;
+		StringInfo	rel1_desc = makeStringInfo();
+		StringInfo	rel2_desc = makeStringInfo();
+
+		/* TODO: remove this notice when finalising the patch. */
+		outBitmapset(rel1_desc, rel1->relids);
+		outBitmapset(rel2_desc, rel2->relids);
+		elog(NOTICE, "join between relations %s and %s is considered for partition-wise join.",
+			 rel1_desc->data, rel2_desc->data);
+
+		/* Create join relations for the partition relations. */
+		for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++)
+		{
+			RelOptInfo	*part_join_rel;
+
+			/*
+			 * TODO: make_join_rel can return NULL if the join is illegal. This
+			 * should not happen since the join between parents is deemed
+			 * legal.
+			 */
+			part_join_rel = make_join_rel(root,
+										  part_info1->part_rels[cnt_parts],
+										  part_info2->part_rels[cnt_parts]);
+			part_info->part_rels[cnt_parts] = part_join_rel;
+		}
+
+		/* Add append paths for pair-wise joins. */
+		add_append_paths_to_joinrel(joinrel);
+	}
+	/* TODO: remove this notice printing code while finalising the patch. */
+	else if (!IS_OTHER_REL(rel1->reloptkind) && !IS_OTHER_REL(rel2->reloptkind))
+	{
+		StringInfo	rel1_desc = makeStringInfo();
+		StringInfo	rel2_desc = makeStringInfo();
+
+		/* TODO: remove this notice when finalising the patch. */
+		outBitmapset(rel1_desc, rel1->relids);
+		outBitmapset(rel2_desc, rel2->relids);
+		elog(NOTICE, "join between relations %s and %s is NOT considered for partition-wise join.",
+			 rel1_desc->data, rel2_desc->data);
+	}
+
 	bms_free(joinrelids);
 
 	return joinrel;
 }
 
+/*
+ * build_joinrel_part_info
+ *
+ * Given partition description of two joining relations, construct partition
+ * description for join between those relations. If we can execute
+ * partition-wise join the resultant join is partitioned in the same way as the
+ * joining relations. Otherwise, it is not partitioned and in such case the
+ * function returns NULL.
+ *
+ * TODO find the right place for this function.
+ *
+ * TODO:
+ * This function and
+ * build_partition_info() should be named consistently e.g.
+ * build_joinrel_partition_info and build_simplerel_partition_info or
+ * build_baserel_partition_info resp.
+ */
+static PartitionOptInfo *
+build_joinrel_part_info(RelOptInfo *rel1, RelOptInfo *rel2,
+						SpecialJoinInfo *sjinfo, List *restrictlist)
+{
+	PartitionOptInfo	*part_info;
+	PartitionOptInfo	*part_info1 = rel1->part_info;
+	PartitionOptInfo	*part_info2 = rel2->part_info;
+	int		cnt_pks;
+	int		num_pks;
+
+	/* Do nothing, if user doesn't want to try partition-wise join. */
+	if (!enable_partition_wise_join)
+		return NULL;
+
+	/*
+	 * If number of partitions, number of partition keys and partitioning
+	 * strategy of the joining relations do not match, we can not apply
+	 * partition-wise join.
+	 *
+	 * TODO: it should be possible to push an inner join down even if the number of
+	 * partitions differ but the common partitions match. In such a case pushing
+	 * down outer joins would be tricky, but still doable using empty relation
+	 * for non-existing partition.
+	 */
+	if (!part_info1 || !part_info2 ||
+		part_info1->nparts != part_info2->nparts ||
+		part_info1->strategy != part_info2->strategy ||
+		part_info1->partnatts != part_info2->partnatts)
+		return NULL;
+
+	/*
+	 * Partition-wise join can not be applied if datatypes and collations of
+	 * the partition keys do not match.
+	 */
+	if (!partkey_types_match(part_info1, part_info2))
+		return NULL;
+
+	/*
+	 * Partition-wise join can not be applied if the partition bounds or lists
+	 * of joining relations do not match.
+	 */
+	switch (part_info1->strategy)
+	{
+		case PARTITION_STRAT_LIST:
+			if (!have_same_partition_lists(part_info1, part_info2))
+				return NULL;
+			break;
+
+		case PARTITION_STRAT_RANGE:
+			if (!have_same_partition_bounds(part_info1, part_info2))
+				return NULL;
+			break;
+
+		default:
+			/* Unknown partition strategy. */
+			return NULL;
+	}
+
+	/*
+	 * Partition-wise join can not be applied if there is no equi-join
+	 * condition between partition keys.
+	 */
+	if (!have_partkey_equi_join(rel1, rel2, sjinfo, restrictlist))
+		return NULL;
+
+	/* The join is partitioned the similar to the joining relations. */
+	part_info = makeNode(PartitionOptInfo);
+
+	/* Information related to the partititions. */
+	part_info->nparts = part_info1->nparts;
+	/* Allocate space for partition RelOptInfos, which will be filled later. */
+	part_info->part_rels = (RelOptInfo **) palloc(sizeof(RelOptInfo *) *
+												  part_info->nparts);
+	part_info->lists = part_info1->lists;
+	part_info->rangelowers = part_info1->rangelowers;
+	part_info->rangeuppers = part_info1->rangeuppers;
+
+	/* Information related to partition keys. */
+	part_info->strategy = part_info1->strategy;
+	part_info->partnatts = part_info1->partnatts;
+	part_info->partopfamily = part_info1->partopfamily;
+	part_info->partopcintype = part_info1->partopcintype;
+	part_info->partsupfunc = part_info1->partsupfunc;
+	part_info->tcinfo = part_info1->tcinfo;
+	num_pks = part_info->partnatts;
+	part_info->partexprs = (List **) palloc0(sizeof(List *) * num_pks);
+	for (cnt_pks = 0; cnt_pks < num_pks; cnt_pks++)
+	{
+		List *pkexpr = list_copy(part_info1->partexprs[cnt_pks]);
+
+		pkexpr = list_concat(pkexpr,
+							 list_copy(part_info2->partexprs[cnt_pks]));
+		part_info->partexprs[cnt_pks] = pkexpr;
+	}
+
+	return part_info;
+}
+
+/*
+ * Returns true if the given relations have equi-join clauses on all the
+ * corresponding partition keys.
+ */
+static bool
+have_partkey_equi_join(RelOptInfo *rel1, RelOptInfo *rel2,
+					 SpecialJoinInfo *sjinfo, List *restrictlist)
+{
+	PartitionOptInfo	*part_info1 = rel1->part_info;
+	PartitionOptInfo	*part_info2 = rel2->part_info;
+	ListCell	*lc;
+	List	   **pkclauses1;
+	List	   **pkclauses2;
+	int		cnt_pks;
+	int		num_pks;
+
+	Assert(part_info1->partnatts == part_info2->partnatts);
+	num_pks = part_info1->partnatts;
+	pkclauses1 = (List **) palloc0(sizeof(List *) * num_pks);
+	pkclauses2 = (List **) palloc0(sizeof(List *) * num_pks);
+
+	foreach (lc, restrictlist)
+	{
+		RestrictInfo *rinfo = lfirst(lc);
+		OpExpr		 *opexpr;
+		Expr		 *expr1;
+		Expr		 *expr2;
+
+		/* If processing an outer join, only use its own join clauses. */
+		if (IS_OUTER_JOIN(sjinfo->jointype) && rinfo->is_pushed_down)
+			continue;
+
+		/* Skip non-equi-join clauses. */
+		if (!rinfo->can_join ||
+			rinfo->hashjoinoperator == InvalidOid ||
+			!rinfo->mergeopfamilies)
+			continue;
+
+		opexpr = (OpExpr *) rinfo->clause;
+		Assert(is_opclause(opexpr));
+
+		/*
+		 * If clause of form rel1_expr op rel2_expr OR rel2_expr op rel1_expr,
+		 * match the operands to the relations. Otherwise, the clause is
+		 * not an equi-join between partition keys of joining relations.
+		 */
+		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. */
+		match_expr_to_partition_keys(rinfo, expr1, rel1, pkclauses1);
+		match_expr_to_partition_keys(rinfo, expr2, rel2, pkclauses2);
+	}
+
+	/*
+	 * If every pair of partition key from either of the joining relation has
+	 * at least one equi-join clause associated with it, we have an equi-join
+	 * between all corresponding partition keys.
+	 */
+	for (cnt_pks = 0; cnt_pks < num_pks; cnt_pks++)
+	{
+		List *li = list_intersection(pkclauses1[cnt_pks],
+									 pkclauses2[cnt_pks]);
+		if (!li)
+			return false;
+
+		/* We don't need the actual intersection list. */
+		list_free(li);
+	}
+	return true;
+}
+
+/*
+ * match_expr_to_partition_keys
+ *
+ * Find the partition key which is same as the given expression. If we are able
+ * to find one, add the clause to the list of clauses associated with that
+ * partition key. It is assumed that the given expression is part of the given
+ * clause.
+ *
+ * TODO: Since a given partition key appears only once in the partition by
+ * clause, it should be possible to return the position of the matching
+ * partition key and let the caller handle association of clauses. If we do so,
+ * we will be able to discard the clauses associated with different partition
+ * keys and just remember whether a pair of partition keys has associated
+ * clause or not (rather than remembering the whole list of clauses).
+ */
+static void
+match_expr_to_partition_keys(RestrictInfo *rinfo, Expr *expr, RelOptInfo *rel,
+							 List **pkclauses)
+{
+	PartitionOptInfo *part_info = rel->part_info;
+	int		cnt_pks;
+
+	/*
+	 * Remove the relabel decoration. We can assume that there is at most one
+	 * RelabelType node; eval_const_expressions() will have simplied if more
+	 * than one.
+	 */
+	if (IsA(expr, RelabelType))
+		expr = (Expr *) ((RelabelType *) expr)->arg;
+
+	for (cnt_pks = 0; cnt_pks < part_info->partnatts; cnt_pks++)
+	{
+		List	 *pkexprs = part_info->partexprs[cnt_pks];
+		ListCell *lc;
+
+		foreach(lc, pkexprs)
+		{
+			Expr *pkexpr = lfirst(lc);
+			if (equal(pkexpr, expr))
+			{
+				pkclauses[cnt_pks] = list_append_unique(pkclauses[cnt_pks],
+														rinfo);
+				break;
+			}
+		}
+	}
+}
+
+/*
+ * Add append paths for the join relation.
+ *
+ * Like set_append_rel_pathlist, this function considers pair-wise partition
+ * join paths with parameterization and pathkeys.
+ *
+ * TODO: right now the function just picks up the cheapest path from each of
+ * the partitions and creates an append path with those.
+ *
+ * TODO: may be we should consider splitting set_append_rel_pathlist() so that
+ * it can be used for both inheritance and partitioning.
+ */
+static void
+add_append_paths_to_joinrel(RelOptInfo *joinrel)
+{
+	PartitionOptInfo	*part_info = joinrel->part_info;
+	RelOptInfo **part_rels = part_info->part_rels;
+	int		nparts;
+	int		cnt_parts;
+	List   *part_paths = NIL;
+
+	Assert(part_info);
+
+	nparts = part_info->nparts;
+
+	for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++)
+	{
+		RelOptInfo *part_rel = part_rels[cnt_parts];
+
+		/* Find the cheapest path for partition relation. */
+		set_cheapest(part_rel);
+
+		/* We don't expect any parameterization here. */
+		Assert(!part_rel->cheapest_total_path->param_info);
+
+		/*
+		 * Instead of lappend, we should use accumulate_append_subpath() to
+		 * pull up the paths in underlying append.
+		 */
+		part_paths = lappend(part_paths, part_rel->cheapest_total_path);
+	}
+
+	add_path(joinrel, (Path *) create_append_path(joinrel, part_paths, NULL,
+												  0));
+	return;
+}
 
 /*
  * have_join_order_restriction
  *		Detect whether the two relations should be joined to satisfy
  *		a join-order restriction arising from special or lateral joins.
  *
  * In practice this is always used with have_relevant_joinclause(), and so
  * could be merged with that function, but it seems clearer to separate the
  * two concerns.  We need this test because there are degenerate cases where
  * a clauseless join must be performed to satisfy join-order restrictions.
@@ -1242,10 +1608,180 @@ restriction_is_constant_false(List *restrictlist, bool only_pushed_down)
 
 			/* constant NULL is as good as constant FALSE for our purposes */
 			if (con->constisnull)
 				return true;
 			if (!DatumGetBool(con->constvalue))
 				return true;
 		}
 	}
 	return false;
 }
+
+/*
+ * have_same_partition_lists
+ *
+ * For given list partitioned relations, return true if lists for all the
+ * partitions of both the relations match. Return false otherwise.
+ */
+static bool
+have_same_partition_lists(PartitionOptInfo *part_info1,
+						  PartitionOptInfo *part_info2)
+{
+	int		cnt_parts;
+
+	Assert(part_info1->strategy == PARTITION_STRAT_LIST &&
+		   part_info2->strategy == PARTITION_STRAT_LIST);
+
+	Assert(part_info1->nparts == part_info2->nparts);
+
+	/* List partition has only one partition key. */
+	Assert(part_info1->partnatts == 1 && part_info2->partnatts == 1);
+
+	for (cnt_parts = 0; cnt_parts < part_info1->nparts; cnt_parts++)
+	{
+		PartitionListBound *list1 = part_info1->lists[cnt_parts];
+		PartitionListBound *list2 = part_info2->lists[cnt_parts];
+		int		cnt;
+
+		/*
+		 * If number of items in the lists do not match or the one of the lists
+		 * contains NULL value and other doesn't, lists do not match.
+		 *
+		 * TODO: This condition can be relaxed to accomodate subset of items,
+		 * but probably not in the first cut.
+		 */
+		if (list1->contains_null != list2->contains_null ||
+			list1->nvalues != list2->nvalues)
+			return false;
+
+		/* Compare actual values in the list. */
+		for (cnt = 0; cnt < list1->nvalues; cnt++)
+		{
+			Datum	cmpval;
+
+			cmpval = FunctionCall2Coll(&part_info1->partsupfunc[0],
+									   part_info1->tcinfo->typcoll[0],
+									   list1->values[cnt],
+									   list2->values[cnt]);
+			if (DatumGetInt32(cmpval))
+				return false;
+		}
+	}
+
+	/* Ok, everything matches, return true. */
+	return true;
+}
+
+/*
+ * have_same_partition_bounds
+ *
+ * For given partitioned relations, return true if the bounds of all the
+ * partitions of the both the relations match. Return false otherwise.
+ */
+static bool
+have_same_partition_bounds(PartitionOptInfo *part_info1,
+						   PartitionOptInfo *part_info2)
+{
+	int		cnt_parts;
+	Assert(part_info1->strategy == PARTITION_STRAT_RANGE &&
+		   part_info2->strategy == PARTITION_STRAT_RANGE);
+
+	Assert(part_info1->nparts == part_info2->nparts);
+
+	for (cnt_parts = 0; cnt_parts < part_info1->nparts; cnt_parts++)
+	{
+		PartitionRangeBound *lower1 = part_info1->rangelowers[cnt_parts];
+		PartitionRangeBound *lower2 = part_info2->rangelowers[cnt_parts];
+		PartitionRangeBound *upper1 = part_info1->rangeuppers[cnt_parts];
+		PartitionRangeBound *upper2 = part_info2->rangeuppers[cnt_parts];
+
+		if (!compare_partition_bounds(part_info1, lower1, lower2) ||
+			!compare_partition_bounds(part_info1, upper1, upper2))
+			return false;
+	}
+
+	/* Ok, everything matches. */
+	return true;
+}
+
+/*
+ * compare_partition_bounds
+ *
+ * Return true if given bound specifications match.
+ */
+static bool
+compare_partition_bounds(PartitionOptInfo *part_info,
+						 PartitionRangeBound *bound1,
+						 PartitionRangeBound *bound2)
+{
+	int		cnt_pks;
+	Datum  *val1 = bound1->val;
+	Datum  *val2 = bound2->val;
+
+	if (bound1->infinite != bound2->infinite ||
+		bound1->inclusive != bound2->inclusive	||
+		bound1->lower != bound2->lower)
+		return false;
+
+	/*
+	 * TODO:
+	 * Following part is common with compare_range_keys(), but that takes
+	 * PartitionKey as argument, which we don't have here.
+	 */
+	for (cnt_pks = 0; cnt_pks < part_info->partnatts; cnt_pks++)
+	{
+		Datum	cmpval;
+
+		cmpval = FunctionCall2Coll(&part_info->partsupfunc[cnt_pks],
+								   part_info->tcinfo->typcoll[cnt_pks],
+								   val1[cnt_pks], val2[cnt_pks]);
+
+		if (DatumGetInt32(cmpval))
+			return false;
+	}
+
+	/* Ok, everything matches. */
+	return true;
+}
+
+/*
+ * partkey_types_match
+ *
+ * Returns true if the partition key datatype and collation match for given
+ * partitioned relations. Returns false otherwise.
+ */
+static bool
+partkey_types_match(PartitionOptInfo *part_info1, PartitionOptInfo *part_info2)
+{
+	int		cnt_pks;
+	Oid	   *partopfamily1 = part_info1->partopfamily;
+	Oid	   *partopfamily2 = part_info2->partopfamily;
+	Oid	   *partopcintype1 = part_info1->partopcintype;
+	Oid	   *partopcintype2 = part_info2->partopcintype;
+	FmgrInfo *partsupfunc1 = part_info1->partsupfunc;
+	FmgrInfo *partsupfunc2 = part_info2->partsupfunc;
+	KeyTypeCollInfo *tcinfo1 = part_info1->tcinfo;
+	KeyTypeCollInfo *tcinfo2 = part_info2->tcinfo;
+
+	if (part_info1->partnatts != part_info1->partnatts)
+		return false;
+
+	for (cnt_pks = 0; cnt_pks < part_info1->partnatts; cnt_pks++)
+	{
+		/*
+		 * It suffices to check the OID of support function as it always has
+		 * two arguemnts 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 (partopfamily1[cnt_pks] != partopfamily2[cnt_pks] ||
+			partopcintype1[cnt_pks] != partopcintype2[cnt_pks] ||
+			partsupfunc1[cnt_pks].fn_oid != partsupfunc2[cnt_pks].fn_oid ||
+			tcinfo1->typid[cnt_pks] != tcinfo2->typid[cnt_pks] ||
+			tcinfo1->typmod[cnt_pks] != tcinfo2->typmod[cnt_pks] ||
+			tcinfo1->typcoll[cnt_pks] != tcinfo2->typcoll[cnt_pks])
+			return false;
+	}
+
+	/* Everything matches. */
+	return true;
+}
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 9e5b60e..6cd8d8c 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -339,20 +339,25 @@ create_plan(PlannerInfo *root, Path *best_path)
 
 /*
  * create_plan_recurse
  *	  Recursive guts of create_plan().
  */
 static Plan *
 create_plan_recurse(PlannerInfo *root, Path *best_path, int flags)
 {
 	Plan	   *plan;
 
+	/* TODO: remove this code to continue partition-wise join planning. */
+	if (best_path->parent->reloptkind == RELOPT_JOINREL &&
+		best_path->parent->part_info)
+		elog(ERROR, "the relation was considered for partition-wise join, which is not supported right now.");
+
 	switch (best_path->pathtype)
 	{
 		case T_SeqScan:
 		case T_SampleScan:
 		case T_IndexScan:
 		case T_IndexOnlyScan:
 		case T_BitmapHeapScan:
 		case T_TidScan:
 		case T_SubqueryScan:
 		case T_FunctionScan:
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 95aca08..739ce3d 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1202,21 +1202,28 @@ get_relation_constraints(PlannerInfo *root,
 					ntest->nulltesttype = IS_NOT_NULL;
 					ntest->argisrow = type_is_rowtype(att->atttypid);
 					ntest->location = -1;
 					result = lappend(result, ntest);
 				}
 			}
 		}
 	}
 
 	/* Append partition check quals, if any */
-	pcqual = RelationGetPartitionCheckQual(relation);
+	/*
+	 * TODO the copyObject wrapper has been added by me to avoid changing
+	 * partitioned copy of cache. This should be fixed by Amit in his original
+	 * patches and mostly in get_check_qual_for_range() or
+	 * get_relation_constraints(). Once it's fixed their, remove the copyObject
+	 * wrapper.
+	 */
+	pcqual = copyObject(RelationGetPartitionCheckQual(relation));
 	if (pcqual)
 	{
 		/* Fix Vars to have the desired varno */
 		if (varno != 1)
 			ChangeVarNodes((Node *) pcqual, 1, varno, 0);
 
 		result = list_concat(result, pcqual);
 	}
 
 	heap_close(relation, NoLock);
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index a0a284b..b310c4d 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -8,31 +8,35 @@
  *
  *
  * IDENTIFICATION
  *	  src/backend/optimizer/util/relnode.c
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
 #include "miscadmin.h"
+#include "catalog/heap.h"
 #include "catalog/pg_class.h"
 #include "foreign/foreign.h"
+#include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/placeholder.h"
 #include "optimizer/plancat.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
 #include "utils/hsearch.h"
+#include "utils/rel.h"
 
 
 typedef struct JoinHashEntry
 {
 	Relids		join_relids;	/* hash key --- MUST BE FIRST */
 	RelOptInfo *join_rel;
 } JoinHashEntry;
 
 static void build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
 					RelOptInfo *input_rel);
@@ -42,20 +46,22 @@ static List *build_joinrel_restrictlist(PlannerInfo *root,
 						   RelOptInfo *inner_rel);
 static void build_joinrel_joinlist(RelOptInfo *joinrel,
 					   RelOptInfo *outer_rel,
 					   RelOptInfo *inner_rel);
 static List *subbuild_joinrel_restrictlist(RelOptInfo *joinrel,
 							  List *joininfo_list,
 							  List *new_restrictlist);
 static List *subbuild_joinrel_joinlist(RelOptInfo *joinrel,
 						  List *joininfo_list,
 						  List *new_joininfo);
+static PartitionOptInfo *build_partition_info(PlannerInfo *root,
+							   RelOptInfo *rel);
 
 
 /*
  * setup_simple_rel_arrays
  *	  Prepare the arrays we use for quickly accessing base relations.
  */
 void
 setup_simple_rel_arrays(PlannerInfo *root)
 {
 	Index		rti;
@@ -190,27 +196,33 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 
 		userid = OidIsValid(rte->checkAsUser) ? rte->checkAsUser : GetUserId();
 		rel->umid = GetUserMappingId(userid, rel->serverid, true);
 	}
 	else
 		rel->umid = InvalidOid;
 
 	/* Save the finished struct in the query's simple_rel_array */
 	root->simple_rel_array[relid] = rel;
 
+	/* Get the partitioning information, if any. */
+	if (rte->rtekind == RTE_RELATION)
+		rel->part_info = build_partition_info(root, rel);
+	else
+		rel->part_info = NULL;
+
 	/*
-	 * If this rel is an appendrel parent, recurse to build "other rel"
-	 * RelOptInfos for its children.  They are "other rels" because they are
-	 * not in the main join tree, but we will need RelOptInfos to plan access
-	 * to them.
+	 * If this rel is an unpartitioned appendrel parent, recurse to build
+	 * "other rel" RelOptInfos for its children.  They are "other rels" because
+	 * they are not in the main join tree, but we will need RelOptInfos to plan
+	 * access to them.
 	 */
-	if (rte->inh)
+	if (!rel->part_info && rte->inh)
 	{
 		ListCell   *l;
 
 		foreach(l, root->append_rel_list)
 		{
 			AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l);
 
 			/* append_rel_list contains all append rels; ignore others */
 			if (appinfo->parent_relid != relid)
 				continue;
@@ -381,21 +393,34 @@ build_join_rel(PlannerInfo *root,
 														   joinrel,
 														   outer_rel,
 														   inner_rel);
 		return joinrel;
 	}
 
 	/*
 	 * Nope, so make one.
 	 */
 	joinrel = makeNode(RelOptInfo);
-	joinrel->reloptkind = RELOPT_JOINREL;
+
+	/*
+	 * A join between partitions or child tables is different from join between
+	 * regular tables.
+	 */
+	if (IS_OTHER_REL(outer_rel->reloptkind))
+	{
+		/* A partition table can be joined only with other partition table. */
+		Assert(IS_OTHER_REL(inner_rel->reloptkind));
+		joinrel->reloptkind = RELOPT_OTHER_JOINREL;
+	}
+	else
+		joinrel->reloptkind = RELOPT_JOINREL;
+
 	joinrel->relids = bms_copy(joinrelids);
 	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;
@@ -540,21 +565,21 @@ build_join_rel(PlannerInfo *root,
 		Assert(!found);
 		hentry->join_rel = joinrel;
 	}
 
 	/*
 	 * Also, if dynamic-programming join search is active, add the new joinrel
 	 * to the appropriate sublist.  Note: you might think the Assert on number
 	 * of members should be for equality, but some of the level 1 rels might
 	 * have been joinrels already, so we can only assert <=.
 	 */
-	if (root->join_rel_level)
+	if (root->join_rel_level && joinrel->reloptkind != RELOPT_OTHER_JOINREL)
 	{
 		Assert(root->join_cur_level > 0);
 		Assert(root->join_cur_level <= bms_num_members(joinrel->relids));
 		root->join_rel_level[root->join_cur_level] =
 			lappend(root->join_rel_level[root->join_cur_level], joinrel);
 	}
 
 	return joinrel;
 }
 
@@ -1317,10 +1342,179 @@ get_appendrel_parampathinfo(RelOptInfo *appendrel, Relids required_outer)
 
 	/* Else build the ParamPathInfo */
 	ppi = makeNode(ParamPathInfo);
 	ppi->ppi_req_outer = required_outer;
 	ppi->ppi_rows = 0;
 	ppi->ppi_clauses = NIL;
 	appendrel->ppilist = lappend(appendrel->ppilist, ppi);
 
 	return ppi;
 }
+
+/*
+ * build_partition_info
+ *
+ * Retrieves partitioning information for given relation.
+ *
+ * The function also builds the RelOptInfos of the partitions recursively.
+ * TODO: complete the prologue.
+ */
+static PartitionOptInfo *
+build_partition_info(PlannerInfo *root, RelOptInfo *rel)
+{
+	Relation	relation;
+	PartitionKey	part_key;
+	PartitionDesc	part_desc;
+	PartitionOptInfo *part_info;
+	RangeTblEntry  *rte;
+	ListCell	*lc;
+	int		num_pkexprs;
+	int		cnt_pke;
+	int		cnt_parts;
+	Expr   *pkexpr;
+	Index	top_parent_relid;
+
+	/* The given relation should be simple relation. */
+	Assert(rel->reloptkind == RELOPT_BASEREL ||
+		   rel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+	Assert(rel->relid != 0);
+
+	rte = root->simple_rte_array[rel->relid];
+	Assert(rte);
+
+	/*
+	 * For multi-level partitioned tables the children themselves can be
+	 * partitioned. In such a case, partitions at higher levels will have
+	 * parent relid of the topmost relation.
+	 */
+	if (rel->reloptkind == RELOPT_OTHER_MEMBER_REL)
+	{
+		RelOptInfo *parent_rel = find_childrel_top_parent(root, rel);
+		RangeTblEntry *parent_rte;
+
+		top_parent_relid = parent_rel->relid;
+		parent_rte = root->simple_rte_array[top_parent_relid];
+
+		/*
+		 * A relation appearing in its own partition hierarchy does not have
+		 * PartitionOptInfo in its RelOptInfo, lest it creates recursively
+		 * infinite chain of RelOptInfos and PartitionOptInfos.
+		 */
+		if (parent_rte->relid == rte->relid)
+			return NULL;
+	}
+	else
+		top_parent_relid = 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 = heap_open(rte->relid, NoLock);
+	part_desc = RelationGetPartitionDesc(relation);
+	part_key = RelationGetPartitionKey(relation);
+
+	/* Nothing to do for an unpartitioned relation. */
+	if (!part_desc || !part_key)
+	{
+		heap_close(relation, NoLock);
+		return NULL;
+	}
+
+	part_info = makeNode(PartitionOptInfo);
+
+	/* Store partition descriptor information. */
+	part_info->nparts = part_desc->nparts;
+	/* TODO: Should we copy the contents of the these arrays? */
+	part_info->lists = part_desc->lists;
+	part_info->rangelowers = part_desc->rangelowers;
+	part_info->rangeuppers = part_desc->rangeuppers;
+
+	/* Store partition key information. */
+	part_info->strategy = part_key->strategy;
+	part_info->partnatts = part_key->partnatts;
+	num_pkexprs = part_info->partnatts;
+	/* TODO: Should we copy the contents of these arrays? */
+	part_info->partopfamily = part_key->partopfamily;
+	part_info->partopcintype = part_key->partopcintype;
+	part_info->partsupfunc = part_key->partsupfunc;
+	part_info->tcinfo = part_key->tcinfo;
+
+	/* Store partition keys as single elements lists. */
+	part_info->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];
+
+		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(rel->relid, attno, att_tup->atttypid,
+									  att_tup->atttypmod,
+									  att_tup->attcollation, 0);
+		}
+		else
+		{
+			if (!lc)
+				elog(ERROR, "wrong number of partition key expressions");
+			pkexpr = (Expr *) copyObject(lfirst(lc));
+			ChangeVarNodes((Node *) pkexpr, 1, rel->relid, 0);
+			lc = lnext(lc);
+		}
+
+		part_info->partexprs[cnt_pke] = list_make1(pkexpr);
+	}
+
+	/* Find RelOptInfo of the partitions. */
+	part_info->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) *
+												 part_info->nparts);
+	foreach(lc, root->append_rel_list)
+	{
+		AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc);
+		int			childRTindex = appinfo->child_relid;
+		RangeTblEntry *childRTE = root->simple_rte_array[childRTindex];
+		RelOptInfo *childrel = root->simple_rel_array[childRTindex];
+
+		/* append_rel_list contains all append rels; ignore others */
+		if (appinfo->parent_relid != top_parent_relid)
+			continue;
+
+		/* If we haven't created a RelOptInfo aleady, create one. */
+		if (!childrel)
+			childrel = build_simple_rel(root, childRTindex,
+									RELOPT_OTHER_MEMBER_REL);
+		/*
+		 * OIDs of the partitions are arranged to match the partition bounds or
+		 * list in corresponding arrays. Arrange RelOptInfo's of partitions in
+		 * the same fashion.
+		 */
+		for (cnt_parts = 0; cnt_parts < part_info->nparts; cnt_parts++)
+		{
+			if (part_desc->oids[cnt_parts] == childRTE->relid)
+			{
+				/* Every partition can be seen only once. */
+				Assert(!part_info->part_rels[cnt_parts]);
+				part_info->part_rels[cnt_parts] = childrel;
+
+				break;
+			}
+		}
+	}
+
+	heap_close(relation, NoLock);
+
+	/* We must have found RelOptInfos of all the partitions. */
+	for (cnt_parts = 0; cnt_parts < part_info->nparts; cnt_parts++)
+		Assert(part_info->part_rels[cnt_parts]);
+
+	return part_info;
+}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 60148b8..2beb0ec 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -870,20 +870,29 @@ static struct config_bool ConfigureNamesBool[] =
 	},
 	{
 		{"enable_hashjoin", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of hash join plans."),
 			NULL
 		},
 		&enable_hashjoin,
 		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,
 			gettext_noop("Enables genetic query optimization."),
 			gettext_noop("This algorithm attempts to do planning without "
 						 "exhaustive searching.")
 		},
 		&enable_geqo,
 		true,
 		NULL, NULL, NULL
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 365073b..b17de61 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -217,20 +217,21 @@ typedef enum NodeTag
 	T_DomainConstraintState,
 
 	/*
 	 * TAGS FOR PLANNER NODES (relation.h)
 	 */
 	T_PlannerInfo = 500,
 	T_PlannerGlobal,
 	T_RelOptInfo,
 	T_IndexOptInfo,
 	T_ForeignKeyOptInfo,
+	T_PartitionOptInfo,
 	T_ParamPathInfo,
 	T_Path,
 	T_IndexPath,
 	T_BitmapHeapPath,
 	T_BitmapAndPath,
 	T_BitmapOrPath,
 	T_TidPath,
 	T_SubqueryScanPath,
 	T_ForeignPath,
 	T_CustomPath,
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 0b5cb9e..bffd312 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -12,20 +12,21 @@
  *-------------------------------------------------------------------------
  */
 #ifndef RELATION_H
 #define RELATION_H
 
 #include "access/sdir.h"
 #include "lib/stringinfo.h"
 #include "nodes/params.h"
 #include "nodes/parsenodes.h"
 #include "storage/block.h"
+#include "catalog/partition.h"
 
 
 /*
  * Relids
  *		Set of relation identifiers (indexes into the rangetable).
  */
 typedef Bitmapset *Relids;
 
 /*
  * When looking for a "cheapest path", this enum specifies whether we want
@@ -347,20 +348,26 @@ typedef struct PlannerInfo
  * is present in the query join tree but the members are not.  The member
  * RTEs and otherrels are used to plan the scans of the individual tables or
  * subqueries of the append set; then the parent baserel is given Append
  * and/or MergeAppend paths comprising the best paths for the individual
  * member rels.  (See comments for AppendRelInfo for more information.)
  *
  * At one time we also made otherrels to represent join RTEs, for use in
  * 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 pair-wise joins between partitions of
+ * partitioned tables. These relations are not added to join_rel_level lists
+ * as they are not joined directly by the dynamic programming algorithm.
+ * Adding these two join_rel_level list also means that top level list has more
+ * than one join relation, which is symantically incorrect.
+ *
  * 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
  * fields always hold Paths showing ways to do that processing step.
  *
  * Lastly, there is a RelOptKind for "dead" relations, which are base rels
  * that we have proven we don't need to join after all.
  *
  * Parts of this data structure are specific to various scan and join
  * mechanisms.  It didn't seem worth creating new node types for them.
@@ -465,24 +472,31 @@ typedef struct PlannerInfo
  * We store baserestrictcost in the RelOptInfo (for base relations) because
  * we know we will need it at least once (to price the sequential scan)
  * and may need it multiple times to price index scans.
  *----------
  */
 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)
+
+typedef struct PartitionOptInfo PartitionOptInfo;
+
 typedef struct RelOptInfo
 {
 	NodeTag		type;
 
 	RelOptKind	reloptkind;
 
 	/* all relations included in this RelOptInfo */
 	Relids		relids;			/* set of base relids (rangetable indexes) */
 
 	/* size estimates generated by planner */
@@ -536,20 +550,26 @@ typedef struct RelOptInfo
 	struct FdwRoutine *fdwroutine;
 	void	   *fdw_private;
 
 	/* used by various scans and joins: */
 	List	   *baserestrictinfo;		/* RestrictInfo structures (if base
 										 * rel) */
 	QualCost	baserestrictcost;		/* cost of evaluating the above */
 	List	   *joininfo;		/* RestrictInfo structures for join clauses
 								 * involving this rel */
 	bool		has_eclass_joins;		/* T means joininfo is incomplete */
+
+	/* For partitioned relations, joins or base relations. NULL otherwise. */
+	/*
+	 * TODO: Notice recursive usage of RelOptInfo.
+	 */
+	PartitionOptInfo	*part_info;
 } RelOptInfo;
 
 /*
  * IndexOptInfo
  *		Per-index information for planning/optimization
  *
  *		indexkeys[], indexcollations[], opfamily[], and opcintype[]
  *		each have ncolumns entries.
  *
  *		sortopfamily[], reverse_sort[], and nulls_first[] likewise have
@@ -650,20 +670,46 @@ typedef struct ForeignKeyOptInfo
 	/* Derived info about whether FK's equality conditions match the query: */
 	int			nmatched_ec;	/* # of FK cols matched by ECs */
 	int			nmatched_rcols; /* # of FK cols matched by non-EC rinfos */
 	int			nmatched_ri;	/* total # of non-EC rinfos matched to FK */
 	/* Pointer to eclass matching each column's condition, if there is one */
 	struct EquivalenceClass *eclass[INDEX_MAX_KEYS];
 	/* List of non-EC RestrictInfos matching each column's condition */
 	List	   *rinfos[INDEX_MAX_KEYS];
 } ForeignKeyOptInfo;
 
+/*
+ * PartitionOptInfo
+ * 		Partitioning information for planning/optimization
+ *
+ * TODO: complete the comment
+ */
+typedef struct PartitionOptInfo
+{
+	NodeTag		type;
+
+	/* Information about partitions */
+	int			nparts;		/* number of partitions */
+	RelOptInfo			  **part_rels;	/* RelOptInfos of partitions */
+	PartitionListBound	  **lists;		/* list bounds */
+	PartitionRangeBound	  **rangelowers;	/* range lower bounds */
+	PartitionRangeBound	  **rangeuppers;	/* range upper bounds */
+
+	/* Information about partition keys */
+	char		strategy;		/* partition strategy */
+	int16		partnatts;		/* number of partition attributes */
+	List	  **partexprs;		/* partition key expressions. */
+	Oid		   *partopfamily;	/* OIDs of operator families */
+	Oid		   *partopcintype;	/* OIDs of opclass declared input data types */
+	FmgrInfo   *partsupfunc;	/* lookup info for support funcs */
+	KeyTypeCollInfo *tcinfo;	/* type and collation info (all columns) */
+} PartitionOptInfo;
 
 /*
  * EquivalenceClasses
  *
  * Whenever we can determine that a mergejoinable equality clause A = B is
  * not delayed by any outer join, we create an EquivalenceClass containing
  * the expressions A and B to record this knowledge.  If we later find another
  * equivalence B = C, we add C to the existing EquivalenceClass; this may
  * require merging two existing EquivalenceClasses.  At the end of the qual
  * distribution process, we have sets of values that are known all transitively
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
@@ -59,20 +59,21 @@ extern bool enable_seqscan;
 extern bool enable_indexscan;
 extern bool enable_indexonlyscan;
 extern bool enable_bitmapscan;
 extern bool enable_tidscan;
 extern bool enable_sort;
 extern bool enable_hashagg;
 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);
 extern double index_pages_fetched(double tuples_fetched, BlockNumber pages,
 					double index_pages, PlannerInfo *root);
 extern void cost_seqscan(Path *path, PlannerInfo *root, RelOptInfo *baserel,
 			 ParamPathInfo *param_info);
 extern void cost_samplescan(Path *path, PlannerInfo *root, RelOptInfo *baserel,
 				ParamPathInfo *param_info);
 extern void cost_index(IndexPath *path, PlannerInfo *root,
diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out
new file mode 100644
index 0000000..a41f8b6
--- /dev/null
+++ b/src/test/regress/expected/partition_join.out
@@ -0,0 +1,1365 @@
+--
+-- PARTITION_JOIN
+-- Test partition-wise join between partitioned tables
+--
+-- TODO: We need to add tests for SEMI-ANTI joins.
+-- TODO: tests with placeholder vars
+--
+-- partitioned by a single column
+--
+CREATE TABLE pt1 (a int, b int, c varchar) PARTITION BY RANGE(a);
+CREATE TABLE pt1_p1 PARTITION OF pt1 FOR VALUES START (0) END (250);
+CREATE TABLE pt1_p2 PARTITION OF pt1 FOR VALUES START (250) END (500);
+CREATE TABLE pt1_p3 PARTITION OF pt1 FOR VALUES START (500) END (600);
+INSERT INTO pt1 SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 2) i;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE upt1 AS SELECT * FROM pt1;
+CREATE TABLE pt2 (a int, b int, c varchar) PARTITION BY RANGE(b);
+CREATE TABLE pt2_p1 PARTITION OF pt2 FOR VALUES START (0) END (250);
+CREATE TABLE pt2_p2 PARTITION OF pt2 FOR VALUES START (250) END (500);
+CREATE TABLE pt2_p3 PARTITION OF pt2 FOR VALUES START (500) END (600);
+INSERT INTO pt2 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.
+CREATE TABLE upt2 AS SELECT * FROM pt2;
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1 t1, pt2 t2 WHERE t1.a = t2.b AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1 t1, pt2 t2 WHERE t1.a = t2.b AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM upt1 t1, upt2 t2 WHERE t1.a = t2.b AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+  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 pt1 t1 LEFT JOIN pt2 t2 ON t1.a = t2.b WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1 t1 LEFT JOIN pt2 t2 ON t1.a = t2.b WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM upt1 t1 LEFT JOIN upt2 t2 ON t1.a = t2.b WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+  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 pt1 t1 RIGHT JOIN pt2 t2 ON t1.a = t2.b WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 2) and (b 1) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1 t1 RIGHT JOIN pt2 t2 ON t1.a = t2.b WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 2) and (b 1) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM upt1 t1 RIGHT JOIN upt2 t2 ON t1.a = t2.b WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 2) and (b 1) is NOT considered for partition-wise join.
+  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 pt1 WHERE pt1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM pt2 WHERE pt2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 4) and (b 5) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM pt1 WHERE pt1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM pt2 WHERE pt2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 4) and (b 5) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM upt1 t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM upt2 t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 4) and (b 5) is NOT considered for partition-wise join.
+  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)
+
+--
+-- partitioned by expression
+--
+CREATE TABLE pt1_e (a int, b int, c varchar) PARTITION BY RANGE(((a + b)/2));
+CREATE TABLE pt1_e_p1 PARTITION OF pt1_e FOR VALUES START (0) END (250);
+CREATE TABLE pt1_e_p2 PARTITION OF pt1_e FOR VALUES START (250) END (500);
+CREATE TABLE pt1_e_p3 PARTITION OF pt1_e FOR VALUES START (500) END (600);
+INSERT INTO pt1_e SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 2) i;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE upt1_e AS SELECT * FROM pt1_e;
+CREATE TABLE pt2_e (a int, b int, c varchar) PARTITION BY RANGE(((b + a)/2));
+CREATE TABLE pt2_e_p1 PARTITION OF pt2_e FOR VALUES START (0) END (250);
+CREATE TABLE pt2_e_p2 PARTITION OF pt2_e FOR VALUES START (250) END (500);
+CREATE TABLE pt2_e_p3 PARTITION OF pt2_e FOR VALUES START (500) END (600);
+INSERT INTO pt2_e 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.
+CREATE TABLE upt2_e AS SELECT * FROM pt2_e;
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1_e t1, pt2_e t2 WHERE (t1.a + t1.b)/2 = (t2.b + t2.a)/2 AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1_e t1, pt2_e t2 WHERE (t1.a + t1.b)/2 = (t2.b + t2.a)/2 AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM upt1_e t1, upt2_e t2 WHERE (t1.a + t1.b)/2 = (t2.b + t2.a)/2 AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+  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 pt1_e t1 LEFT JOIN pt2_e t2 ON (t1.a + t1.b)/2 = (t2.b + t2.a)/2 WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1_e t1 LEFT JOIN pt2_e t2 ON (t1.a + t1.b)/2 = (t2.b + t2.a)/2 WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM upt1_e t1 LEFT JOIN upt2_e t2 ON (t1.a + t1.b)/2 = (t2.b + t2.a)/2 WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+  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 pt1_e t1 RIGHT JOIN pt2_e t2 ON (t1.a + t1.b)/2 = (t2.b + t2.a)/2 WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 2) and (b 1) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1_e t1 RIGHT JOIN pt2_e t2 ON (t1.a + t1.b)/2 = (t2.b + t2.a)/2 WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 2) and (b 1) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM upt1_e t1 RIGHT JOIN upt2_e t2 ON (t1.a + t1.b)/2 = (t2.b + t2.a)/2 WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 2) and (b 1) is NOT considered for partition-wise join.
+  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 pt1_e WHERE pt1_e.a % 25 = 0) t1 FULL JOIN (SELECT * FROM pt2_e WHERE pt2_e.b % 25 = 0) t2 ON ((t1.a + t1.b)/2 = (t2.b + t2.a)/2) ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 4) and (b 5) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM pt1_e WHERE pt1_e.a % 25 = 0) t1 FULL JOIN (SELECT * FROM pt2_e WHERE pt2_e.b % 25 = 0) t2 ON ((t1.a + t1.b)/2 = (t2.b + t2.a)/2) ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 4) and (b 5) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM upt1_e t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM upt2_e t2 WHERE t2.b % 25 = 0) t2 ON ((t1.a + t1.b)/2 = (t2.b + t2.a)/2) ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 4) and (b 5) is NOT considered for partition-wise join.
+  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)
+
+--
+-- N-way join
+--
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM pt1 t1, pt2 t2, pt1_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;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 3) is considered for partition-wise join.
+NOTICE:  join between relations (b 2) and (b 3) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 3) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 3) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 2 3) and (b 1) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM pt1 t1, pt2 t2, pt1_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;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 3) is considered for partition-wise join.
+NOTICE:  join between relations (b 2) and (b 3) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 3) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 3) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 2 3) and (b 1) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM upt1 t1, upt2 t2, upt1_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;
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 3) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 2) and (b 3) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 3) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 1 3) and (b 2) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 2 3) and (b 1) is NOT considered for partition-wise join.
+  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 (pt1 t1 LEFT JOIN pt2 t2 ON t1.a = t2.b) LEFT JOIN pt1_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;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 4) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (pt1 t1 LEFT JOIN pt2 t2 ON t1.a = t2.b) LEFT JOIN pt1_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;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 4) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (upt1 t1 LEFT JOIN upt2 t2 ON t1.a = t2.b) LEFT JOIN upt1_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;
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 4) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 4) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 1 4) and (b 2) is NOT considered for partition-wise join.
+  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 (pt1 t1 LEFT JOIN pt2 t2 ON t1.a = t2.b) LEFT JOIN pt1_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;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 2) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 2 4) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (pt1 t1 LEFT JOIN pt2 t2 ON t1.a = t2.b) LEFT JOIN pt1_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;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 2) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 2 4) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (upt1 t1 LEFT JOIN upt2 t2 ON t1.a = t2.b) LEFT JOIN upt1_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;
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 2) and (b 4) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 4) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 2 4) is NOT considered for partition-wise join.
+  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 (pt1 t1 LEFT JOIN pt2 t2 ON t1.a = t2.b) RIGHT JOIN pt1_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;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 4) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (pt1 t1 LEFT JOIN pt2 t2 ON t1.a = t2.b) RIGHT JOIN pt1_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;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 4) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (upt1 t1 LEFT JOIN upt2 t2 ON t1.a = t2.b) RIGHT JOIN upt1_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;
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 4) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 4) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 1 4) and (b 2) is NOT considered for partition-wise join.
+  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 (pt1 t1 RIGHT JOIN pt2 t2 ON t1.a = t2.b) RIGHT JOIN pt1_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;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 2) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 4) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 2 4) and (b 1) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (pt1 t1 RIGHT JOIN pt2 t2 ON t1.a = t2.b) RIGHT JOIN pt1_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;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 2) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 4) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 2 4) and (b 1) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (upt1 t1 RIGHT JOIN upt2 t2 ON t1.a = t2.b) RIGHT JOIN upt1_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;
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 4) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 2) and (b 4) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 4) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 1 4) and (b 2) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 2 4) and (b 1) is NOT considered for partition-wise join.
+  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 ((SELECT * FROM pt1 WHERE pt1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM pt2 WHERE pt2.b % 25 = 0) t2 ON (t1.a = t2.b)) FULL JOIN (SELECT * FROM pt1_e WHERE pt1_e.a % 25 = 0) t3 ON (t1.a = (t3.a + t3.b)/2) ORDER BY t1.a, t2.b, t3.a + t3.b;
+NOTICE:  join between relations (b 6) and (b 7) is considered for partition-wise join.
+NOTICE:  join between relations (b 6 7) and (b 8) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM ((SELECT * FROM pt1 WHERE pt1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM pt2 WHERE pt2.b % 25 = 0) t2 ON (t1.a = t2.b)) FULL JOIN (SELECT * FROM pt1_e WHERE pt1_e.a % 25 = 0) t3 ON (t1.a = (t3.a + t3.b)/2) ORDER BY t1.a, t2.b, t3.a + t3.b;
+NOTICE:  join between relations (b 6) and (b 7) is considered for partition-wise join.
+NOTICE:  join between relations (b 6 7) and (b 8) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM ((SELECT * FROM upt1 WHERE upt1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM upt2 WHERE upt2.b % 25 = 0) t2 ON (t1.a = t2.b)) FULL JOIN (SELECT * FROM upt1_e WHERE upt1_e.a % 25 = 0) t3 ON (t1.a = (t3.a + t3.b)/2) ORDER BY t1.a, t2.b, t3.a + t3.b;
+NOTICE:  join between relations (b 6) and (b 7) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 6 7) and (b 8) is NOT considered for partition-wise join.
+  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)
+
+--
+-- partitioned by multiple columns
+--
+CREATE TABLE pt1_m (a int, b int, c varchar) PARTITION BY RANGE(a, ((a + b)/2));
+CREATE TABLE pt1_m_p1 PARTITION OF pt1_m FOR VALUES START (0, 0) END (250, 250);
+CREATE TABLE pt1_m_p2 PARTITION OF pt1_m FOR VALUES START (250, 250) END (500, 500);
+CREATE TABLE pt1_m_p3 PARTITION OF pt1_m FOR VALUES START (500, 500) END (600, 600);
+INSERT INTO pt1_m SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 2) i;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE upt1_m AS SELECT * FROM pt1_m;
+CREATE TABLE pt2_m (a int, b int, c varchar) PARTITION BY RANGE(((b + a)/2), b);
+CREATE TABLE pt2_m_p1 PARTITION OF pt2_m FOR VALUES START (0, 0) END (250, 250);
+CREATE TABLE pt2_m_p2 PARTITION OF pt2_m FOR VALUES START (250, 250) END (500, 500);
+CREATE TABLE pt2_m_p3 PARTITION OF pt2_m FOR VALUES START (500, 500) END (600, 600);
+INSERT INTO pt2_m 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.
+CREATE TABLE upt2_m AS SELECT * FROM pt2_m;
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1_m t1, pt2_m t2 WHERE t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2 AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1_m t1, pt2_m t2 WHERE t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2 AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM upt1_m t1, upt2_m t2 WHERE t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2 AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+  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 pt1_m t1 LEFT JOIN pt2_m t2 ON t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2 WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1_m t1 LEFT JOIN pt2_m t2 ON t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2 WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM upt1_m t1 LEFT JOIN upt2_m t2 ON t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2 WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+  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 pt1_m t1 RIGHT JOIN pt2_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;
+NOTICE:  join between relations (b 2) and (b 1) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1_m t1 RIGHT JOIN pt2_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;
+NOTICE:  join between relations (b 2) and (b 1) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM upt1_m t1 RIGHT JOIN upt2_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;
+NOTICE:  join between relations (b 2) and (b 1) is NOT considered for partition-wise join.
+  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 pt1_m WHERE pt1_m.a % 25 = 0) t1 FULL JOIN (SELECT * FROM pt2_m WHERE pt2_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;
+NOTICE:  join between relations (b 4) and (b 5) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM pt1_m WHERE pt1_m.a % 25 = 0) t1 FULL JOIN (SELECT * FROM pt2_m WHERE pt2_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;
+NOTICE:  join between relations (b 4) and (b 5) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM upt1_m t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM upt2_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;
+NOTICE:  join between relations (b 4) and (b 5) is NOT considered for partition-wise join.
+  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)
+
+--
+-- multi-leveled partitions
+--
+CREATE TABLE pt1_l (a int, b int, c varchar) PARTITION BY RANGE(a);
+CREATE TABLE pt1_l_p1 PARTITION OF pt1_l FOR VALUES START (0) END (250) PARTITION BY RANGE (b);
+CREATE TABLE pt1_l_p1_p1 PARTITION OF pt1_l_p1 FOR VALUES START (0) END (100);
+CREATE TABLE pt1_l_p1_p2 PARTITION OF pt1_l_p1 FOR VALUES START (100) END (250);
+CREATE TABLE pt1_l_p2 PARTITION OF pt1_l FOR VALUES START (250) END (500) PARTITION BY RANGE (c);
+CREATE TABLE pt1_l_p2_p1 PARTITION OF pt1_l_p2 FOR VALUES START ('0250') END ('0400');
+CREATE TABLE pt1_l_p2_p2 PARTITION OF pt1_l_p2 FOR VALUES START ('0400') END ('0500');
+CREATE TABLE pt1_l_p3 PARTITION OF pt1_l FOR VALUES START (500) END (600) PARTITION BY RANGE ((b + a));
+CREATE TABLE pt1_l_p3_p1 PARTITION OF pt1_l_p3 FOR VALUES START (1000) END (1100);
+CREATE TABLE pt1_l_p3_p2 PARTITION OF pt1_l_p3 FOR VALUES START (1100) END (1200);
+INSERT INTO pt1_l SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 2) i;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE upt1_l AS SELECT * FROM pt1_l;
+CREATE TABLE pt2_l (a int, b int, c varchar) PARTITION BY RANGE(b);
+CREATE TABLE pt2_l_p1 PARTITION OF pt2_l FOR VALUES START (0) END (250) PARTITION BY RANGE (a);
+CREATE TABLE pt2_l_p1_p1 PARTITION OF pt2_l_p1 FOR VALUES START (0) END (100);
+CREATE TABLE pt2_l_p1_p2 PARTITION OF pt2_l_p1 FOR VALUES START (100) END (250);
+CREATE TABLE pt2_l_p2 PARTITION OF pt2_l FOR VALUES START (250) END (500) PARTITION BY RANGE (c);
+CREATE TABLE pt2_l_p2_p1 PARTITION OF pt2_l_p2 FOR VALUES START ('0250') END ('0400');
+CREATE TABLE pt2_l_p2_p2 PARTITION OF pt2_l_p2 FOR VALUES START ('0400') END ('0500');
+CREATE TABLE pt2_l_p3 PARTITION OF pt2_l FOR VALUES START (500) END (600) PARTITION BY RANGE ((a + b));
+CREATE TABLE pt2_l_p3_p1 PARTITION OF pt2_l_p3 FOR VALUES START (1000) END (1100);
+CREATE TABLE pt2_l_p3_p2 PARTITION OF pt2_l_p3 FOR VALUES START (1100) END (1200);
+INSERT INTO pt2_l 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.
+CREATE TABLE upt2_l AS SELECT * FROM pt2_l;
+--
+-- TODO: a varchar = varchar comparison is converted into a comparison
+-- varchar::text = varchar::text. Thus the partition key on c does not match
+-- the equi-join condition on c. This is fine, but can be improved. Also, as
+-- long as we have this behaviour, we get to test partial partition-wise join
+-- between multi-level partitioned relations.
+--
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1_l t1, pt2_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;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 4) and (b 14) is considered for partition-wise join.
+NOTICE:  join between relations (b 5) and (b 15) is considered for partition-wise join.
+NOTICE:  join between relations (b 6) and (b 16) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1_l t1, pt2_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;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 4) and (b 14) is considered for partition-wise join.
+NOTICE:  join between relations (b 5) and (b 15) is considered for partition-wise join.
+NOTICE:  join between relations (b 6) and (b 16) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM upt1_l t1, upt2_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;
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+  a  |  c   |  b  |  c   
+-----+------+-----+------
+   0 | 0000 |   0 | 0000
+ 150 | 0150 | 150 | 0150
+ 300 | 0300 | 300 | 0300
+ 450 | 0450 | 450 | 0450
+(4 rows)
+
+-- TODO for outer joins the equality conditions do not appear in joininfo of
+-- children, which means that the equi-join clauses are not present for
+-- subpartitions. Hence the joins between subpartitions are not currently being
+-- considered for partition-wise join.
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1_l t1 LEFT JOIN pt2_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;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1_l t1 LEFT JOIN pt2_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;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM upt1_l t1 LEFT JOIN upt2_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;
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+  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 pt1_l t1 RIGHT JOIN pt2_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;
+NOTICE:  join between relations (b 2) and (b 1) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1_l t1 RIGHT JOIN pt2_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;
+NOTICE:  join between relations (b 2) and (b 1) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM upt1_l t1 RIGHT JOIN upt2_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;
+NOTICE:  join between relations (b 2) and (b 1) is NOT considered for partition-wise join.
+  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 pt1_l WHERE pt1_l.a % 25 = 0) t1 FULL JOIN (SELECT * FROM pt2_l WHERE pt2_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;
+NOTICE:  join between relations (b 4) and (b 5) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM pt1_l WHERE pt1_l.a % 25 = 0) t1 FULL JOIN (SELECT * FROM pt2_l WHERE pt2_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;
+NOTICE:  join between relations (b 4) and (b 5) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM upt1_l t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM upt2_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;
+NOTICE:  join between relations (b 4) and (b 5) is NOT considered for partition-wise join.
+  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;
+-- 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;
+-- 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;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+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;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+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;
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+  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;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+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;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+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;
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+  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;
+NOTICE:  join between relations (b 2) and (b 1) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+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;
+NOTICE:  join between relations (b 2) and (b 1) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+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;
+NOTICE:  join between relations (b 2) and (b 1) is NOT considered for partition-wise join.
+  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;
+NOTICE:  join between relations (b 4) and (b 5) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+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;
+NOTICE:  join between relations (b 4) and (b 5) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+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;
+NOTICE:  join between relations (b 4) and (b 5) is NOT considered for partition-wise join.
+  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)
+
+--
+-- 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;
+-- 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;
+-- 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, plt2_e t2 WHERE t1.a = t2.b AND ltrim(t1.c, 'A') = ltrim(t2.c, 'A') AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM plt1_e t1, plt2_e t2 WHERE t1.a = t2.b AND ltrim(t1.c, 'A') = ltrim(t2.c, 'A') AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM uplt1_e t1, uplt2_e t2 WHERE t1.a = t2.b AND ltrim(t1.c, 'A') = ltrim(t2.c, 'A') AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+  a  |   c   |  b  |   c   
+-----+-------+-----+-------
+   0 | A0000 |   0 | A0000
+ 150 | A0003 | 150 | A0003
+ 300 | A0006 | 300 | A0006
+ 450 | A0009 | 450 | A0009
+(4 rows)
+
+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;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+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;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+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;
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+  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 plt1_e t1 RIGHT JOIN plt2_e t2 ON t1.a = t2.b AND ltrim(t1.c, 'A') = ltrim(t2.c, 'A') WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 2) and (b 1) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM plt1_e t1 RIGHT JOIN plt2_e t2 ON t1.a = t2.b AND ltrim(t1.c, 'A') = ltrim(t2.c, 'A') WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 2) and (b 1) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c FROM uplt1_e t1 RIGHT JOIN uplt2_e t2 ON t1.a = t2.b AND ltrim(t1.c, 'A') = ltrim(t2.c, 'A') WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 2) and (b 1) is NOT considered for partition-wise join.
+  a  |   c   |  b  |   c   
+-----+-------+-----+-------
+   0 | A0000 |   0 | A0000
+ 150 | A0003 | 150 | A0003
+ 300 | A0006 | 300 | A0006
+ 450 | A0009 | 450 | A0009
+     |       |  75 | A0001
+     |       | 225 | A0004
+     |       | 375 | A0007
+     |       | 525 | A0010
+(8 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;
+NOTICE:  join between relations (b 4) and (b 5) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+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;
+NOTICE:  join between relations (b 4) and (b 5) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+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;
+NOTICE:  join between relations (b 4) and (b 5) is NOT considered for partition-wise join.
+  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 t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM plt1 t1, plt2 t2, plt1_e t3 WHERE t1.a = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c AND t1.a = t3.a AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 3) is considered for partition-wise join.
+NOTICE:  join between relations (b 2) and (b 3) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 3) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 3) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 2 3) and (b 1) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM plt1 t1, plt2 t2, plt1_e t3 WHERE t1.a = t2.b AND t1.c = t2.c AND t1.a = t3.a AND ltrim(t3.c, 'A') = t1.c AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 3) is considered for partition-wise join.
+NOTICE:  join between relations (b 2) and (b 3) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 3) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 3) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 2 3) and (b 1) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM uplt1 t1, uplt2 t2, uplt1_e t3 WHERE t1.a = t2.b AND t1.c = t2.c AND t1.a = t3.a AND ltrim(t3.c, 'A') = t1.c AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 3) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 2) and (b 3) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 3) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 1 3) and (b 2) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 2 3) and (b 1) is NOT considered for partition-wise join.
+  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
+(4 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;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 4) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+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;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 4) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+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;
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 4) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 4) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 1 4) and (b 2) is NOT considered for partition-wise join.
+  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;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 2) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 2 4) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+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;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 2) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 2 4) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+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;
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 2) and (b 4) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 4) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 2 4) is NOT considered for partition-wise join.
+  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 t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 4) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+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 t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 4) and (b 2) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+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 t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 4) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 4) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 1 4) and (b 2) is NOT considered for partition-wise join.
+  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 t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 2) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 4) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 2 4) and (b 1) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+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 t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+NOTICE:  join between relations (b 1) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 2) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 4) is considered for partition-wise join.
+NOTICE:  join between relations (b 1 4) and (b 2) is considered for partition-wise join.
+NOTICE:  join between relations (b 2 4) and (b 1) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+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 t1.a % 25 = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 1) and (b 4) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 2) and (b 4) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 1 2) and (b 4) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 1 4) and (b 2) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 2 4) and (b 1) is NOT considered for partition-wise join.
+  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
+(4 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;
+NOTICE:  join between relations (b 6) and (b 7) is considered for partition-wise join.
+NOTICE:  join between relations (b 6 7) and (b 8) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+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;
+NOTICE:  join between relations (b 6) and (b 7) is considered for partition-wise join.
+NOTICE:  join between relations (b 6 7) and (b 8) is considered for partition-wise join.
+ERROR:  the relation was considered for partition-wise join, which is not supported right now.
+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;
+NOTICE:  join between relations (b 6) and (b 7) is NOT considered for partition-wise join.
+NOTICE:  join between relations (b 6 7) and (b 8) is NOT considered for partition-wise join.
+  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)
+
+--
+-- negative testcases
+--
+CREATE TABLE pt1_n (a int, b int, c varchar) PARTITION BY RANGE(c);
+CREATE TABLE pt1_n_p1 PARTITION OF pt1_n FOR VALUES START ('0000') END ('0250');
+CREATE TABLE pt1_n_p2 PARTITION OF pt1_n FOR VALUES START ('0250') END ('0500');
+INSERT INTO pt1_n SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 499, 2) i;
+CREATE TABLE pt2_n (a int, b int, c text) PARTITION BY LIST(c);
+CREATE TABLE pt2_n_p1 PARTITION OF pt2_n FOR VALUES IN ('0000', '0003', '0004', '0010', '0006', '0007');
+CREATE TABLE pt2_n_p2 PARTITION OF pt2_n FOR VALUES IN ('0001', '0005', '0002', '0009', '0008', '0011');
+INSERT INTO pt2_n SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i;
+CREATE TABLE pt3_n (a int, b int, c text) PARTITION BY LIST(c);
+CREATE TABLE pt3_n_p1 PARTITION OF pt3_n FOR VALUES IN ('0000', '0004', '0006', '0007');
+CREATE TABLE pt3_n_p2 PARTITION OF pt3_n FOR VALUES IN ('0001', '0002', '0008', '0010');
+CREATE TABLE pt3_n_p3 PARTITION OF pt3_n FOR VALUES IN ('0003', '0005', '0009', '0011');
+INSERT INTO pt2_n SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i;
+CREATE TABLE pt4_n (a int, b int, c text) PARTITION BY RANGE(a);
+CREATE TABLE pt4_n_p1 PARTITION OF pt4_n FOR VALUES START (0) END (300);
+CREATE TABLE pt4_n_p2 PARTITION OF pt4_n FOR VALUES START (300) END (500);
+CREATE TABLE pt4_n_p3 PARTITION OF pt4_n FOR VALUES START (500) END (600);
+INSERT INTO pt4_n SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 2) i;
+-- 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 pt1 t1, pt4_n t2 WHERE t1.a = t2.a;
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+                 QUERY PLAN                  
+---------------------------------------------
+ Merge Join
+   Merge Cond: (t1.a = t2.a)
+   ->  Sort
+         Sort Key: t1.a
+         ->  Append
+               ->  Seq Scan on pt1 t1
+               ->  Seq Scan on pt1_p1 t1_1
+               ->  Seq Scan on pt1_p2 t1_2
+               ->  Seq Scan on pt1_p3 t1_3
+   ->  Sort
+         Sort Key: t2.a
+         ->  Append
+               ->  Seq Scan on pt4_n t2
+               ->  Seq Scan on pt4_n_p1 t2_1
+               ->  Seq Scan on pt4_n_p2 t2_2
+               ->  Seq Scan on pt4_n_p3 t2_3
+(16 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1 t1 FULL JOIN pt4_n t2 ON t1.a = t2.a;
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+                 QUERY PLAN                  
+---------------------------------------------
+ Merge Full Join
+   Merge Cond: (t1.a = t2.a)
+   ->  Sort
+         Sort Key: t1.a
+         ->  Append
+               ->  Seq Scan on pt1 t1
+               ->  Seq Scan on pt1_p1 t1_1
+               ->  Seq Scan on pt1_p2 t1_2
+               ->  Seq Scan on pt1_p3 t1_3
+   ->  Sort
+         Sort Key: t2.a
+         ->  Append
+               ->  Seq Scan on pt4_n t2
+               ->  Seq Scan on pt4_n_p1 t2_1
+               ->  Seq Scan on pt4_n_p2 t2_2
+               ->  Seq Scan on pt4_n_p3 t2_3
+(16 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 pt1 t1 LEFT JOIN pt2 t2 ON (t1.a < t2.b);
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+                QUERY PLAN                 
+-------------------------------------------
+ Nested Loop Left Join
+   Join Filter: (t1.a < t2.b)
+   ->  Append
+         ->  Seq Scan on pt1 t1
+         ->  Seq Scan on pt1_p1 t1_1
+         ->  Seq Scan on pt1_p2 t1_2
+         ->  Seq Scan on pt1_p3 t1_3
+   ->  Materialize
+         ->  Append
+               ->  Seq Scan on pt2 t2
+               ->  Seq Scan on pt2_p1 t2_1
+               ->  Seq Scan on pt2_p2 t2_2
+               ->  Seq Scan on pt2_p3 t2_3
+(13 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 pt1_m t1, pt2_m t2 WHERE t1.a = (t2.b + t2.a)/2 AND t1.a % 25 = 0;
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+                 QUERY PLAN                  
+---------------------------------------------
+ Hash Join
+   Hash Cond: (((t2.b + t2.a) / 2) = t1.a)
+   ->  Append
+         ->  Seq Scan on pt2_m t2
+         ->  Seq Scan on pt2_m_p1 t2_1
+         ->  Seq Scan on pt2_m_p2 t2_2
+         ->  Seq Scan on pt2_m_p3 t2_3
+   ->  Hash
+         ->  Append
+               ->  Seq Scan on pt1_m t1
+                     Filter: ((a % 25) = 0)
+               ->  Seq Scan on pt1_m_p1 t1_1
+                     Filter: ((a % 25) = 0)
+               ->  Seq Scan on pt1_m_p2 t1_2
+                     Filter: ((a % 25) = 0)
+               ->  Seq Scan on pt1_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 pt1_m t1 LEFT JOIN pt2_m t2 ON t1.a = t2.b WHERE t1.a % 25 = 0;
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+                 QUERY PLAN                  
+---------------------------------------------
+ Hash Right Join
+   Hash Cond: (t2.b = t1.a)
+   ->  Append
+         ->  Seq Scan on pt2_m t2
+         ->  Seq Scan on pt2_m_p1 t2_1
+         ->  Seq Scan on pt2_m_p2 t2_2
+         ->  Seq Scan on pt2_m_p3 t2_3
+   ->  Hash
+         ->  Append
+               ->  Seq Scan on pt1_m t1
+                     Filter: ((a % 25) = 0)
+               ->  Seq Scan on pt1_m_p1 t1_1
+                     Filter: ((a % 25) = 0)
+               ->  Seq Scan on pt1_m_p2 t1_2
+                     Filter: ((a % 25) = 0)
+               ->  Seq Scan on pt1_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 pt1_m t1 LEFT JOIN pt2_m t2 ON t1.c = t2.c WHERE t1.a % 25 = 0;
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+                 QUERY PLAN                  
+---------------------------------------------
+ Hash Right Join
+   Hash Cond: ((t2.c)::text = (t1.c)::text)
+   ->  Append
+         ->  Seq Scan on pt2_m t2
+         ->  Seq Scan on pt2_m_p1 t2_1
+         ->  Seq Scan on pt2_m_p2 t2_2
+         ->  Seq Scan on pt2_m_p3 t2_3
+   ->  Hash
+         ->  Append
+               ->  Seq Scan on pt1_m t1
+                     Filter: ((a % 25) = 0)
+               ->  Seq Scan on pt1_m_p1 t1_1
+                     Filter: ((a % 25) = 0)
+               ->  Seq Scan on pt1_m_p2 t1_2
+                     Filter: ((a % 25) = 0)
+               ->  Seq Scan on pt1_m_p3 t1_3
+                     Filter: ((a % 25) = 0)
+(17 rows)
+
+-- TODO: an equalify condition on the partition keys in WHERE clause should not
+-- qualify for partition-wise join. But such a condition converts an OUTER
+-- join into an INNER join, in which case the conditions in WHERE clause can be
+-- treated as join conditions, thus qualifying the join for partition-wise
+-- join.
+-- 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 pt1_n t1, pt2_n t2 WHERE t1.c = t2.c;
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+                 QUERY PLAN                  
+---------------------------------------------
+ Merge Join
+   Merge Cond: ((t1.c)::text = t2.c)
+   ->  Sort
+         Sort Key: t1.c
+         ->  Append
+               ->  Seq Scan on pt1_n t1
+               ->  Seq Scan on pt1_n_p1 t1_1
+               ->  Seq Scan on pt1_n_p2 t1_2
+   ->  Sort
+         Sort Key: t2.c
+         ->  Append
+               ->  Seq Scan on pt2_n t2
+               ->  Seq Scan on pt2_n_p1 t2_1
+               ->  Seq Scan on pt2_n_p2 t2_2
+(14 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1_n t1 LEFT JOIN pt2_n t2 ON (t1.c = t2.c);
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+                 QUERY PLAN                  
+---------------------------------------------
+ Merge Left Join
+   Merge Cond: ((t1.c)::text = t2.c)
+   ->  Sort
+         Sort Key: t1.c
+         ->  Append
+               ->  Seq Scan on pt1_n t1
+               ->  Seq Scan on pt1_n_p1 t1_1
+               ->  Seq Scan on pt1_n_p2 t1_2
+   ->  Sort
+         Sort Key: t2.c
+         ->  Append
+               ->  Seq Scan on pt2_n t2
+               ->  Seq Scan on pt2_n_p1 t2_1
+               ->  Seq Scan on pt2_n_p2 t2_2
+(14 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 pt1_n t1 RIGHT JOIN pt1 t2 ON (t1.c = t2.c);
+NOTICE:  join between relations (b 2) and (b 1) is NOT considered for partition-wise join.
+                 QUERY PLAN                  
+---------------------------------------------
+ Merge Right Join
+   Merge Cond: ((t1.c)::text = (t2.c)::text)
+   ->  Sort
+         Sort Key: t1.c
+         ->  Append
+               ->  Seq Scan on pt1_n t1
+               ->  Seq Scan on pt1_n_p1 t1_1
+               ->  Seq Scan on pt1_n_p2 t1_2
+   ->  Sort
+         Sort Key: t2.c
+         ->  Append
+               ->  Seq Scan on pt1 t2
+               ->  Seq Scan on pt1_p1 t2_1
+               ->  Seq Scan on pt1_p2 t2_2
+               ->  Seq Scan on pt1_p3 t2_3
+(15 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1_n t1 FULL JOIN pt1 t2 ON (t1.c = t2.c);
+NOTICE:  join between relations (b 1) and (b 2) is NOT considered for partition-wise join.
+                 QUERY PLAN                  
+---------------------------------------------
+ Merge Full Join
+   Merge Cond: ((t1.c)::text = (t2.c)::text)
+   ->  Sort
+         Sort Key: t1.c
+         ->  Append
+               ->  Seq Scan on pt1_n t1
+               ->  Seq Scan on pt1_n_p1 t1_1
+               ->  Seq Scan on pt1_n_p2 t1_2
+   ->  Sort
+         Sort Key: t2.c
+         ->  Append
+               ->  Seq Scan on pt1 t2
+               ->  Seq Scan on pt1_p1 t2_1
+               ->  Seq Scan on pt1_p2 t2_2
+               ->  Seq Scan on pt1_p3 t2_3
+(15 rows)
+
diff --git a/src/test/regress/sql/partition_join.sql b/src/test/regress/sql/partition_join.sql
new file mode 100644
index 0000000..128987b
--- /dev/null
+++ b/src/test/regress/sql/partition_join.sql
@@ -0,0 +1,408 @@
+--
+-- PARTITION_JOIN
+-- Test partition-wise join between partitioned tables
+--
+
+-- TODO: We need to add tests for SEMI-ANTI joins.
+-- TODO: tests with placeholder vars
+
+--
+-- partitioned by a single column
+--
+CREATE TABLE pt1 (a int, b int, c varchar) PARTITION BY RANGE(a);
+CREATE TABLE pt1_p1 PARTITION OF pt1 FOR VALUES START (0) END (250);
+CREATE TABLE pt1_p2 PARTITION OF pt1 FOR VALUES START (250) END (500);
+CREATE TABLE pt1_p3 PARTITION OF pt1 FOR VALUES START (500) END (600);
+INSERT INTO pt1 SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 2) i;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE upt1 AS SELECT * FROM pt1;
+
+CREATE TABLE pt2 (a int, b int, c varchar) PARTITION BY RANGE(b);
+CREATE TABLE pt2_p1 PARTITION OF pt2 FOR VALUES START (0) END (250);
+CREATE TABLE pt2_p2 PARTITION OF pt2 FOR VALUES START (250) END (500);
+CREATE TABLE pt2_p3 PARTITION OF pt2 FOR VALUES START (500) END (600);
+INSERT INTO pt2 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.
+CREATE TABLE upt2 AS SELECT * FROM pt2;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1 t1, pt2 t2 WHERE t1.a = t2.b AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1 t1, pt2 t2 WHERE t1.a = t2.b AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM upt1 t1, upt2 t2 WHERE t1.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 pt1 t1 LEFT JOIN pt2 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 pt1 t1 LEFT JOIN pt2 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 upt1 t1 LEFT JOIN upt2 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 pt1 t1 RIGHT JOIN pt2 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 pt1 t1 RIGHT JOIN pt2 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 upt1 t1 RIGHT JOIN upt2 t2 ON t1.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 pt1 WHERE pt1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM pt2 WHERE pt2.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 pt1 WHERE pt1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM pt2 WHERE pt2.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 upt1 t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM upt2 t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b;
+
+--
+-- partitioned by expression
+--
+CREATE TABLE pt1_e (a int, b int, c varchar) PARTITION BY RANGE(((a + b)/2));
+CREATE TABLE pt1_e_p1 PARTITION OF pt1_e FOR VALUES START (0) END (250);
+CREATE TABLE pt1_e_p2 PARTITION OF pt1_e FOR VALUES START (250) END (500);
+CREATE TABLE pt1_e_p3 PARTITION OF pt1_e FOR VALUES START (500) END (600);
+INSERT INTO pt1_e SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 2) i;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE upt1_e AS SELECT * FROM pt1_e;
+
+CREATE TABLE pt2_e (a int, b int, c varchar) PARTITION BY RANGE(((b + a)/2));
+CREATE TABLE pt2_e_p1 PARTITION OF pt2_e FOR VALUES START (0) END (250);
+CREATE TABLE pt2_e_p2 PARTITION OF pt2_e FOR VALUES START (250) END (500);
+CREATE TABLE pt2_e_p3 PARTITION OF pt2_e FOR VALUES START (500) END (600);
+INSERT INTO pt2_e 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.
+CREATE TABLE upt2_e AS SELECT * FROM pt2_e;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1_e t1, pt2_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 pt1_e t1, pt2_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 upt1_e t1, upt2_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 pt1_e t1 LEFT JOIN pt2_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 pt1_e t1 LEFT JOIN pt2_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 upt1_e t1 LEFT JOIN upt2_e t2 ON (t1.a + t1.b)/2 = (t2.b + t2.a)/2 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 pt1_e t1 RIGHT JOIN pt2_e t2 ON (t1.a + t1.b)/2 = (t2.b + t2.a)/2 WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1_e t1 RIGHT JOIN pt2_e t2 ON (t1.a + t1.b)/2 = (t2.b + t2.a)/2 WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM upt1_e t1 RIGHT JOIN upt2_e t2 ON (t1.a + t1.b)/2 = (t2.b + t2.a)/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 pt1_e WHERE pt1_e.a % 25 = 0) t1 FULL JOIN (SELECT * FROM pt2_e WHERE pt2_e.b % 25 = 0) t2 ON ((t1.a + t1.b)/2 = (t2.b + t2.a)/2) ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM pt1_e WHERE pt1_e.a % 25 = 0) t1 FULL JOIN (SELECT * FROM pt2_e WHERE pt2_e.b % 25 = 0) t2 ON ((t1.a + t1.b)/2 = (t2.b + t2.a)/2) ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM upt1_e t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM upt2_e t2 WHERE t2.b % 25 = 0) t2 ON ((t1.a + t1.b)/2 = (t2.b + t2.a)/2) 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 pt1 t1, pt2 t2, pt1_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 pt1 t1, pt2 t2, pt1_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 upt1 t1, upt2 t2, upt1_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 (pt1 t1 LEFT JOIN pt2 t2 ON t1.a = t2.b) LEFT JOIN pt1_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 (pt1 t1 LEFT JOIN pt2 t2 ON t1.a = t2.b) LEFT JOIN pt1_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 (upt1 t1 LEFT JOIN upt2 t2 ON t1.a = t2.b) LEFT JOIN upt1_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 (pt1 t1 LEFT JOIN pt2 t2 ON t1.a = t2.b) LEFT JOIN pt1_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 (pt1 t1 LEFT JOIN pt2 t2 ON t1.a = t2.b) LEFT JOIN pt1_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 (upt1 t1 LEFT JOIN upt2 t2 ON t1.a = t2.b) LEFT JOIN upt1_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 (pt1 t1 LEFT JOIN pt2 t2 ON t1.a = t2.b) RIGHT JOIN pt1_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 (pt1 t1 LEFT JOIN pt2 t2 ON t1.a = t2.b) RIGHT JOIN pt1_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 (upt1 t1 LEFT JOIN upt2 t2 ON t1.a = t2.b) RIGHT JOIN upt1_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 (pt1 t1 RIGHT JOIN pt2 t2 ON t1.a = t2.b) RIGHT JOIN pt1_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 (pt1 t1 RIGHT JOIN pt2 t2 ON t1.a = t2.b) RIGHT JOIN pt1_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 (upt1 t1 RIGHT JOIN upt2 t2 ON t1.a = t2.b) RIGHT JOIN upt1_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 ((SELECT * FROM pt1 WHERE pt1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM pt2 WHERE pt2.b % 25 = 0) t2 ON (t1.a = t2.b)) FULL JOIN (SELECT * FROM pt1_e WHERE pt1_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 pt1 WHERE pt1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM pt2 WHERE pt2.b % 25 = 0) t2 ON (t1.a = t2.b)) FULL JOIN (SELECT * FROM pt1_e WHERE pt1_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 upt1 WHERE upt1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM upt2 WHERE upt2.b % 25 = 0) t2 ON (t1.a = t2.b)) FULL JOIN (SELECT * FROM upt1_e WHERE upt1_e.a % 25 = 0) t3 ON (t1.a = (t3.a + t3.b)/2) ORDER BY t1.a, t2.b, t3.a + t3.b;
+
+--
+-- partitioned by multiple columns
+--
+CREATE TABLE pt1_m (a int, b int, c varchar) PARTITION BY RANGE(a, ((a + b)/2));
+CREATE TABLE pt1_m_p1 PARTITION OF pt1_m FOR VALUES START (0, 0) END (250, 250);
+CREATE TABLE pt1_m_p2 PARTITION OF pt1_m FOR VALUES START (250, 250) END (500, 500);
+CREATE TABLE pt1_m_p3 PARTITION OF pt1_m FOR VALUES START (500, 500) END (600, 600);
+INSERT INTO pt1_m SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 2) i;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE upt1_m AS SELECT * FROM pt1_m;
+
+CREATE TABLE pt2_m (a int, b int, c varchar) PARTITION BY RANGE(((b + a)/2), b);
+CREATE TABLE pt2_m_p1 PARTITION OF pt2_m FOR VALUES START (0, 0) END (250, 250);
+CREATE TABLE pt2_m_p2 PARTITION OF pt2_m FOR VALUES START (250, 250) END (500, 500);
+CREATE TABLE pt2_m_p3 PARTITION OF pt2_m FOR VALUES START (500, 500) END (600, 600);
+INSERT INTO pt2_m 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.
+CREATE TABLE upt2_m AS SELECT * FROM pt2_m;
+
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1_m t1, pt2_m t2 WHERE t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2 AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1_m t1, pt2_m t2 WHERE t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2 AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM upt1_m t1, upt2_m t2 WHERE t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.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 FROM pt1_m t1 LEFT JOIN pt2_m t2 ON t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2 WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1_m t1 LEFT JOIN pt2_m t2 ON t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2 WHERE t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM upt1_m t1 LEFT JOIN upt2_m t2 ON t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2 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 pt1_m t1 RIGHT JOIN pt2_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 pt1_m t1 RIGHT JOIN pt2_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 upt1_m t1 RIGHT JOIN upt2_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 pt1_m WHERE pt1_m.a % 25 = 0) t1 FULL JOIN (SELECT * FROM pt2_m WHERE pt2_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 pt1_m WHERE pt1_m.a % 25 = 0) t1 FULL JOIN (SELECT * FROM pt2_m WHERE pt2_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 upt1_m t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM upt2_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;
+
+--
+-- multi-leveled partitions
+--
+CREATE TABLE pt1_l (a int, b int, c varchar) PARTITION BY RANGE(a);
+CREATE TABLE pt1_l_p1 PARTITION OF pt1_l FOR VALUES START (0) END (250) PARTITION BY RANGE (b);
+CREATE TABLE pt1_l_p1_p1 PARTITION OF pt1_l_p1 FOR VALUES START (0) END (100);
+CREATE TABLE pt1_l_p1_p2 PARTITION OF pt1_l_p1 FOR VALUES START (100) END (250);
+CREATE TABLE pt1_l_p2 PARTITION OF pt1_l FOR VALUES START (250) END (500) PARTITION BY RANGE (c);
+CREATE TABLE pt1_l_p2_p1 PARTITION OF pt1_l_p2 FOR VALUES START ('0250') END ('0400');
+CREATE TABLE pt1_l_p2_p2 PARTITION OF pt1_l_p2 FOR VALUES START ('0400') END ('0500');
+CREATE TABLE pt1_l_p3 PARTITION OF pt1_l FOR VALUES START (500) END (600) PARTITION BY RANGE ((b + a));
+CREATE TABLE pt1_l_p3_p1 PARTITION OF pt1_l_p3 FOR VALUES START (1000) END (1100);
+CREATE TABLE pt1_l_p3_p2 PARTITION OF pt1_l_p3 FOR VALUES START (1100) END (1200);
+INSERT INTO pt1_l SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 2) i;
+-- TODO: This table is created only for testing the results. Remove once
+-- results are tested.
+CREATE TABLE upt1_l AS SELECT * FROM pt1_l;
+
+CREATE TABLE pt2_l (a int, b int, c varchar) PARTITION BY RANGE(b);
+CREATE TABLE pt2_l_p1 PARTITION OF pt2_l FOR VALUES START (0) END (250) PARTITION BY RANGE (a);
+CREATE TABLE pt2_l_p1_p1 PARTITION OF pt2_l_p1 FOR VALUES START (0) END (100);
+CREATE TABLE pt2_l_p1_p2 PARTITION OF pt2_l_p1 FOR VALUES START (100) END (250);
+CREATE TABLE pt2_l_p2 PARTITION OF pt2_l FOR VALUES START (250) END (500) PARTITION BY RANGE (c);
+CREATE TABLE pt2_l_p2_p1 PARTITION OF pt2_l_p2 FOR VALUES START ('0250') END ('0400');
+CREATE TABLE pt2_l_p2_p2 PARTITION OF pt2_l_p2 FOR VALUES START ('0400') END ('0500');
+CREATE TABLE pt2_l_p3 PARTITION OF pt2_l FOR VALUES START (500) END (600) PARTITION BY RANGE ((a + b));
+CREATE TABLE pt2_l_p3_p1 PARTITION OF pt2_l_p3 FOR VALUES START (1000) END (1100);
+CREATE TABLE pt2_l_p3_p2 PARTITION OF pt2_l_p3 FOR VALUES START (1100) END (1200);
+INSERT INTO pt2_l 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.
+CREATE TABLE upt2_l AS SELECT * FROM pt2_l;
+
+--
+-- TODO: a varchar = varchar comparison is converted into a comparison
+-- varchar::text = varchar::text. Thus the partition key on c does not match
+-- the equi-join condition on c. This is fine, but can be improved. Also, as
+-- long as we have this behaviour, we get to test partial partition-wise join
+-- between multi-level partitioned relations.
+--
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1_l t1, pt2_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 pt1_l t1, pt2_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 upt1_l t1, upt2_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;
+
+-- TODO for outer joins the equality conditions do not appear in joininfo of
+-- children, which means that the equi-join clauses are not present for
+-- subpartitions. Hence the joins between subpartitions are not currently being
+-- considered for partition-wise join.
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1_l t1 LEFT JOIN pt2_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 pt1_l t1 LEFT JOIN pt2_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 upt1_l t1 LEFT JOIN upt2_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 pt1_l t1 RIGHT JOIN pt2_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 pt1_l t1 RIGHT JOIN pt2_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 upt1_l t1 RIGHT JOIN upt2_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 pt1_l WHERE pt1_l.a % 25 = 0) t1 FULL JOIN (SELECT * FROM pt2_l WHERE pt2_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 pt1_l WHERE pt1_l.a % 25 = 0) t1 FULL JOIN (SELECT * FROM pt2_l WHERE pt2_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 upt1_l t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT * FROM upt2_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;
+
+--
+-- 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;
+-- 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;
+-- 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;
+
+--
+-- 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;
+-- 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;
+-- 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, plt2_e t2 WHERE t1.a = t2.b AND ltrim(t1.c, 'A') = ltrim(t2.c, 'A') AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM plt1_e t1, plt2_e t2 WHERE t1.a = t2.b AND ltrim(t1.c, 'A') = ltrim(t2.c, 'A') AND t1.a % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM uplt1_e t1, uplt2_e t2 WHERE t1.a = t2.b AND ltrim(t1.c, 'A') = ltrim(t2.c, '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_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 plt1_e t1 RIGHT JOIN plt2_e t2 ON t1.a = t2.b AND ltrim(t1.c, 'A') = ltrim(t2.c, 'A') WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM plt1_e t1 RIGHT JOIN plt2_e t2 ON t1.a = t2.b AND ltrim(t1.c, 'A') = ltrim(t2.c, 'A') WHERE t2.b % 25 = 0 ORDER BY t1.a, t2.b;
+SELECT t1.a, t1.c, t2.b, t2.c FROM uplt1_e t1 RIGHT JOIN uplt2_e t2 ON t1.a = t2.b AND ltrim(t1.c, 'A') = ltrim(t2.c, 'A') 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_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 t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM plt1 t1, plt2 t2, plt1_e t3 WHERE t1.a = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c AND t1.a = t3.a 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 plt1 t1, plt2 t2, plt1_e t3 WHERE t1.a = t2.b AND t1.c = t2.c AND t1.a = t3.a AND ltrim(t3.c, 'A') = t1.c 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 uplt1 t1, uplt2 t2, uplt1_e t3 WHERE t1.a = t2.b AND t1.c = t2.c AND t1.a = t3.a AND ltrim(t3.c, 'A') = t1.c 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 (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 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) RIGHT 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) RIGHT 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 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 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 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 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 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 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 ((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;
+
+--
+-- negative testcases
+--
+
+CREATE TABLE pt1_n (a int, b int, c varchar) PARTITION BY RANGE(c);
+CREATE TABLE pt1_n_p1 PARTITION OF pt1_n FOR VALUES START ('0000') END ('0250');
+CREATE TABLE pt1_n_p2 PARTITION OF pt1_n FOR VALUES START ('0250') END ('0500');
+INSERT INTO pt1_n SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 499, 2) i;
+
+CREATE TABLE pt2_n (a int, b int, c text) PARTITION BY LIST(c);
+CREATE TABLE pt2_n_p1 PARTITION OF pt2_n FOR VALUES IN ('0000', '0003', '0004', '0010', '0006', '0007');
+CREATE TABLE pt2_n_p2 PARTITION OF pt2_n FOR VALUES IN ('0001', '0005', '0002', '0009', '0008', '0011');
+INSERT INTO pt2_n SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i;
+
+CREATE TABLE pt3_n (a int, b int, c text) PARTITION BY LIST(c);
+CREATE TABLE pt3_n_p1 PARTITION OF pt3_n FOR VALUES IN ('0000', '0004', '0006', '0007');
+CREATE TABLE pt3_n_p2 PARTITION OF pt3_n FOR VALUES IN ('0001', '0002', '0008', '0010');
+CREATE TABLE pt3_n_p3 PARTITION OF pt3_n FOR VALUES IN ('0003', '0005', '0009', '0011');
+INSERT INTO pt2_n SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i;
+
+CREATE TABLE pt4_n (a int, b int, c text) PARTITION BY RANGE(a);
+CREATE TABLE pt4_n_p1 PARTITION OF pt4_n FOR VALUES START (0) END (300);
+CREATE TABLE pt4_n_p2 PARTITION OF pt4_n FOR VALUES START (300) END (500);
+CREATE TABLE pt4_n_p3 PARTITION OF pt4_n FOR VALUES START (500) END (600);
+INSERT INTO pt4_n SELECT i, i, to_char(i, 'FM0000') FROM generate_series(0, 599, 2) i;
+
+-- 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 pt1 t1, pt4_n t2 WHERE t1.a = t2.a;
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1 t1 FULL JOIN pt4_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 pt1 t1 LEFT JOIN pt2 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 pt1_m t1, pt2_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 pt1_m t1 LEFT JOIN pt2_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 pt1_m t1 LEFT JOIN pt2_m t2 ON t1.c = t2.c WHERE t1.a % 25 = 0;
+-- TODO: an equalify condition on the partition keys in WHERE clause should not
+-- qualify for partition-wise join. But such a condition converts an OUTER
+-- join into an INNER join, in which case the conditions in WHERE clause can be
+-- treated as join conditions, thus qualifying the join for partition-wise
+-- join.
+
+-- 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 pt1_n t1, pt2_n t2 WHERE t1.c = t2.c;
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1_n t1 LEFT JOIN pt2_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 pt1_n t1 RIGHT JOIN pt1 t2 ON (t1.c = t2.c);
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t2.b, t2.c FROM pt1_n t1 FULL JOIN pt1 t2 ON (t1.c = t2.c);
