From 75cb769d22a7ecfae4af813c931ad93025d1a7f9 Mon Sep 17 00:00:00 2001
From: Nitin <nitin.jadhav@enterprisedb.com>
Date: Tue, 21 Dec 2021 18:25:04 +0530
Subject: [PATCH 2/2] partition-wise join support

---
 src/backend/partitioning/partbounds.c        |  304 ++++---
 src/include/partitioning/partbounds.h        |    5 +-
 src/test/regress/expected/partition_join.out | 1257 ++++++++++++++++++++++++++
 src/test/regress/sql/partition_join.sql      |  257 ++++++
 4 files changed, 1680 insertions(+), 143 deletions(-)

diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c
index dd75a25..9b55e05 100644
--- a/src/backend/partitioning/partbounds.c
+++ b/src/backend/partitioning/partbounds.c
@@ -106,7 +106,8 @@ static PartitionBoundInfo create_list_bounds(PartitionBoundSpec **boundspecs,
 											 int nparts, PartitionKey key, int **mapping);
 static PartitionBoundInfo create_range_bounds(PartitionBoundSpec **boundspecs,
 											  int nparts, PartitionKey key, int **mapping);
-static PartitionBoundInfo merge_list_bounds(FmgrInfo *partsupfunc,
+static PartitionBoundInfo merge_list_bounds(int partnatts,
+											FmgrInfo *partsupfunc,
 											Oid *collations,
 											RelOptInfo *outer_rel,
 											RelOptInfo *inner_rel,
@@ -147,15 +148,14 @@ static int	process_inner_partition(PartitionMap *outer_map,
 									JoinType jointype,
 									int *next_index,
 									int *default_index);
-static void merge_null_partitions(PartitionMap *outer_map,
+static int	merge_null_partitions(PartitionMap *outer_map,
 								  PartitionMap *inner_map,
-								  bool outer_has_null,
-								  bool inner_has_null,
+								  bool consider_outer_null,
+								  bool consider_inner_null,
 								  int outer_null,
 								  int inner_null,
 								  JoinType jointype,
-								  int *next_index,
-								  int *null_index);
+								  int *next_index);
 static void merge_default_partitions(PartitionMap *outer_map,
 									 PartitionMap *inner_map,
 									 bool outer_has_default,
@@ -372,7 +372,6 @@ create_hash_bounds(PartitionBoundSpec **boundspecs, int nparts,
 	boundinfo->strategy = key->strategy;
 	boundinfo->partnatts = key->partnatts;
 	/* No special hash partitions. */
-	boundinfo->null_index = -1;
 	boundinfo->isnulls = NULL;
 	boundinfo->default_index = -1;
 
@@ -445,6 +444,34 @@ create_hash_bounds(PartitionBoundSpec **boundspecs, int nparts,
 }
 
 /*
+ * partition_bound_accepts_nulls
+ *
+ * Returns TRUE if any of the partition bounds contains a NULL value,
+ * FALSE otherwise.
+ */
+bool
+partition_bound_accepts_nulls(PartitionBoundInfo boundinfo)
+{
+	int			i;
+
+	if (!boundinfo->isnulls)
+		return false;
+
+	for (i = 0; i < boundinfo->ndatums; i++)
+	{
+		int			j;
+
+		for (j = 0; j < boundinfo->partnatts; j++)
+		{
+			if (boundinfo->isnulls[i][j])
+				return true;
+		}
+	}
+
+	return false;
+}
+
+/*
  * get_list_datum_count
  * 		Returns the total number of datums in all the partitions.
  */
@@ -475,7 +502,6 @@ create_list_bounds(PartitionBoundSpec **boundspecs, int nparts,
 	int			ndatums;
 	int			next_index = 0;
 	int			default_index = -1;
-	int			null_index = -1;
 	Datum	   *boundDatums;
 	bool	   *boundIsNulls;
 
@@ -484,7 +510,6 @@ create_list_bounds(PartitionBoundSpec **boundspecs, int nparts,
 	boundinfo->strategy = key->strategy;
 	boundinfo->partnatts = key->partnatts;
 	/* Will be set correctly below. */
-	boundinfo->null_index = -1;
 	boundinfo->default_index = -1;
 
 	ndatums = get_list_datum_count(boundspecs, nparts);
@@ -528,10 +553,7 @@ create_list_bounds(PartitionBoundSpec **boundspecs, int nparts,
 				if (!val->constisnull)
 					all_values[j].values[k] = val->constvalue;
 				else
-				{
-					null_index = i;
 					all_values[j].isnulls[k] = true;
-				}
 
 				k++;
 			}
@@ -595,22 +617,6 @@ create_list_bounds(PartitionBoundSpec **boundspecs, int nparts,
 
 	pfree(all_values);
 
-	/*
-	 * Set the canonical value for null_index, if any.
-	 *
-	 * It is possible that the null-accepting partition has not been assigned
-	 * an index yet, which could happen if such partition accepts only null
-	 * and hence not handled in the above loop which only looked at non-null
-	 * values.
-	 */
-	if (null_index != -1)
-	{
-		Assert(null_index >= 0);
-		if ((*mapping)[null_index] == -1)
-			(*mapping)[null_index] = next_index++;
-		boundinfo->null_index = (*mapping)[null_index];
-	}
-
 	/* Set the canonical value for default_index, if any. */
 	if (default_index != -1)
 	{
@@ -706,8 +712,6 @@ create_range_bounds(PartitionBoundSpec **boundspecs, int nparts,
 	boundinfo = (PartitionBoundInfoData *)
 		palloc0(sizeof(PartitionBoundInfoData));
 	boundinfo->strategy = key->strategy;
-	/* There is no special null-accepting range partition. */
-	boundinfo->null_index = -1;
 	boundinfo->partnatts = key->partnatts;
 	boundinfo->isnulls = NULL;
 	/* Will be set correctly below. */
@@ -1139,7 +1143,6 @@ partition_bounds_copy(PartitionBoundInfo src,
 	dest->indexes = (int *) palloc(sizeof(int) * nindexes);
 	memcpy(dest->indexes, src->indexes, sizeof(int) * nindexes);
 
-	dest->null_index = src->null_index;
 	dest->default_index = src->default_index;
 
 	return dest;
@@ -1199,7 +1202,8 @@ partition_bounds_merge(int partnatts,
 			return NULL;
 
 		case PARTITION_STRATEGY_LIST:
-			return merge_list_bounds(partsupfunc,
+			return merge_list_bounds(partnatts,
+									 partsupfunc,
 									 partcollation,
 									 outer_rel,
 									 inner_rel,
@@ -1243,7 +1247,8 @@ partition_bounds_merge(int partnatts,
  * join can't handle.
  */
 static PartitionBoundInfo
-merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation,
+merge_list_bounds(int partnatts,
+				  FmgrInfo *partsupfunc, Oid *partcollation,
 				  RelOptInfo *outer_rel, RelOptInfo *inner_rel,
 				  JoinType jointype,
 				  List **outer_parts, List **inner_parts)
@@ -1255,8 +1260,6 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation,
 	bool		inner_has_default = partition_bound_has_default(inner_bi);
 	int			outer_default = outer_bi->default_index;
 	int			inner_default = inner_bi->default_index;
-	bool		outer_has_null = partition_bound_accepts_nulls(outer_bi);
-	bool		inner_has_null = partition_bound_accepts_nulls(inner_bi);
 	PartitionMap outer_map;
 	PartitionMap inner_map;
 	int			outer_pos;
@@ -1307,6 +1310,11 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation,
 		bool	   *outer_isnull = NULL;
 		bool	   *inner_isnull = NULL;
 		bool	   *merged_isnull = NULL;
+		bool		consider_outer_null = false;
+		bool		consider_inner_null = false;
+		bool		outer_has_null = false;
+		bool		inner_has_null = false;
+		int			i;
 
 		if (outer_bi->isnulls && outer_pos < outer_bi->ndatums)
 			outer_isnull = outer_bi->isnulls[outer_pos];
@@ -1341,24 +1349,38 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation,
 			}
 		}
 
-		if (outer_isnull && outer_isnull[0])
-		{
-			outer_pos++;
-			continue;
-		}
-
-		if (inner_isnull && inner_isnull[0])
-		{
-			inner_pos++;
-			continue;
-		}
-
 		/* Get the list values. */
 		outer_datums = outer_pos < outer_bi->ndatums ?
 			outer_bi->datums[outer_pos] : NULL;
 		inner_datums = inner_pos < inner_bi->ndatums ?
 			inner_bi->datums[inner_pos] : NULL;
 
+		for (i = 0; i < partnatts; i++)
+		{
+			if (outer_isnull && outer_isnull[i])
+			{
+				outer_has_null = true;
+				if (outer_map.merged_indexes[outer_index] == -1)
+				{
+					consider_outer_null = true;
+					break;
+				}
+			}
+		}
+
+		for (i = 0; i < partnatts; i++)
+		{
+			if (inner_isnull && inner_isnull[i])
+			{
+				inner_has_null = true;
+				if (inner_map.merged_indexes[inner_index] == -1)
+				{
+					consider_inner_null = true;
+					break;
+				}
+			}
+		}
+
 		/*
 		 * We run this loop till both sides finish.  This allows us to avoid
 		 * duplicating code to handle the remaining values on the side which
@@ -1375,10 +1397,10 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation,
 		else
 		{
 			Assert(outer_datums != NULL && inner_datums != NULL);
-			cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[0],
-													 partcollation[0],
-													 outer_datums[0],
-													 inner_datums[0]));
+			cmpval = partition_lbound_datum_cmp(partsupfunc, partcollation,
+												outer_datums, outer_isnull,
+												inner_datums, inner_isnull,
+												partnatts);
 		}
 
 		if (cmpval == 0)
@@ -1389,15 +1411,31 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation,
 			Assert(outer_index >= 0);
 			Assert(inner_index >= 0);
 
-			/*
-			 * Try merging both partitions.  If successful, add the list value
-			 * and index of the merged partition below.
-			 */
-			merged_index = merge_matching_partitions(&outer_map, &inner_map,
+			if (outer_has_null && inner_has_null)
+			{
+				/* Merge the NULL partitions. */
+				merged_index = merge_null_partitions(&outer_map, &inner_map,
+													 consider_outer_null,
+													 consider_inner_null,
 													 outer_index, inner_index,
-													 &next_index);
-			if (merged_index == -1)
-				goto cleanup;
+													 jointype, &next_index);
+
+				if (merged_index == -1)
+					goto cleanup;
+			}
+			else
+			{
+				/*
+				 * Try merging both partitions.  If successful, add the list
+				 * value and index of the merged partition below.
+				 */
+				merged_index = merge_matching_partitions(&outer_map, &inner_map,
+														 outer_index, inner_index,
+														 &next_index);
+
+				if (merged_index == -1)
+					goto cleanup;
+			}
 
 			merged_datum = outer_datums;
 			merged_isnull = outer_isnull;
@@ -1411,14 +1449,30 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation,
 			/* A list value missing from the inner side. */
 			Assert(outer_pos < outer_bi->ndatums);
 
-			/*
-			 * If the inner side has the default partition, or this is an
-			 * outer join, try to assign a merged partition to the outer
-			 * partition (see process_outer_partition()).  Otherwise, the
-			 * outer partition will not contribute to the result.
-			 */
-			if (inner_has_default || IS_OUTER_JOIN(jointype))
+			if (inner_has_null)
+			{
+				if (consider_inner_null)
+				{
+					/* Merge the NULL partitions. */
+					merged_index = merge_null_partitions(&outer_map, &inner_map,
+														 consider_outer_null,
+														 consider_inner_null,
+														 outer_index, inner_index,
+														 jointype, &next_index);
+
+					if (merged_index == -1)
+						goto cleanup;
+				}
+			}
+			else if (inner_has_default || IS_OUTER_JOIN(jointype))
 			{
+				/*
+				 * If the inner side has the default partition, or this is an
+				 * outer join, try to assign a merged partition to the outer
+				 * partition (see process_outer_partition()).  Otherwise, the
+				 * outer partition will not contribute to the result.
+				 */
+
 				/* Get the outer partition. */
 				outer_index = outer_bi->indexes[outer_pos];
 				Assert(outer_index >= 0);
@@ -1433,10 +1487,11 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation,
 													   &default_index);
 				if (merged_index == -1)
 					goto cleanup;
-				merged_datum = outer_datums;
-				merged_isnull = outer_isnull;
 			}
 
+			merged_datum = outer_datums;
+			merged_isnull = outer_isnull;
+
 			/* Move to the next list value on the outer side. */
 			outer_pos++;
 		}
@@ -1446,14 +1501,30 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation,
 			Assert(cmpval > 0);
 			Assert(inner_pos < inner_bi->ndatums);
 
-			/*
-			 * If the outer side has the default partition, or this is a FULL
-			 * join, try to assign a merged partition to the inner partition
-			 * (see process_inner_partition()).  Otherwise, the inner
-			 * partition will not contribute to the result.
-			 */
-			if (outer_has_default || jointype == JOIN_FULL)
+			if (outer_has_null || inner_has_null)
 			{
+				if (consider_outer_null || consider_inner_null)
+				{
+					/* Merge the NULL partitions. */
+					merged_index = merge_null_partitions(&outer_map, &inner_map,
+														 consider_outer_null,
+														 consider_inner_null,
+														 outer_index, inner_index,
+														 jointype, &next_index);
+
+					if (merged_index == -1)
+						goto cleanup;
+				}
+			}
+			else if (outer_has_default || jointype == JOIN_FULL)
+			{
+				/*
+				 * If the outer side has the default partition, or this is a
+				 * FULL join, try to assign a merged partition to the inner
+				 * partition (see process_inner_partition()).  Otherwise, the
+				 * innerpartition will not contribute to the result.
+				 */
+
 				/* Get the inner partition. */
 				inner_index = inner_bi->indexes[inner_pos];
 				Assert(inner_index >= 0);
@@ -1468,10 +1539,11 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation,
 													   &default_index);
 				if (merged_index == -1)
 					goto cleanup;
-				merged_datum = inner_datums;
-				merged_isnull = inner_isnull;
 			}
 
+			merged_datum = inner_datums;
+			merged_isnull = inner_isnull;
+
 			/* Move to the next list value on the inner side. */
 			inner_pos++;
 		}
@@ -1488,26 +1560,6 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation,
 		}
 	}
 
-	/*
-	 * If the NULL partitions (if any) have been proven empty, deem them
-	 * non-existent.
-	 */
-	if (outer_has_null &&
-		is_dummy_partition(outer_rel, outer_bi->null_index))
-		outer_has_null = false;
-	if (inner_has_null &&
-		is_dummy_partition(inner_rel, inner_bi->null_index))
-		inner_has_null = false;
-
-	/* Merge the NULL partitions if any. */
-	if (outer_has_null || inner_has_null)
-		merge_null_partitions(&outer_map, &inner_map,
-							  outer_has_null, inner_has_null,
-							  outer_bi->null_index, inner_bi->null_index,
-							  jointype, &next_index, &null_index);
-	else
-		Assert(null_index == -1);
-
 	/* Merge the default partitions if any. */
 	if (outer_has_default || inner_has_default)
 		merge_default_partitions(&outer_map, &inner_map,
@@ -2220,48 +2272,24 @@ process_inner_partition(PartitionMap *outer_map,
  * be mergejoinable, and we currently assume that mergejoinable operators are
  * strict (see MJEvalOuterValues()/MJEvalInnerValues()).
  */
-static void
+static int
 merge_null_partitions(PartitionMap *outer_map,
 					  PartitionMap *inner_map,
-					  bool outer_has_null,
-					  bool inner_has_null,
+					  bool consider_outer_null,
+					  bool consider_inner_null,
 					  int outer_null,
 					  int inner_null,
 					  JoinType jointype,
-					  int *next_index,
-					  int *null_index)
+					  int *next_index)
 {
-	bool		consider_outer_null = false;
-	bool		consider_inner_null = false;
-
-	Assert(outer_has_null || inner_has_null);
-	Assert(*null_index == -1);
-
-	/*
-	 * Check whether the NULL partitions have already been merged and if so,
-	 * set the consider_outer_null/consider_inner_null flags.
-	 */
-	if (outer_has_null)
-	{
-		Assert(outer_null >= 0 && outer_null < outer_map->nparts);
-		if (outer_map->merged_indexes[outer_null] == -1)
-			consider_outer_null = true;
-	}
-	if (inner_has_null)
-	{
-		Assert(inner_null >= 0 && inner_null < inner_map->nparts);
-		if (inner_map->merged_indexes[inner_null] == -1)
-			consider_inner_null = true;
-	}
+	int			merged_index = *next_index;
 
 	/* If both flags are set false, we don't need to do anything. */
 	if (!consider_outer_null && !consider_inner_null)
-		return;
+		return merged_index;
 
 	if (consider_outer_null && !consider_inner_null)
 	{
-		Assert(outer_has_null);
-
 		/*
 		 * If this is an outer join, the NULL partition on the outer side has
 		 * to be scanned all the way anyway; merge the NULL partition with a
@@ -2273,14 +2301,12 @@ merge_null_partitions(PartitionMap *outer_map,
 		if (IS_OUTER_JOIN(jointype))
 		{
 			Assert(jointype != JOIN_RIGHT);
-			*null_index = merge_partition_with_dummy(outer_map, outer_null,
-													 next_index);
+			merged_index = merge_partition_with_dummy(outer_map, outer_null,
+													  next_index);
 		}
 	}
 	else if (!consider_outer_null && consider_inner_null)
 	{
-		Assert(inner_has_null);
-
 		/*
 		 * If this is a FULL join, the NULL partition on the inner side has to
 		 * be scanned all the way anyway; merge the NULL partition with a
@@ -2290,14 +2316,12 @@ merge_null_partitions(PartitionMap *outer_map,
 		 * treat it as the NULL partition of the join relation.
 		 */
 		if (jointype == JOIN_FULL)
-			*null_index = merge_partition_with_dummy(inner_map, inner_null,
-													 next_index);
+			merged_index = merge_partition_with_dummy(inner_map, inner_null,
+													  next_index);
 	}
 	else
 	{
 		Assert(consider_outer_null && consider_inner_null);
-		Assert(outer_has_null);
-		Assert(inner_has_null);
 
 		/*
 		 * If this is an outer join, the NULL partition on the outer side (and
@@ -2315,12 +2339,13 @@ merge_null_partitions(PartitionMap *outer_map,
 		if (IS_OUTER_JOIN(jointype))
 		{
 			Assert(jointype != JOIN_RIGHT);
-			*null_index = merge_matching_partitions(outer_map, inner_map,
-													outer_null, inner_null,
-													next_index);
-			Assert(*null_index >= 0);
+			merged_index = merge_matching_partitions(outer_map, inner_map,
+													 outer_null, inner_null,
+													 next_index);
 		}
 	}
+
+	return merged_index;
 }
 
 /*
@@ -2653,7 +2678,6 @@ build_merged_partition_bounds(char strategy, List *merged_datums,
 	foreach(lc, merged_indexes)
 		merged_bounds->indexes[pos++] = lfirst_int(lc);
 
-	merged_bounds->null_index = null_index;
 	merged_bounds->default_index = default_index;
 
 	return merged_bounds;
diff --git a/src/include/partitioning/partbounds.h b/src/include/partitioning/partbounds.h
index 2c9c97a..4afedce 100644
--- a/src/include/partitioning/partbounds.h
+++ b/src/include/partitioning/partbounds.h
@@ -92,15 +92,14 @@ typedef struct PartitionBoundInfoData
 									 * only set for LIST partitioned tables */
 	int			nindexes;		/* Length of the indexes[] array */
 	int		   *indexes;		/* Partition indexes */
-	int			null_index;		/* Index of the null-accepting partition; -1
-								 * if there isn't one */
 	int			default_index;	/* Index of the default partition; -1 if there
 								 * isn't one */
 } PartitionBoundInfoData;
 
-#define partition_bound_accepts_nulls(bi) ((bi)->null_index != -1)
 #define partition_bound_has_default(bi) ((bi)->default_index != -1)
 
+extern bool partition_bound_accepts_nulls(PartitionBoundInfo boundinfo);
+
 extern int	get_hash_partition_greatest_modulus(PartitionBoundInfo b);
 extern uint64 compute_partition_hash_value(int partnatts, FmgrInfo *partsupfunc,
 										   Oid *partcollation,
diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out
index 27f7525..84b5b36 100644
--- a/src/test/regress/expected/partition_join.out
+++ b/src/test/regress/expected/partition_join.out
@@ -4650,6 +4650,1263 @@ SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM (plt1_adv t1 LEFT JOIN plt2_adv t
 DROP TABLE plt1_adv;
 DROP TABLE plt2_adv;
 DROP TABLE plt3_adv;
+-- Tests for multi-column list-partitioned tables
+CREATE TABLE plt1_adv_m (a int, b int, c text, d int) PARTITION BY LIST (c, d);
+CREATE TABLE plt1_adv_m_p1 PARTITION OF plt1_adv_m FOR VALUES IN (('0001', 1), ('0003', 3));
+CREATE TABLE plt1_adv_m_p2 PARTITION OF plt1_adv_m FOR VALUES IN (('0004', 4), ('0006', 6));
+CREATE TABLE plt1_adv_m_p3 PARTITION OF plt1_adv_m FOR VALUES IN (('0008', 8), ('0009', 9));
+INSERT INTO plt1_adv_m SELECT i, i, to_char(i % 10, 'FM0000'), (i % 10) FROM generate_series(1, 299) i WHERE i % 10 IN (1, 3, 4, 6, 8, 9);
+ANALYZE plt1_adv_m;
+CREATE TABLE plt2_adv_m (a int, b int, c text, d int) PARTITION BY LIST (c, d);
+CREATE TABLE plt2_adv_m_p1 PARTITION OF plt2_adv_m FOR VALUES IN (('0002', 2), ('0003', 3));
+CREATE TABLE plt2_adv_m_p2 PARTITION OF plt2_adv_m FOR VALUES IN (('0004', 4), ('0006', 6));
+CREATE TABLE plt2_adv_m_p3 PARTITION OF plt2_adv_m FOR VALUES IN (('0007', 7), ('0009', 9));
+INSERT INTO plt2_adv_m SELECT i, i, to_char(i % 10, 'FM0000'), (i % 10) FROM generate_series(1, 299) i WHERE i % 10 IN (2, 3, 4, 6, 7, 9);
+ANALYZE plt2_adv_m;
+-- inner join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a;
+                                        QUERY PLAN                                        
+------------------------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a
+   ->  Append
+         ->  Hash Join
+               Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c) AND (t2_1.d = t1_1.d))
+               ->  Seq Scan on plt2_adv_m_p1 t2_1
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p1 t1_1
+                           Filter: (a < 10)
+         ->  Hash Join
+               Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c) AND (t2_2.d = t1_2.d))
+               ->  Seq Scan on plt2_adv_m_p2 t2_2
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p2 t1_2
+                           Filter: (a < 10)
+         ->  Hash Join
+               Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c) AND (t2_3.d = t1_3.d))
+               ->  Seq Scan on plt2_adv_m_p3 t2_3
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p3 t1_3
+                           Filter: (a < 10)
+(21 rows)
+
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a;
+ a |  c   | d | a |  c   | d 
+---+------+---+---+------+---
+ 3 | 0003 | 3 | 3 | 0003 | 3
+ 4 | 0004 | 4 | 4 | 0004 | 4
+ 6 | 0006 | 6 | 6 | 0006 | 6
+ 9 | 0009 | 9 | 9 | 0009 | 9
+(4 rows)
+
+-- semi join
+EXPLAIN (COSTS OFF)
+SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a;
+                                         QUERY PLAN                                         
+--------------------------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a
+   ->  Append
+         ->  Nested Loop Semi Join
+               Join Filter: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c) AND (t1_1.d = t2_1.d))
+               ->  Seq Scan on plt1_adv_m_p1 t1_1
+                     Filter: (a < 10)
+               ->  Seq Scan on plt2_adv_m_p1 t2_1
+         ->  Nested Loop Semi Join
+               Join Filter: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c) AND (t1_2.d = t2_2.d))
+               ->  Seq Scan on plt1_adv_m_p2 t1_2
+                     Filter: (a < 10)
+               ->  Seq Scan on plt2_adv_m_p2 t2_2
+         ->  Nested Loop Semi Join
+               Join Filter: ((t1_3.a = t2_3.a) AND (t1_3.c = t2_3.c) AND (t1_3.d = t2_3.d))
+               ->  Seq Scan on plt1_adv_m_p3 t1_3
+                     Filter: (a < 10)
+               ->  Seq Scan on plt2_adv_m_p3 t2_3
+(18 rows)
+
+SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a;
+ a | b |  c   | d 
+---+---+------+---
+ 3 | 3 | 0003 | 3
+ 4 | 4 | 0004 | 4
+ 6 | 6 | 0006 | 6
+ 9 | 9 | 0009 | 9
+(4 rows)
+
+-- left join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a;
+                                        QUERY PLAN                                        
+------------------------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a
+   ->  Append
+         ->  Hash Right Join
+               Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c) AND (t2_1.d = t1_1.d))
+               ->  Seq Scan on plt2_adv_m_p1 t2_1
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p1 t1_1
+                           Filter: (a < 10)
+         ->  Hash Right Join
+               Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c) AND (t2_2.d = t1_2.d))
+               ->  Seq Scan on plt2_adv_m_p2 t2_2
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p2 t1_2
+                           Filter: (a < 10)
+         ->  Hash Right Join
+               Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c) AND (t2_3.d = t1_3.d))
+               ->  Seq Scan on plt2_adv_m_p3 t2_3
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p3 t1_3
+                           Filter: (a < 10)
+(21 rows)
+
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a;
+ a |  c   | d | a |  c   | d 
+---+------+---+---+------+---
+ 1 | 0001 | 1 |   |      |  
+ 3 | 0003 | 3 | 3 | 0003 | 3
+ 4 | 0004 | 4 | 4 | 0004 | 4
+ 6 | 0006 | 6 | 6 | 0006 | 6
+ 8 | 0008 | 8 |   |      |  
+ 9 | 0009 | 9 | 9 | 0009 | 9
+(6 rows)
+
+-- anti join
+EXPLAIN (COSTS OFF)
+SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a;
+                                         QUERY PLAN                                         
+--------------------------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a
+   ->  Append
+         ->  Nested Loop Anti Join
+               Join Filter: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c) AND (t1_1.d = t2_1.d))
+               ->  Seq Scan on plt1_adv_m_p1 t1_1
+                     Filter: (a < 10)
+               ->  Seq Scan on plt2_adv_m_p1 t2_1
+         ->  Nested Loop Anti Join
+               Join Filter: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c) AND (t1_2.d = t2_2.d))
+               ->  Seq Scan on plt1_adv_m_p2 t1_2
+                     Filter: (a < 10)
+               ->  Seq Scan on plt2_adv_m_p2 t2_2
+         ->  Nested Loop Anti Join
+               Join Filter: ((t1_3.a = t2_3.a) AND (t1_3.c = t2_3.c) AND (t1_3.d = t2_3.d))
+               ->  Seq Scan on plt1_adv_m_p3 t1_3
+                     Filter: (a < 10)
+               ->  Seq Scan on plt2_adv_m_p3 t2_3
+(18 rows)
+
+SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a;
+ a | b |  c   | d 
+---+---+------+---
+ 1 | 1 | 0001 | 1
+ 8 | 8 | 0008 | 8
+(2 rows)
+
+-- full join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.a, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a;
+                                        QUERY PLAN                                        
+------------------------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a, t2.a
+   ->  Append
+         ->  Hash Full Join
+               Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c) AND (t1_1.d = t2_1.d))
+               Filter: ((COALESCE(t1_1.a, 0) < 10) AND (COALESCE(t2_1.b, 0) < 10))
+               ->  Seq Scan on plt1_adv_m_p1 t1_1
+               ->  Hash
+                     ->  Seq Scan on plt2_adv_m_p1 t2_1
+         ->  Hash Full Join
+               Hash Cond: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c) AND (t1_2.d = t2_2.d))
+               Filter: ((COALESCE(t1_2.a, 0) < 10) AND (COALESCE(t2_2.b, 0) < 10))
+               ->  Seq Scan on plt1_adv_m_p2 t1_2
+               ->  Hash
+                     ->  Seq Scan on plt2_adv_m_p2 t2_2
+         ->  Hash Full Join
+               Hash Cond: ((t1_3.a = t2_3.a) AND (t1_3.c = t2_3.c) AND (t1_3.d = t2_3.d))
+               Filter: ((COALESCE(t1_3.a, 0) < 10) AND (COALESCE(t2_3.b, 0) < 10))
+               ->  Seq Scan on plt1_adv_m_p3 t1_3
+               ->  Hash
+                     ->  Seq Scan on plt2_adv_m_p3 t2_3
+(21 rows)
+
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.a, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a;
+ a |  c   | d | a |  c   | d 
+---+------+---+---+------+---
+ 1 | 0001 | 1 |   |      |  
+ 3 | 0003 | 3 | 3 | 0003 | 3
+ 4 | 0004 | 4 | 4 | 0004 | 4
+ 6 | 0006 | 6 | 6 | 0006 | 6
+ 8 | 0008 | 8 |   |      |  
+ 9 | 0009 | 9 | 9 | 0009 | 9
+   |      |   | 2 | 0002 | 2
+   |      |   | 7 | 0007 | 7
+(8 rows)
+
+-- Test cases where one side has an extra partition
+CREATE TABLE plt2_adv_m_extra PARTITION OF plt2_adv_m FOR VALUES IN (('0000', 0));
+INSERT INTO plt2_adv_m_extra VALUES (0, 0, '0000', 0);
+ANALYZE plt2_adv_m;
+-- inner join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+                                        QUERY PLAN                                        
+------------------------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a
+   ->  Append
+         ->  Hash Join
+               Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c) AND (t2_1.d = t1_1.d))
+               ->  Seq Scan on plt2_adv_m_p1 t2_1
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p1 t1_1
+                           Filter: (b < 10)
+         ->  Hash Join
+               Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c) AND (t2_2.d = t1_2.d))
+               ->  Seq Scan on plt2_adv_m_p2 t2_2
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p2 t1_2
+                           Filter: (b < 10)
+         ->  Hash Join
+               Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c) AND (t2_3.d = t1_3.d))
+               ->  Seq Scan on plt2_adv_m_p3 t2_3
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p3 t1_3
+                           Filter: (b < 10)
+(21 rows)
+
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+ a |  c   | d | a |  c   | d 
+---+------+---+---+------+---
+ 3 | 0003 | 3 | 3 | 0003 | 3
+ 4 | 0004 | 4 | 4 | 0004 | 4
+ 6 | 0006 | 6 | 6 | 0006 | 6
+ 9 | 0009 | 9 | 9 | 0009 | 9
+(4 rows)
+
+-- semi join
+EXPLAIN (COSTS OFF)
+SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a;
+                                         QUERY PLAN                                         
+--------------------------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a
+   ->  Append
+         ->  Nested Loop Semi Join
+               Join Filter: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c) AND (t1_1.d = t2_1.d))
+               ->  Seq Scan on plt1_adv_m_p1 t1_1
+                     Filter: (b < 10)
+               ->  Seq Scan on plt2_adv_m_p1 t2_1
+         ->  Nested Loop Semi Join
+               Join Filter: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c) AND (t1_2.d = t2_2.d))
+               ->  Seq Scan on plt1_adv_m_p2 t1_2
+                     Filter: (b < 10)
+               ->  Seq Scan on plt2_adv_m_p2 t2_2
+         ->  Nested Loop Semi Join
+               Join Filter: ((t1_3.a = t2_3.a) AND (t1_3.c = t2_3.c) AND (t1_3.d = t2_3.d))
+               ->  Seq Scan on plt1_adv_m_p3 t1_3
+                     Filter: (b < 10)
+               ->  Seq Scan on plt2_adv_m_p3 t2_3
+(18 rows)
+
+SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a;
+ a | b |  c   | d 
+---+---+------+---
+ 3 | 3 | 0003 | 3
+ 4 | 4 | 0004 | 4
+ 6 | 6 | 0006 | 6
+ 9 | 9 | 0009 | 9
+(4 rows)
+
+-- left join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+                                        QUERY PLAN                                        
+------------------------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a
+   ->  Append
+         ->  Hash Right Join
+               Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c) AND (t2_1.d = t1_1.d))
+               ->  Seq Scan on plt2_adv_m_p1 t2_1
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p1 t1_1
+                           Filter: (b < 10)
+         ->  Hash Right Join
+               Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c) AND (t2_2.d = t1_2.d))
+               ->  Seq Scan on plt2_adv_m_p2 t2_2
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p2 t1_2
+                           Filter: (b < 10)
+         ->  Hash Right Join
+               Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c) AND (t2_3.d = t1_3.d))
+               ->  Seq Scan on plt2_adv_m_p3 t2_3
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p3 t1_3
+                           Filter: (b < 10)
+(21 rows)
+
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+ a |  c   | d | a |  c   | d 
+---+------+---+---+------+---
+ 1 | 0001 | 1 |   |      |  
+ 3 | 0003 | 3 | 3 | 0003 | 3
+ 4 | 0004 | 4 | 4 | 0004 | 4
+ 6 | 0006 | 6 | 6 | 0006 | 6
+ 8 | 0008 | 8 |   |      |  
+ 9 | 0009 | 9 | 9 | 0009 | 9
+(6 rows)
+
+-- left join; currently we can't do partitioned join if there are no matched
+-- partitions on the nullable side
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt2_adv_m t1 LEFT JOIN plt1_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+                               QUERY PLAN                               
+------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a
+   ->  Hash Right Join
+         Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c) AND (t2.d = t1.d))
+         ->  Append
+               ->  Seq Scan on plt1_adv_m_p1 t2_1
+               ->  Seq Scan on plt1_adv_m_p2 t2_2
+               ->  Seq Scan on plt1_adv_m_p3 t2_3
+         ->  Hash
+               ->  Append
+                     ->  Seq Scan on plt2_adv_m_extra t1_1
+                           Filter: (b < 10)
+                     ->  Seq Scan on plt2_adv_m_p1 t1_2
+                           Filter: (b < 10)
+                     ->  Seq Scan on plt2_adv_m_p2 t1_3
+                           Filter: (b < 10)
+                     ->  Seq Scan on plt2_adv_m_p3 t1_4
+                           Filter: (b < 10)
+(18 rows)
+
+-- anti join
+EXPLAIN (COSTS OFF)
+SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a;
+                                         QUERY PLAN                                         
+--------------------------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a
+   ->  Append
+         ->  Nested Loop Anti Join
+               Join Filter: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c) AND (t1_1.d = t2_1.d))
+               ->  Seq Scan on plt1_adv_m_p1 t1_1
+                     Filter: (b < 10)
+               ->  Seq Scan on plt2_adv_m_p1 t2_1
+         ->  Nested Loop Anti Join
+               Join Filter: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c) AND (t1_2.d = t2_2.d))
+               ->  Seq Scan on plt1_adv_m_p2 t1_2
+                     Filter: (b < 10)
+               ->  Seq Scan on plt2_adv_m_p2 t2_2
+         ->  Nested Loop Anti Join
+               Join Filter: ((t1_3.a = t2_3.a) AND (t1_3.c = t2_3.c) AND (t1_3.d = t2_3.d))
+               ->  Seq Scan on plt1_adv_m_p3 t1_3
+                     Filter: (b < 10)
+               ->  Seq Scan on plt2_adv_m_p3 t2_3
+(18 rows)
+
+SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a;
+ a | b |  c   | d 
+---+---+------+---
+ 1 | 1 | 0001 | 1
+ 8 | 8 | 0008 | 8
+(2 rows)
+
+-- anti join; currently we can't do partitioned join if there are no matched
+-- partitions on the nullable side
+EXPLAIN (COSTS OFF)
+SELECT t1.* FROM plt2_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt1_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a;
+                               QUERY PLAN                               
+------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a
+   ->  Hash Anti Join
+         Hash Cond: ((t1.a = t2.a) AND (t1.c = t2.c) AND (t1.d = t2.d))
+         ->  Append
+               ->  Seq Scan on plt2_adv_m_extra t1_1
+                     Filter: (b < 10)
+               ->  Seq Scan on plt2_adv_m_p1 t1_2
+                     Filter: (b < 10)
+               ->  Seq Scan on plt2_adv_m_p2 t1_3
+                     Filter: (b < 10)
+               ->  Seq Scan on plt2_adv_m_p3 t1_4
+                     Filter: (b < 10)
+         ->  Hash
+               ->  Append
+                     ->  Seq Scan on plt1_adv_m_p1 t2_1
+                     ->  Seq Scan on plt1_adv_m_p2 t2_2
+                     ->  Seq Scan on plt1_adv_m_p3 t2_3
+(18 rows)
+
+-- full join; currently we can't do partitioned join if there are no matched
+-- partitions on the nullable side
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a;
+                               QUERY PLAN                                
+-------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a, t2.a
+   ->  Hash Full Join
+         Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c) AND (t2.d = t1.d))
+         Filter: ((COALESCE(t1.b, 0) < 10) AND (COALESCE(t2.b, 0) < 10))
+         ->  Append
+               ->  Seq Scan on plt2_adv_m_extra t2_1
+               ->  Seq Scan on plt2_adv_m_p1 t2_2
+               ->  Seq Scan on plt2_adv_m_p2 t2_3
+               ->  Seq Scan on plt2_adv_m_p3 t2_4
+         ->  Hash
+               ->  Append
+                     ->  Seq Scan on plt1_adv_m_p1 t1_1
+                     ->  Seq Scan on plt1_adv_m_p2 t1_2
+                     ->  Seq Scan on plt1_adv_m_p3 t1_3
+(15 rows)
+
+DROP TABLE plt2_adv_m_extra;
+-- Test cases where a partition on one side matches multiple partitions on
+-- the other side; we currently can't do partitioned join in such cases
+ALTER TABLE plt2_adv_m DETACH PARTITION plt2_adv_m_p2;
+-- Split plt2_adv_p2 into two partitions so that plt1_adv_p2 matches both
+CREATE TABLE plt2_adv_m_p2_1 PARTITION OF plt2_adv_m FOR VALUES IN (('0004', 4));
+CREATE TABLE plt2_adv_m_p2_2 PARTITION OF plt2_adv_m FOR VALUES IN (('0006', 6));
+INSERT INTO plt2_adv_m SELECT i, i, to_char(i % 10, 'FM0000'), (i % 10) FROM generate_series(1, 299) i WHERE i % 10 IN (4, 6);
+ANALYZE plt2_adv_m;
+-- inner join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+                               QUERY PLAN                               
+------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a
+   ->  Hash Join
+         Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c) AND (t2.d = t1.d))
+         ->  Append
+               ->  Seq Scan on plt2_adv_m_p1 t2_1
+               ->  Seq Scan on plt2_adv_m_p2_1 t2_2
+               ->  Seq Scan on plt2_adv_m_p2_2 t2_3
+               ->  Seq Scan on plt2_adv_m_p3 t2_4
+         ->  Hash
+               ->  Append
+                     ->  Seq Scan on plt1_adv_m_p1 t1_1
+                           Filter: (b < 10)
+                     ->  Seq Scan on plt1_adv_m_p2 t1_2
+                           Filter: (b < 10)
+                     ->  Seq Scan on plt1_adv_m_p3 t1_3
+                           Filter: (b < 10)
+(17 rows)
+
+-- semi join
+EXPLAIN (COSTS OFF)
+SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a;
+                               QUERY PLAN                               
+------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a
+   ->  Hash Semi Join
+         Hash Cond: ((t1.a = t2.a) AND (t1.c = t2.c) AND (t1.d = t2.d))
+         ->  Append
+               ->  Seq Scan on plt1_adv_m_p1 t1_1
+                     Filter: (b < 10)
+               ->  Seq Scan on plt1_adv_m_p2 t1_2
+                     Filter: (b < 10)
+               ->  Seq Scan on plt1_adv_m_p3 t1_3
+                     Filter: (b < 10)
+         ->  Hash
+               ->  Append
+                     ->  Seq Scan on plt2_adv_m_p1 t2_1
+                     ->  Seq Scan on plt2_adv_m_p2_1 t2_2
+                     ->  Seq Scan on plt2_adv_m_p2_2 t2_3
+                     ->  Seq Scan on plt2_adv_m_p3 t2_4
+(17 rows)
+
+-- left join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+                               QUERY PLAN                               
+------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a
+   ->  Hash Right Join
+         Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c) AND (t2.d = t1.d))
+         ->  Append
+               ->  Seq Scan on plt2_adv_m_p1 t2_1
+               ->  Seq Scan on plt2_adv_m_p2_1 t2_2
+               ->  Seq Scan on plt2_adv_m_p2_2 t2_3
+               ->  Seq Scan on plt2_adv_m_p3 t2_4
+         ->  Hash
+               ->  Append
+                     ->  Seq Scan on plt1_adv_m_p1 t1_1
+                           Filter: (b < 10)
+                     ->  Seq Scan on plt1_adv_m_p2 t1_2
+                           Filter: (b < 10)
+                     ->  Seq Scan on plt1_adv_m_p3 t1_3
+                           Filter: (b < 10)
+(17 rows)
+
+-- anti join
+EXPLAIN (COSTS OFF)
+SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a;
+                               QUERY PLAN                               
+------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a
+   ->  Hash Anti Join
+         Hash Cond: ((t1.a = t2.a) AND (t1.c = t2.c) AND (t1.d = t2.d))
+         ->  Append
+               ->  Seq Scan on plt1_adv_m_p1 t1_1
+                     Filter: (b < 10)
+               ->  Seq Scan on plt1_adv_m_p2 t1_2
+                     Filter: (b < 10)
+               ->  Seq Scan on plt1_adv_m_p3 t1_3
+                     Filter: (b < 10)
+         ->  Hash
+               ->  Append
+                     ->  Seq Scan on plt2_adv_m_p1 t2_1
+                     ->  Seq Scan on plt2_adv_m_p2_1 t2_2
+                     ->  Seq Scan on plt2_adv_m_p2_2 t2_3
+                     ->  Seq Scan on plt2_adv_m_p3 t2_4
+(17 rows)
+
+-- full join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a;
+                               QUERY PLAN                                
+-------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a, t2.a
+   ->  Hash Full Join
+         Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c) AND (t2.d = t1.d))
+         Filter: ((COALESCE(t1.b, 0) < 10) AND (COALESCE(t2.b, 0) < 10))
+         ->  Append
+               ->  Seq Scan on plt2_adv_m_p1 t2_1
+               ->  Seq Scan on plt2_adv_m_p2_1 t2_2
+               ->  Seq Scan on plt2_adv_m_p2_2 t2_3
+               ->  Seq Scan on plt2_adv_m_p3 t2_4
+         ->  Hash
+               ->  Append
+                     ->  Seq Scan on plt1_adv_m_p1 t1_1
+                     ->  Seq Scan on plt1_adv_m_p2 t1_2
+                     ->  Seq Scan on plt1_adv_m_p3 t1_3
+(15 rows)
+
+DROP TABLE plt2_adv_m_p2_1;
+DROP TABLE plt2_adv_m_p2_2;
+-- Restore plt2_adv_p2
+ALTER TABLE plt2_adv_m ATTACH PARTITION plt2_adv_m_p2 FOR VALUES IN (('0004', 4), ('0006', 6));
+-- Test NULL partitions
+ALTER TABLE plt1_adv_m DETACH PARTITION plt1_adv_m_p1;
+-- Change plt1_adv_p1 to the NULL partition
+CREATE TABLE plt1_adv_m_p1_null PARTITION OF plt1_adv_m FOR VALUES IN ((NULL, NULL), ('0001', 1), ('0003', 3));
+INSERT INTO plt1_adv_m SELECT i, i, to_char(i % 10, 'FM0000'), (i % 10) FROM generate_series(1, 299) i WHERE i % 10 IN (1, 3);
+INSERT INTO plt1_adv_m VALUES (-1, -1, NULL, NULL);
+ANALYZE plt1_adv_m;
+ALTER TABLE plt2_adv_m DETACH PARTITION plt2_adv_m_p3;
+-- Change plt2_adv_p3 to the NULL partition
+CREATE TABLE plt2_adv_m_p3_null PARTITION OF plt2_adv_m FOR VALUES IN ((NULL, NULL), ('0007', 7), ('0009', 9));
+INSERT INTO plt2_adv_m SELECT i, i, to_char(i % 10, 'FM0000'), (i % 10) FROM generate_series(1, 299) i WHERE i % 10 IN (7, 9);
+INSERT INTO plt2_adv_m VALUES (-1, -1, NULL, NULL);
+ANALYZE plt2_adv_m;
+-- inner join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+                                        QUERY PLAN                                        
+------------------------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a
+   ->  Append
+         ->  Hash Join
+               Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c) AND (t2_1.d = t1_1.d))
+               ->  Seq Scan on plt2_adv_m_p1 t2_1
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p1_null t1_1
+                           Filter: (b < 10)
+         ->  Hash Join
+               Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c) AND (t2_2.d = t1_2.d))
+               ->  Seq Scan on plt2_adv_m_p2 t2_2
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p2 t1_2
+                           Filter: (b < 10)
+         ->  Hash Join
+               Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c) AND (t2_3.d = t1_3.d))
+               ->  Seq Scan on plt2_adv_m_p3_null t2_3
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p3 t1_3
+                           Filter: (b < 10)
+(21 rows)
+
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+ a |  c   | d | a |  c   | d 
+---+------+---+---+------+---
+ 3 | 0003 | 3 | 3 | 0003 | 3
+ 4 | 0004 | 4 | 4 | 0004 | 4
+ 6 | 0006 | 6 | 6 | 0006 | 6
+ 9 | 0009 | 9 | 9 | 0009 | 9
+(4 rows)
+
+-- semi join
+EXPLAIN (COSTS OFF)
+SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a;
+                                         QUERY PLAN                                         
+--------------------------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a
+   ->  Append
+         ->  Hash Semi Join
+               Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c) AND (t1_1.d = t2_1.d))
+               ->  Seq Scan on plt1_adv_m_p1_null t1_1
+                     Filter: (b < 10)
+               ->  Hash
+                     ->  Seq Scan on plt2_adv_m_p1 t2_1
+         ->  Nested Loop Semi Join
+               Join Filter: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c) AND (t1_2.d = t2_2.d))
+               ->  Seq Scan on plt1_adv_m_p2 t1_2
+                     Filter: (b < 10)
+               ->  Seq Scan on plt2_adv_m_p2 t2_2
+         ->  Nested Loop Semi Join
+               Join Filter: ((t1_3.a = t2_3.a) AND (t1_3.c = t2_3.c) AND (t1_3.d = t2_3.d))
+               ->  Seq Scan on plt1_adv_m_p3 t1_3
+                     Filter: (b < 10)
+               ->  Seq Scan on plt2_adv_m_p3_null t2_3
+(19 rows)
+
+SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a;
+ a | b |  c   | d 
+---+---+------+---
+ 3 | 3 | 0003 | 3
+ 4 | 4 | 0004 | 4
+ 6 | 6 | 0006 | 6
+ 9 | 9 | 0009 | 9
+(4 rows)
+
+-- left join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+                                        QUERY PLAN                                        
+------------------------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a
+   ->  Append
+         ->  Hash Right Join
+               Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c) AND (t2_1.d = t1_1.d))
+               ->  Seq Scan on plt2_adv_m_p1 t2_1
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p1_null t1_1
+                           Filter: (b < 10)
+         ->  Hash Right Join
+               Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c) AND (t2_2.d = t1_2.d))
+               ->  Seq Scan on plt2_adv_m_p2 t2_2
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p2 t1_2
+                           Filter: (b < 10)
+         ->  Hash Right Join
+               Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c) AND (t2_3.d = t1_3.d))
+               ->  Seq Scan on plt2_adv_m_p3_null t2_3
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p3 t1_3
+                           Filter: (b < 10)
+(21 rows)
+
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+ a  |  c   | d | a |  c   | d 
+----+------+---+---+------+---
+ -1 |      |   |   |      |  
+  1 | 0001 | 1 |   |      |  
+  3 | 0003 | 3 | 3 | 0003 | 3
+  4 | 0004 | 4 | 4 | 0004 | 4
+  6 | 0006 | 6 | 6 | 0006 | 6
+  8 | 0008 | 8 |   |      |  
+  9 | 0009 | 9 | 9 | 0009 | 9
+(7 rows)
+
+-- anti join
+EXPLAIN (COSTS OFF)
+SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a;
+                                         QUERY PLAN                                         
+--------------------------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a
+   ->  Append
+         ->  Hash Anti Join
+               Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c) AND (t1_1.d = t2_1.d))
+               ->  Seq Scan on plt1_adv_m_p1_null t1_1
+                     Filter: (b < 10)
+               ->  Hash
+                     ->  Seq Scan on plt2_adv_m_p1 t2_1
+         ->  Nested Loop Anti Join
+               Join Filter: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c) AND (t1_2.d = t2_2.d))
+               ->  Seq Scan on plt1_adv_m_p2 t1_2
+                     Filter: (b < 10)
+               ->  Seq Scan on plt2_adv_m_p2 t2_2
+         ->  Nested Loop Anti Join
+               Join Filter: ((t1_3.a = t2_3.a) AND (t1_3.c = t2_3.c) AND (t1_3.d = t2_3.d))
+               ->  Seq Scan on plt1_adv_m_p3 t1_3
+                     Filter: (b < 10)
+               ->  Seq Scan on plt2_adv_m_p3_null t2_3
+(19 rows)
+
+SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a;
+ a  | b  |  c   | d 
+----+----+------+---
+ -1 | -1 |      |  
+  1 |  1 | 0001 | 1
+  8 |  8 | 0008 | 8
+(3 rows)
+
+-- full join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a;
+                                        QUERY PLAN                                        
+------------------------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a, t2.a
+   ->  Append
+         ->  Hash Full Join
+               Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c) AND (t1_1.d = t2_1.d))
+               Filter: ((COALESCE(t1_1.b, 0) < 10) AND (COALESCE(t2_1.b, 0) < 10))
+               ->  Seq Scan on plt1_adv_m_p1_null t1_1
+               ->  Hash
+                     ->  Seq Scan on plt2_adv_m_p1 t2_1
+         ->  Hash Full Join
+               Hash Cond: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c) AND (t1_2.d = t2_2.d))
+               Filter: ((COALESCE(t1_2.b, 0) < 10) AND (COALESCE(t2_2.b, 0) < 10))
+               ->  Seq Scan on plt1_adv_m_p2 t1_2
+               ->  Hash
+                     ->  Seq Scan on plt2_adv_m_p2 t2_2
+         ->  Hash Full Join
+               Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c) AND (t2_3.d = t1_3.d))
+               Filter: ((COALESCE(t1_3.b, 0) < 10) AND (COALESCE(t2_3.b, 0) < 10))
+               ->  Seq Scan on plt2_adv_m_p3_null t2_3
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p3 t1_3
+(21 rows)
+
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a;
+ a  |  c   | d | a  |  c   | d 
+----+------+---+----+------+---
+ -1 |      |   |    |      |  
+  1 | 0001 | 1 |    |      |  
+  3 | 0003 | 3 |  3 | 0003 | 3
+  4 | 0004 | 4 |  4 | 0004 | 4
+  6 | 0006 | 6 |  6 | 0006 | 6
+  8 | 0008 | 8 |    |      |  
+  9 | 0009 | 9 |  9 | 0009 | 9
+    |      |   | -1 |      |  
+    |      |   |  2 | 0002 | 2
+    |      |   |  7 | 0007 | 7
+(10 rows)
+
+DROP TABLE plt1_adv_m_p1_null;
+-- Restore plt1_adv_p1
+ALTER TABLE plt1_adv_m ATTACH PARTITION plt1_adv_m_p1 FOR VALUES IN (('0001', 1), ('0003', 3));
+-- Add to plt1_adv the extra NULL partition containing only NULL values as the
+-- key values
+CREATE TABLE plt1_adv_m_extra PARTITION OF plt1_adv_m FOR VALUES IN ((NULL, NULL));
+INSERT INTO plt1_adv_m VALUES (-1, -1, NULL, NULL);
+ANALYZE plt1_adv_m;
+DROP TABLE plt2_adv_m_p3_null;
+-- Restore plt2_adv_p3
+ALTER TABLE plt2_adv_m ATTACH PARTITION plt2_adv_m_p3 FOR VALUES IN (('0007', 7), ('0009', 9));
+ANALYZE plt2_adv_m;
+-- inner join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+                                        QUERY PLAN                                        
+------------------------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a
+   ->  Append
+         ->  Hash Join
+               Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c) AND (t2_1.d = t1_1.d))
+               ->  Seq Scan on plt2_adv_m_p1 t2_1
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p1 t1_1
+                           Filter: (b < 10)
+         ->  Hash Join
+               Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c) AND (t2_2.d = t1_2.d))
+               ->  Seq Scan on plt2_adv_m_p2 t2_2
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p2 t1_2
+                           Filter: (b < 10)
+         ->  Hash Join
+               Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c) AND (t2_3.d = t1_3.d))
+               ->  Seq Scan on plt2_adv_m_p3 t2_3
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p3 t1_3
+                           Filter: (b < 10)
+(21 rows)
+
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+ a |  c   | d | a |  c   | d 
+---+------+---+---+------+---
+ 3 | 0003 | 3 | 3 | 0003 | 3
+ 4 | 0004 | 4 | 4 | 0004 | 4
+ 6 | 0006 | 6 | 6 | 0006 | 6
+ 9 | 0009 | 9 | 9 | 0009 | 9
+(4 rows)
+
+-- left join; currently we can't do partitioned join if there are no matched
+-- partitions on the nullable side
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+                               QUERY PLAN                               
+------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a
+   ->  Hash Right Join
+         Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c) AND (t2.d = t1.d))
+         ->  Append
+               ->  Seq Scan on plt2_adv_m_p1 t2_1
+               ->  Seq Scan on plt2_adv_m_p2 t2_2
+               ->  Seq Scan on plt2_adv_m_p3 t2_3
+         ->  Hash
+               ->  Append
+                     ->  Seq Scan on plt1_adv_m_p1 t1_1
+                           Filter: (b < 10)
+                     ->  Seq Scan on plt1_adv_m_p2 t1_2
+                           Filter: (b < 10)
+                     ->  Seq Scan on plt1_adv_m_p3 t1_3
+                           Filter: (b < 10)
+                     ->  Seq Scan on plt1_adv_m_extra t1_4
+                           Filter: (b < 10)
+(18 rows)
+
+-- full join; currently we can't do partitioned join if there are no matched
+-- partitions on the nullable side
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a;
+                               QUERY PLAN                                
+-------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a, t2.a
+   ->  Hash Full Join
+         Hash Cond: ((t1.a = t2.a) AND (t1.c = t2.c) AND (t1.d = t2.d))
+         Filter: ((COALESCE(t1.b, 0) < 10) AND (COALESCE(t2.b, 0) < 10))
+         ->  Append
+               ->  Seq Scan on plt1_adv_m_p1 t1_1
+               ->  Seq Scan on plt1_adv_m_p2 t1_2
+               ->  Seq Scan on plt1_adv_m_p3 t1_3
+               ->  Seq Scan on plt1_adv_m_extra t1_4
+         ->  Hash
+               ->  Append
+                     ->  Seq Scan on plt2_adv_m_p1 t2_1
+                     ->  Seq Scan on plt2_adv_m_p2 t2_2
+                     ->  Seq Scan on plt2_adv_m_p3 t2_3
+(15 rows)
+
+-- Add to plt2_adv the extra NULL partition containing only NULL values as the
+-- key values
+CREATE TABLE plt2_adv_m_extra PARTITION OF plt2_adv_m FOR VALUES IN ((NULL, NULL));
+INSERT INTO plt2_adv_m VALUES (-1, -1, NULL, NULL);
+ANALYZE plt2_adv_m;
+-- inner join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+                                        QUERY PLAN                                        
+------------------------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a
+   ->  Append
+         ->  Hash Join
+               Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c) AND (t2_1.d = t1_1.d))
+               ->  Seq Scan on plt2_adv_m_p1 t2_1
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p1 t1_1
+                           Filter: (b < 10)
+         ->  Hash Join
+               Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c) AND (t2_2.d = t1_2.d))
+               ->  Seq Scan on plt2_adv_m_p2 t2_2
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p2 t1_2
+                           Filter: (b < 10)
+         ->  Hash Join
+               Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c) AND (t2_3.d = t1_3.d))
+               ->  Seq Scan on plt2_adv_m_p3 t2_3
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p3 t1_3
+                           Filter: (b < 10)
+(21 rows)
+
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+ a |  c   | d | a |  c   | d 
+---+------+---+---+------+---
+ 3 | 0003 | 3 | 3 | 0003 | 3
+ 4 | 0004 | 4 | 4 | 0004 | 4
+ 6 | 0006 | 6 | 6 | 0006 | 6
+ 9 | 0009 | 9 | 9 | 0009 | 9
+(4 rows)
+
+-- left join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+                                         QUERY PLAN                                         
+--------------------------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a
+   ->  Append
+         ->  Hash Right Join
+               Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c) AND (t2_1.d = t1_1.d))
+               ->  Seq Scan on plt2_adv_m_p1 t2_1
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p1 t1_1
+                           Filter: (b < 10)
+         ->  Hash Right Join
+               Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c) AND (t2_2.d = t1_2.d))
+               ->  Seq Scan on plt2_adv_m_p2 t2_2
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p2 t1_2
+                           Filter: (b < 10)
+         ->  Hash Right Join
+               Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c) AND (t2_3.d = t1_3.d))
+               ->  Seq Scan on plt2_adv_m_p3 t2_3
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p3 t1_3
+                           Filter: (b < 10)
+         ->  Nested Loop Left Join
+               Join Filter: ((t1_4.a = t2_4.a) AND (t1_4.c = t2_4.c) AND (t1_4.d = t2_4.d))
+               ->  Seq Scan on plt1_adv_m_extra t1_4
+                     Filter: (b < 10)
+               ->  Seq Scan on plt2_adv_m_extra t2_4
+(26 rows)
+
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+ a  |  c   | d | a |  c   | d 
+----+------+---+---+------+---
+ -1 |      |   |   |      |  
+  1 | 0001 | 1 |   |      |  
+  3 | 0003 | 3 | 3 | 0003 | 3
+  4 | 0004 | 4 | 4 | 0004 | 4
+  6 | 0006 | 6 | 6 | 0006 | 6
+  8 | 0008 | 8 |   |      |  
+  9 | 0009 | 9 | 9 | 0009 | 9
+(7 rows)
+
+-- full join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a;
+                                        QUERY PLAN                                        
+------------------------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a, t2.a
+   ->  Append
+         ->  Hash Full Join
+               Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c) AND (t1_1.d = t2_1.d))
+               Filter: ((COALESCE(t1_1.b, 0) < 10) AND (COALESCE(t2_1.b, 0) < 10))
+               ->  Seq Scan on plt1_adv_m_p1 t1_1
+               ->  Hash
+                     ->  Seq Scan on plt2_adv_m_p1 t2_1
+         ->  Hash Full Join
+               Hash Cond: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c) AND (t1_2.d = t2_2.d))
+               Filter: ((COALESCE(t1_2.b, 0) < 10) AND (COALESCE(t2_2.b, 0) < 10))
+               ->  Seq Scan on plt1_adv_m_p2 t1_2
+               ->  Hash
+                     ->  Seq Scan on plt2_adv_m_p2 t2_2
+         ->  Hash Full Join
+               Hash Cond: ((t1_3.a = t2_3.a) AND (t1_3.c = t2_3.c) AND (t1_3.d = t2_3.d))
+               Filter: ((COALESCE(t1_3.b, 0) < 10) AND (COALESCE(t2_3.b, 0) < 10))
+               ->  Seq Scan on plt1_adv_m_p3 t1_3
+               ->  Hash
+                     ->  Seq Scan on plt2_adv_m_p3 t2_3
+         ->  Hash Full Join
+               Hash Cond: ((t1_4.a = t2_4.a) AND (t1_4.c = t2_4.c) AND (t1_4.d = t2_4.d))
+               Filter: ((COALESCE(t1_4.b, 0) < 10) AND (COALESCE(t2_4.b, 0) < 10))
+               ->  Seq Scan on plt1_adv_m_extra t1_4
+               ->  Hash
+                     ->  Seq Scan on plt2_adv_m_extra t2_4
+(27 rows)
+
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a;
+ a  |  c   | d | a  |  c   | d 
+----+------+---+----+------+---
+ -1 |      |   |    |      |  
+  1 | 0001 | 1 |    |      |  
+  3 | 0003 | 3 |  3 | 0003 | 3
+  4 | 0004 | 4 |  4 | 0004 | 4
+  6 | 0006 | 6 |  6 | 0006 | 6
+  8 | 0008 | 8 |    |      |  
+  9 | 0009 | 9 |  9 | 0009 | 9
+    |      |   | -1 |      |  
+    |      |   |  2 | 0002 | 2
+    |      |   |  7 | 0007 | 7
+(10 rows)
+
+-- 3-way join to test the NULL partition of a join relation
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d, t3.a, t3.c, t3.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) LEFT JOIN plt1_adv_m t3 ON (t1.a = t3.a AND t1.c = t3.c AND t1.d = t3.d) WHERE t1.b < 10 ORDER BY t1.a;
+                                              QUERY PLAN                                              
+------------------------------------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a
+   ->  Append
+         ->  Hash Right Join
+               Hash Cond: ((t3_1.a = t1_1.a) AND (t3_1.c = t1_1.c) AND (t3_1.d = t1_1.d))
+               ->  Seq Scan on plt1_adv_m_p1 t3_1
+               ->  Hash
+                     ->  Hash Right Join
+                           Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c) AND (t2_1.d = t1_1.d))
+                           ->  Seq Scan on plt2_adv_m_p1 t2_1
+                           ->  Hash
+                                 ->  Seq Scan on plt1_adv_m_p1 t1_1
+                                       Filter: (b < 10)
+         ->  Hash Right Join
+               Hash Cond: ((t3_2.a = t1_2.a) AND (t3_2.c = t1_2.c) AND (t3_2.d = t1_2.d))
+               ->  Seq Scan on plt1_adv_m_p2 t3_2
+               ->  Hash
+                     ->  Hash Right Join
+                           Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c) AND (t2_2.d = t1_2.d))
+                           ->  Seq Scan on plt2_adv_m_p2 t2_2
+                           ->  Hash
+                                 ->  Seq Scan on plt1_adv_m_p2 t1_2
+                                       Filter: (b < 10)
+         ->  Hash Right Join
+               Hash Cond: ((t3_3.a = t1_3.a) AND (t3_3.c = t1_3.c) AND (t3_3.d = t1_3.d))
+               ->  Seq Scan on plt1_adv_m_p3 t3_3
+               ->  Hash
+                     ->  Hash Right Join
+                           Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c) AND (t2_3.d = t1_3.d))
+                           ->  Seq Scan on plt2_adv_m_p3 t2_3
+                           ->  Hash
+                                 ->  Seq Scan on plt1_adv_m_p3 t1_3
+                                       Filter: (b < 10)
+         ->  Nested Loop Left Join
+               Join Filter: ((t1_4.a = t3_4.a) AND (t1_4.c = t3_4.c) AND (t1_4.d = t3_4.d))
+               ->  Nested Loop Left Join
+                     Join Filter: ((t1_4.a = t2_4.a) AND (t1_4.c = t2_4.c) AND (t1_4.d = t2_4.d))
+                     ->  Seq Scan on plt1_adv_m_extra t1_4
+                           Filter: (b < 10)
+                     ->  Seq Scan on plt2_adv_m_extra t2_4
+               ->  Seq Scan on plt1_adv_m_extra t3_4
+(41 rows)
+
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d, t3.a, t3.c, t3.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) LEFT JOIN plt1_adv_m t3 ON (t1.a = t3.a AND t1.c = t3.c AND t1.d = t3.d) WHERE t1.b < 10 ORDER BY t1.a;
+ a  |  c   | d | a |  c   | d | a |  c   | d 
+----+------+---+---+------+---+---+------+---
+ -1 |      |   |   |      |   |   |      |  
+  1 | 0001 | 1 |   |      |   | 1 | 0001 | 1
+  3 | 0003 | 3 | 3 | 0003 | 3 | 3 | 0003 | 3
+  4 | 0004 | 4 | 4 | 0004 | 4 | 4 | 0004 | 4
+  6 | 0006 | 6 | 6 | 0006 | 6 | 6 | 0006 | 6
+  8 | 0008 | 8 |   |      |   | 8 | 0008 | 8
+  9 | 0009 | 9 | 9 | 0009 | 9 | 9 | 0009 | 9
+(7 rows)
+
+DROP TABLE plt1_adv_m_extra;
+DROP TABLE plt2_adv_m_extra;
+-- Multiple NULL test
+CREATE TABLE plt1_adv_m_p4 PARTITION OF plt1_adv_m FOR VALUES IN (('0005', NULL));
+CREATE TABLE plt1_adv_m_p5 PARTITION OF plt1_adv_m FOR VALUES IN (('0010', NULL), (NULL, 10));
+INSERT INTO plt1_adv_m VALUES (-1, -1, '0005', NULL);
+INSERT INTO plt1_adv_m VALUES (-1, -1, '0010', NULL);
+INSERT INTO plt1_adv_m VALUES (-1, -1, NULL, 10);
+ANALYZE plt1_adv_m;
+CREATE TABLE plt2_adv_m_p4 PARTITION OF plt2_adv_m FOR VALUES IN ((NULL, 5));
+CREATE TABLE plt2_adv_m_p5 PARTITION OF plt2_adv_m FOR VALUES IN (('0010', NULL), (NULL, 10));
+INSERT INTO plt2_adv_m VALUES (-1, -1, '0005', NULL);
+ERROR:  no partition of relation "plt2_adv_m" found for row
+DETAIL:  Partition key of the failing row contains (c, d) = (0005, null).
+INSERT INTO plt2_adv_m VALUES (-1, -1, '0010', NULL);
+INSERT INTO plt2_adv_m VALUES (-1, -1, NULL, 10);
+ANALYZE plt2_adv_m;
+-- inner join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a;
+                                        QUERY PLAN                                        
+------------------------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a
+   ->  Append
+         ->  Hash Join
+               Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c) AND (t2_1.d = t1_1.d))
+               ->  Seq Scan on plt2_adv_m_p1 t2_1
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p1 t1_1
+                           Filter: (a < 10)
+         ->  Hash Join
+               Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c) AND (t2_2.d = t1_2.d))
+               ->  Seq Scan on plt2_adv_m_p2 t2_2
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p2 t1_2
+                           Filter: (a < 10)
+         ->  Hash Join
+               Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c) AND (t2_3.d = t1_3.d))
+               ->  Seq Scan on plt2_adv_m_p3 t2_3
+               ->  Hash
+                     ->  Seq Scan on plt1_adv_m_p3 t1_3
+                           Filter: (a < 10)
+(21 rows)
+
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a;
+ a |  c   | d | a |  c   | d 
+---+------+---+---+------+---
+ 3 | 0003 | 3 | 3 | 0003 | 3
+ 4 | 0004 | 4 | 4 | 0004 | 4
+ 6 | 0006 | 6 | 6 | 0006 | 6
+ 9 | 0009 | 9 | 9 | 0009 | 9
+(4 rows)
+
+-- semi join
+EXPLAIN (COSTS OFF)
+SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a;
+                                         QUERY PLAN                                         
+--------------------------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a
+   ->  Append
+         ->  Nested Loop Semi Join
+               Join Filter: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c) AND (t1_1.d = t2_1.d))
+               ->  Seq Scan on plt1_adv_m_p1 t1_1
+                     Filter: (a < 10)
+               ->  Seq Scan on plt2_adv_m_p1 t2_1
+         ->  Nested Loop Semi Join
+               Join Filter: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c) AND (t1_2.d = t2_2.d))
+               ->  Seq Scan on plt1_adv_m_p2 t1_2
+                     Filter: (a < 10)
+               ->  Seq Scan on plt2_adv_m_p2 t2_2
+         ->  Nested Loop Semi Join
+               Join Filter: ((t1_3.a = t2_3.a) AND (t1_3.c = t2_3.c) AND (t1_3.d = t2_3.d))
+               ->  Seq Scan on plt1_adv_m_p3 t1_3
+                     Filter: (a < 10)
+               ->  Seq Scan on plt2_adv_m_p3 t2_3
+(18 rows)
+
+SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a;
+ a | b |  c   | d 
+---+---+------+---
+ 3 | 3 | 0003 | 3
+ 4 | 4 | 0004 | 4
+ 6 | 6 | 0006 | 6
+ 9 | 9 | 0009 | 9
+(4 rows)
+
+-- left join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a;
+                               QUERY PLAN                               
+------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a
+   ->  Hash Right Join
+         Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c) AND (t2.d = t1.d))
+         ->  Append
+               ->  Seq Scan on plt2_adv_m_p1 t2_1
+               ->  Seq Scan on plt2_adv_m_p2 t2_2
+               ->  Seq Scan on plt2_adv_m_p3 t2_3
+               ->  Seq Scan on plt2_adv_m_p5 t2_4
+               ->  Seq Scan on plt2_adv_m_p4 t2_5
+         ->  Hash
+               ->  Append
+                     ->  Seq Scan on plt1_adv_m_p1 t1_1
+                           Filter: (a < 10)
+                     ->  Seq Scan on plt1_adv_m_p2 t1_2
+                           Filter: (a < 10)
+                     ->  Seq Scan on plt1_adv_m_p4 t1_3
+                           Filter: (a < 10)
+                     ->  Seq Scan on plt1_adv_m_p3 t1_4
+                           Filter: (a < 10)
+                     ->  Seq Scan on plt1_adv_m_p5 t1_5
+                           Filter: (a < 10)
+(22 rows)
+
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a;
+ a  |  c   | d  | a |  c   | d 
+----+------+----+---+------+---
+ -1 | 0010 |    |   |      |  
+ -1 |      | 10 |   |      |  
+ -1 | 0005 |    |   |      |  
+  1 | 0001 |  1 |   |      |  
+  3 | 0003 |  3 | 3 | 0003 | 3
+  4 | 0004 |  4 | 4 | 0004 | 4
+  6 | 0006 |  6 | 6 | 0006 | 6
+  8 | 0008 |  8 |   |      |  
+  9 | 0009 |  9 | 9 | 0009 | 9
+(9 rows)
+
+-- anti join
+EXPLAIN (COSTS OFF)
+SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a;
+                               QUERY PLAN                               
+------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a
+   ->  Hash Anti Join
+         Hash Cond: ((t1.a = t2.a) AND (t1.c = t2.c) AND (t1.d = t2.d))
+         ->  Append
+               ->  Seq Scan on plt1_adv_m_p1 t1_1
+                     Filter: (a < 10)
+               ->  Seq Scan on plt1_adv_m_p2 t1_2
+                     Filter: (a < 10)
+               ->  Seq Scan on plt1_adv_m_p4 t1_3
+                     Filter: (a < 10)
+               ->  Seq Scan on plt1_adv_m_p3 t1_4
+                     Filter: (a < 10)
+               ->  Seq Scan on plt1_adv_m_p5 t1_5
+                     Filter: (a < 10)
+         ->  Hash
+               ->  Append
+                     ->  Seq Scan on plt2_adv_m_p1 t2_1
+                     ->  Seq Scan on plt2_adv_m_p2 t2_2
+                     ->  Seq Scan on plt2_adv_m_p3 t2_3
+                     ->  Seq Scan on plt2_adv_m_p5 t2_4
+                     ->  Seq Scan on plt2_adv_m_p4 t2_5
+(22 rows)
+
+SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a;
+ a  | b  |  c   | d  
+----+----+------+----
+ -1 | -1 | 0005 |   
+ -1 | -1 | 0010 |   
+ -1 | -1 |      | 10
+  1 |  1 | 0001 |  1
+  8 |  8 | 0008 |  8
+(5 rows)
+
+-- full join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.a, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a;
+                               QUERY PLAN                                
+-------------------------------------------------------------------------
+ Sort
+   Sort Key: t1.a, t2.a
+   ->  Hash Full Join
+         Hash Cond: ((t1.a = t2.a) AND (t1.c = t2.c) AND (t1.d = t2.d))
+         Filter: ((COALESCE(t1.a, 0) < 10) AND (COALESCE(t2.b, 0) < 10))
+         ->  Append
+               ->  Seq Scan on plt1_adv_m_p1 t1_1
+               ->  Seq Scan on plt1_adv_m_p2 t1_2
+               ->  Seq Scan on plt1_adv_m_p4 t1_3
+               ->  Seq Scan on plt1_adv_m_p3 t1_4
+               ->  Seq Scan on plt1_adv_m_p5 t1_5
+         ->  Hash
+               ->  Append
+                     ->  Seq Scan on plt2_adv_m_p1 t2_1
+                     ->  Seq Scan on plt2_adv_m_p2 t2_2
+                     ->  Seq Scan on plt2_adv_m_p3 t2_3
+                     ->  Seq Scan on plt2_adv_m_p5 t2_4
+                     ->  Seq Scan on plt2_adv_m_p4 t2_5
+(18 rows)
+
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.a, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a;
+ a  |  c   | d  | a  |  c   | d  
+----+------+----+----+------+----
+ -1 | 0010 |    |    |      |   
+ -1 | 0005 |    |    |      |   
+ -1 |      | 10 |    |      |   
+  1 | 0001 |  1 |    |      |   
+  3 | 0003 |  3 |  3 | 0003 |  3
+  4 | 0004 |  4 |  4 | 0004 |  4
+  6 | 0006 |  6 |  6 | 0006 |  6
+  8 | 0008 |  8 |    |      |   
+  9 | 0009 |  9 |  9 | 0009 |  9
+    |      |    | -1 | 0010 |   
+    |      |    | -1 |      | 10
+    |      |    |  2 | 0002 |  2
+    |      |    |  7 | 0007 |  7
+(13 rows)
+
 -- Tests for multi-level partitioned tables
 CREATE TABLE alpha (a double precision, b int, c text) PARTITION BY RANGE (a);
 CREATE TABLE alpha_neg PARTITION OF alpha FOR VALUES FROM ('-Infinity') TO (0) PARTITION BY RANGE (b);
diff --git a/src/test/regress/sql/partition_join.sql b/src/test/regress/sql/partition_join.sql
index d97b5b6..ca0ec38 100644
--- a/src/test/regress/sql/partition_join.sql
+++ b/src/test/regress/sql/partition_join.sql
@@ -1100,6 +1100,263 @@ DROP TABLE plt2_adv;
 DROP TABLE plt3_adv;
 
 
+-- Tests for multi-column list-partitioned tables
+CREATE TABLE plt1_adv_m (a int, b int, c text, d int) PARTITION BY LIST (c, d);
+CREATE TABLE plt1_adv_m_p1 PARTITION OF plt1_adv_m FOR VALUES IN (('0001', 1), ('0003', 3));
+CREATE TABLE plt1_adv_m_p2 PARTITION OF plt1_adv_m FOR VALUES IN (('0004', 4), ('0006', 6));
+CREATE TABLE plt1_adv_m_p3 PARTITION OF plt1_adv_m FOR VALUES IN (('0008', 8), ('0009', 9));
+INSERT INTO plt1_adv_m SELECT i, i, to_char(i % 10, 'FM0000'), (i % 10) FROM generate_series(1, 299) i WHERE i % 10 IN (1, 3, 4, 6, 8, 9);
+ANALYZE plt1_adv_m;
+
+CREATE TABLE plt2_adv_m (a int, b int, c text, d int) PARTITION BY LIST (c, d);
+CREATE TABLE plt2_adv_m_p1 PARTITION OF plt2_adv_m FOR VALUES IN (('0002', 2), ('0003', 3));
+CREATE TABLE plt2_adv_m_p2 PARTITION OF plt2_adv_m FOR VALUES IN (('0004', 4), ('0006', 6));
+CREATE TABLE plt2_adv_m_p3 PARTITION OF plt2_adv_m FOR VALUES IN (('0007', 7), ('0009', 9));
+INSERT INTO plt2_adv_m SELECT i, i, to_char(i % 10, 'FM0000'), (i % 10) FROM generate_series(1, 299) i WHERE i % 10 IN (2, 3, 4, 6, 7, 9);
+ANALYZE plt2_adv_m;
+
+-- inner join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a;
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a;
+
+-- semi join
+EXPLAIN (COSTS OFF)
+SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a;
+SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a;
+
+-- left join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a;
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a;
+
+-- anti join
+EXPLAIN (COSTS OFF)
+SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a;
+SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a;
+
+-- full join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.a, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a;
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.a, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a;
+
+-- Test cases where one side has an extra partition
+CREATE TABLE plt2_adv_m_extra PARTITION OF plt2_adv_m FOR VALUES IN (('0000', 0));
+INSERT INTO plt2_adv_m_extra VALUES (0, 0, '0000', 0);
+ANALYZE plt2_adv_m;
+
+-- inner join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+
+-- semi join
+EXPLAIN (COSTS OFF)
+SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a;
+SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a;
+
+-- left join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+
+-- left join; currently we can't do partitioned join if there are no matched
+-- partitions on the nullable side
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt2_adv_m t1 LEFT JOIN plt1_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+
+-- anti join
+EXPLAIN (COSTS OFF)
+SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a;
+SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a;
+
+-- anti join; currently we can't do partitioned join if there are no matched
+-- partitions on the nullable side
+EXPLAIN (COSTS OFF)
+SELECT t1.* FROM plt2_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt1_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a;
+
+-- full join; currently we can't do partitioned join if there are no matched
+-- partitions on the nullable side
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a;
+
+DROP TABLE plt2_adv_m_extra;
+
+-- Test cases where a partition on one side matches multiple partitions on
+-- the other side; we currently can't do partitioned join in such cases
+ALTER TABLE plt2_adv_m DETACH PARTITION plt2_adv_m_p2;
+-- Split plt2_adv_p2 into two partitions so that plt1_adv_p2 matches both
+CREATE TABLE plt2_adv_m_p2_1 PARTITION OF plt2_adv_m FOR VALUES IN (('0004', 4));
+CREATE TABLE plt2_adv_m_p2_2 PARTITION OF plt2_adv_m FOR VALUES IN (('0006', 6));
+INSERT INTO plt2_adv_m SELECT i, i, to_char(i % 10, 'FM0000'), (i % 10) FROM generate_series(1, 299) i WHERE i % 10 IN (4, 6);
+ANALYZE plt2_adv_m;
+
+-- inner join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+
+-- semi join
+EXPLAIN (COSTS OFF)
+SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a;
+
+-- left join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+
+-- anti join
+EXPLAIN (COSTS OFF)
+SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a;
+
+-- full join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a;
+
+DROP TABLE plt2_adv_m_p2_1;
+DROP TABLE plt2_adv_m_p2_2;
+-- Restore plt2_adv_p2
+ALTER TABLE plt2_adv_m ATTACH PARTITION plt2_adv_m_p2 FOR VALUES IN (('0004', 4), ('0006', 6));
+
+
+-- Test NULL partitions
+ALTER TABLE plt1_adv_m DETACH PARTITION plt1_adv_m_p1;
+-- Change plt1_adv_p1 to the NULL partition
+CREATE TABLE plt1_adv_m_p1_null PARTITION OF plt1_adv_m FOR VALUES IN ((NULL, NULL), ('0001', 1), ('0003', 3));
+INSERT INTO plt1_adv_m SELECT i, i, to_char(i % 10, 'FM0000'), (i % 10) FROM generate_series(1, 299) i WHERE i % 10 IN (1, 3);
+INSERT INTO plt1_adv_m VALUES (-1, -1, NULL, NULL);
+ANALYZE plt1_adv_m;
+
+ALTER TABLE plt2_adv_m DETACH PARTITION plt2_adv_m_p3;
+-- Change plt2_adv_p3 to the NULL partition
+CREATE TABLE plt2_adv_m_p3_null PARTITION OF plt2_adv_m FOR VALUES IN ((NULL, NULL), ('0007', 7), ('0009', 9));
+INSERT INTO plt2_adv_m SELECT i, i, to_char(i % 10, 'FM0000'), (i % 10) FROM generate_series(1, 299) i WHERE i % 10 IN (7, 9);
+INSERT INTO plt2_adv_m VALUES (-1, -1, NULL, NULL);
+ANALYZE plt2_adv_m;
+
+-- inner join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+
+-- semi join
+EXPLAIN (COSTS OFF)
+SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a;
+SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a;
+
+-- left join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+
+-- anti join
+EXPLAIN (COSTS OFF)
+SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a;
+SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a;
+
+-- full join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a;
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a;
+
+DROP TABLE plt1_adv_m_p1_null;
+-- Restore plt1_adv_p1
+ALTER TABLE plt1_adv_m ATTACH PARTITION plt1_adv_m_p1 FOR VALUES IN (('0001', 1), ('0003', 3));
+
+-- Add to plt1_adv the extra NULL partition containing only NULL values as the
+-- key values
+CREATE TABLE plt1_adv_m_extra PARTITION OF plt1_adv_m FOR VALUES IN ((NULL, NULL));
+INSERT INTO plt1_adv_m VALUES (-1, -1, NULL, NULL);
+ANALYZE plt1_adv_m;
+
+DROP TABLE plt2_adv_m_p3_null;
+-- Restore plt2_adv_p3
+ALTER TABLE plt2_adv_m ATTACH PARTITION plt2_adv_m_p3 FOR VALUES IN (('0007', 7), ('0009', 9));
+ANALYZE plt2_adv_m;
+
+-- inner join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+
+-- left join; currently we can't do partitioned join if there are no matched
+-- partitions on the nullable side
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+
+-- full join; currently we can't do partitioned join if there are no matched
+-- partitions on the nullable side
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a;
+
+
+-- Add to plt2_adv the extra NULL partition containing only NULL values as the
+-- key values
+CREATE TABLE plt2_adv_m_extra PARTITION OF plt2_adv_m FOR VALUES IN ((NULL, NULL));
+INSERT INTO plt2_adv_m VALUES (-1, -1, NULL, NULL);
+ANALYZE plt2_adv_m;
+
+-- inner join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+
+-- left join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a;
+
+-- full join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a;
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a;
+
+-- 3-way join to test the NULL partition of a join relation
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d, t3.a, t3.c, t3.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) LEFT JOIN plt1_adv_m t3 ON (t1.a = t3.a AND t1.c = t3.c AND t1.d = t3.d) WHERE t1.b < 10 ORDER BY t1.a;
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d, t3.a, t3.c, t3.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) LEFT JOIN plt1_adv_m t3 ON (t1.a = t3.a AND t1.c = t3.c AND t1.d = t3.d) WHERE t1.b < 10 ORDER BY t1.a;
+
+DROP TABLE plt1_adv_m_extra;
+DROP TABLE plt2_adv_m_extra;
+
+-- Multiple NULL test
+CREATE TABLE plt1_adv_m_p4 PARTITION OF plt1_adv_m FOR VALUES IN (('0005', NULL));
+CREATE TABLE plt1_adv_m_p5 PARTITION OF plt1_adv_m FOR VALUES IN (('0010', NULL), (NULL, 10));
+INSERT INTO plt1_adv_m VALUES (-1, -1, '0005', NULL);
+INSERT INTO plt1_adv_m VALUES (-1, -1, '0010', NULL);
+INSERT INTO plt1_adv_m VALUES (-1, -1, NULL, 10);
+ANALYZE plt1_adv_m;
+
+CREATE TABLE plt2_adv_m_p4 PARTITION OF plt2_adv_m FOR VALUES IN ((NULL, 5));
+CREATE TABLE plt2_adv_m_p5 PARTITION OF plt2_adv_m FOR VALUES IN (('0010', NULL), (NULL, 10));
+INSERT INTO plt2_adv_m VALUES (-1, -1, '0005', NULL);
+INSERT INTO plt2_adv_m VALUES (-1, -1, '0010', NULL);
+INSERT INTO plt2_adv_m VALUES (-1, -1, NULL, 10);
+ANALYZE plt2_adv_m;
+
+-- inner join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a;
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a;
+
+-- semi join
+EXPLAIN (COSTS OFF)
+SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a;
+SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a;
+
+-- left join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a;
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a;
+
+-- anti join
+EXPLAIN (COSTS OFF)
+SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a;
+SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a;
+
+-- full join
+EXPLAIN (COSTS OFF)
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.a, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a;
+SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.a, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a;
+
 -- Tests for multi-level partitioned tables
 CREATE TABLE alpha (a double precision, b int, c text) PARTITION BY RANGE (a);
 CREATE TABLE alpha_neg PARTITION OF alpha FOR VALUES FROM ('-Infinity') TO (0) PARTITION BY RANGE (b);
-- 
1.8.3.1

