diff --git a/src/backend/executor/nodeAppend.c b/src/backend/executor/nodeAppend.c
index 7a3dd2e..401ed5d 100644
--- a/src/backend/executor/nodeAppend.c
+++ b/src/backend/executor/nodeAppend.c
@@ -60,6 +60,9 @@
 #include "executor/execdebug.h"
 #include "executor/nodeAppend.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "optimizer/partprune.h"
+#include "utils/memutils.h"
 
 /* Shared state for parallel-aware Append. */
 struct ParallelAppendState
@@ -76,12 +79,25 @@ struct ParallelAppendState
 	bool		pa_finished[FLEXIBLE_ARRAY_MEMBER];
 };
 
+struct PartitionPruneContextCache
+{
+	PartitionPruneContext *context;
+	PartitionPruneContextCache *subcache;
+};
+
 #define INVALID_SUBPLAN_INDEX		-1
 
 static TupleTableSlot *ExecAppend(PlanState *pstate);
 static bool choose_next_subplan_locally(AppendState *node);
 static bool choose_next_subplan_for_leader(AppendState *node);
 static bool choose_next_subplan_for_worker(AppendState *node);
+static void set_valid_runtime_subplans(AppendState *node);
+static void set_valid_runtime_subplans_recurse(AppendState *node,
+								   PartitionPruneInfo *pinfo,
+								   PartitionPruneContextCache *ctxcache,
+								   Bitmapset **validsubplans);
+static void mark_invalid_subplans_as_finished(AppendState *node);
+
 
 /* ----------------------------------------------------------------
  *		ExecInitAppend
@@ -127,6 +143,34 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
 	appendstate->ps.ExecProcNode = ExecAppend;
 	appendstate->appendplans = appendplanstates;
 	appendstate->as_nplans = nplans;
+	appendstate->as_valid_subplans = NULL;
+	appendstate->part_prune_params = NULL; /* determined later */
+	appendstate->part_prune_info = node->part_prune_info;
+	appendstate->contextcache = NULL; /* populate this as needed below */
+
+	if (node->part_prune_info)
+	{
+		/*
+		 * When run-time partition pruning is enabled we make calls to a query
+		 * planner function to determine which partitions will match.  The
+		 * planner is not too careful about freeing memory, so we'll ensure we
+		 * call the function in a temporary memory context to avoid any memory
+		 * leaking in the executor's memory context.
+		 */
+		appendstate->prune_context =
+			AllocSetContextCreate(CurrentMemoryContext,
+								  "Partition Prune",
+								  ALLOCSET_DEFAULT_SIZES);
+	}
+	else
+	{
+		/*
+		 * When run-time partition pruning is not enabled we can just mark
+		 * all subplans as valid.
+		 */
+		appendstate->as_valid_subplans = bms_add_range(NULL, 0, nplans - 1);
+		appendstate->prune_context = NULL;
+	}
 
 	/*
 	 * Initialize result tuple type and slot.
@@ -149,18 +193,14 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
 	/*
 	 * Miscellaneous initialization
 	 *
-	 * Append plans don't have expression contexts because they never call
-	 * ExecQual or ExecProject.
+	 * create expression context for node
 	 */
+	ExecAssignExprContext(estate, &appendstate->ps);
+
 	appendstate->ps.ps_ProjInfo = NULL;
 
-	/*
-	 * Parallel-aware append plans must choose the first subplan to execute by
-	 * looking at shared memory, but non-parallel-aware append plans can
-	 * always start with the first subplan.
-	 */
-	appendstate->as_whichplan =
-		appendstate->ps.plan->parallel_aware ? INVALID_SUBPLAN_INDEX : 0;
+	/* Let choose_next_subplan_* function handle setting the first subplan */
+	appendstate->as_whichplan = INVALID_SUBPLAN_INDEX;
 
 	/* If parallel-aware, this will be overridden later. */
 	appendstate->choose_next_subplan = choose_next_subplan_locally;
@@ -251,6 +291,18 @@ ExecReScanAppend(AppendState *node)
 {
 	int			i;
 
+	/*
+	 * If any of the parameters being used for partition pruning have changed,
+	 * then we'd better unset the valid subplans so that they can reselected
+	 * for the new parameter values.
+	 */
+	if (node->part_prune_info &&
+		bms_overlap(node->ps.chgParam, node->part_prune_params))
+	{
+		bms_free(node->as_valid_subplans);
+		node->as_valid_subplans = NULL;
+	}
+
 	for (i = 0; i < node->as_nplans; i++)
 	{
 		PlanState  *subnode = node->appendplans[i];
@@ -270,8 +322,8 @@ ExecReScanAppend(AppendState *node)
 			ExecReScan(subnode);
 	}
 
-	node->as_whichplan =
-		node->ps.plan->parallel_aware ? INVALID_SUBPLAN_INDEX : 0;
+	/* Let choose_next_subplan_* function handle setting the first subplan */
+	node->as_whichplan = INVALID_SUBPLAN_INDEX;
 }
 
 /* ----------------------------------------------------------------
@@ -360,22 +412,35 @@ static bool
 choose_next_subplan_locally(AppendState *node)
 {
 	int			whichplan = node->as_whichplan;
+	int			nextplan;
 
-	/* We should never see INVALID_SUBPLAN_INDEX in this case. */
-	Assert(whichplan >= 0 && whichplan <= node->as_nplans);
-
-	if (ScanDirectionIsForward(node->ps.state->es_direction))
+	/*
+	 * If first call then have the bms member function choose the first valid
+	 * subplan by initializing whichplan to -1.  If there happen to be no
+	 * valid subplans then the bms member function will handle that by
+	 * returning a negative number which will allow us to exit returning a
+	 * false value.
+	 */
+	if (whichplan == INVALID_SUBPLAN_INDEX)
 	{
-		if (whichplan >= node->as_nplans - 1)
-			return false;
-		node->as_whichplan++;
+		if (node->as_valid_subplans == NULL)
+			set_valid_runtime_subplans(node);
+
+		whichplan = -1;
 	}
+
+	/* Ensure whichplan is within the expected range */
+	Assert(whichplan >= -1 && whichplan <= node->as_nplans);
+
+	if (ScanDirectionIsForward(node->ps.state->es_direction))
+		nextplan = bms_next_member(node->as_valid_subplans, whichplan);
 	else
-	{
-		if (whichplan <= 0)
-			return false;
-		node->as_whichplan--;
-	}
+		nextplan = bms_prev_member(node->as_valid_subplans, whichplan);
+
+	if (nextplan < 0)
+		return false;
+
+	node->as_whichplan = nextplan;
 
 	return true;
 }
@@ -408,6 +473,17 @@ choose_next_subplan_for_leader(AppendState *node)
 	{
 		/* Start with last subplan. */
 		node->as_whichplan = node->as_nplans - 1;
+
+		/*
+		 * If we've yet to determine the valid subplans for these parameters
+		 * then do so now.  If run-time pruning is disabled then the valid
+		 * subplans will always be set to all subplans.
+		 */
+		if (node->as_valid_subplans == NULL)
+		{
+			set_valid_runtime_subplans(node);
+			mark_invalid_subplans_as_finished(node);
+		}
 	}
 
 	/* Loop until we find a subplan to execute. */
@@ -460,6 +536,17 @@ choose_next_subplan_for_worker(AppendState *node)
 	if (node->as_whichplan != INVALID_SUBPLAN_INDEX)
 		node->as_pstate->pa_finished[node->as_whichplan] = true;
 
+	/*
+	 * If we've yet to determine the valid subplans for these parameters then
+	 * do so now.  If run-time pruning is disabled then the valid subplans
+	 * will always be set to all subplans.
+	 */
+	else if (node->as_valid_subplans == NULL)
+	{
+		set_valid_runtime_subplans(node);
+		mark_invalid_subplans_as_finished(node);
+	}
+
 	/* If all the plans are already done, we have nothing to do */
 	if (pstate->pa_next_plan == INVALID_SUBPLAN_INDEX)
 	{
@@ -525,3 +612,210 @@ choose_next_subplan_for_worker(AppendState *node)
 
 	return true;
 }
+
+/*
+ * set_valid_runtime_subplans
+ *		Determine which subset of subplan nodes we need to scan based on
+ *		the details stored in node's 'part_prune_info'.  All subplans which
+ *		provably cannot possibly have matching records are eliminated and the
+ *		remainder are set in the AppendState's 'as_valid_subplans' variable.
+ */
+static void
+set_valid_runtime_subplans(AppendState *node)
+{
+	MemoryContext oldcontext;
+	Bitmapset *validsubplans = NULL;
+
+	/* Should never be called when already set */
+	Assert(node->as_valid_subplans == NULL);
+
+	if (!node->contextcache)
+		node->contextcache = palloc0(sizeof(PartitionPruneContextCache));
+
+	/*
+	 * Switch to a temp context to avoid leaking memory in the
+	 * executor's memory context.
+	 */
+	oldcontext = MemoryContextSwitchTo(node->prune_context);
+
+
+	set_valid_runtime_subplans_recurse(node, node->part_prune_info,
+									   node->contextcache,
+									   &validsubplans);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Move to the correct memory context */
+	node->as_valid_subplans = bms_copy(validsubplans);
+
+	MemoryContextReset(node->prune_context);
+}
+
+static void
+set_valid_runtime_subplans_recurse(AppendState *node,
+								   PartitionPruneInfo *pinfo,
+								   PartitionPruneContextCache *ctxcache,
+								   Bitmapset **validsubplans)
+{
+	PartitionPruneContext *context;
+	Bitmapset	   *partset;
+	int				i;
+
+	check_stack_depth();
+
+	/*
+	 * If the PartitionPruneContext has not yet been initialized for this rel
+	 * yet, then do that now.
+	 */
+	if (!ctxcache->context)
+	{
+		PartitionDesc partdesc;
+		Relation		rel;
+		PartitionKey	partkey;
+		ListCell	   *lc;
+		int				i;
+		MemoryContext oldContext;
+
+		oldContext = MemoryContextSwitchTo(node->ps.state->es_query_cxt);
+
+		ctxcache->context = context = palloc(sizeof(PartitionPruneContext));
+		ctxcache->subcache = palloc0(sizeof(PartitionPruneContextCache) *
+									 pinfo->nparts);
+
+		rel = relation_open(pinfo->parentoid, NoLock);
+
+		partkey = RelationGetPartitionKey(rel);
+		partdesc = RelationGetPartitionDesc(rel);
+
+		context->relid = pinfo->relid;
+		context->strategy = partkey->strategy;
+		context->partnatts = partkey->partnatts;
+		context->partkeys = palloc(sizeof(Expr *) * context->partnatts);
+
+		lc = list_head(partkey->partexprs);
+
+		for (i = 0; i < context->partnatts; i++)
+		{
+			AttrNumber	attno = partkey->partattrs[i];
+
+			if (attno != InvalidAttrNumber)
+			{
+				Assert(attno > 0);
+
+				context->partkeys[i] = (Expr *) makeVar(pinfo->relid,
+														attno,
+														partkey->parttypid[i],
+													partkey->parttypmod[i],
+													partkey->parttypcoll[i],
+														0);
+			}
+			else
+			{
+				if (lc == NULL)
+					elog(ERROR, "wrong number of partition key expressions");
+
+				context->partkeys[i] = (Expr *) lfirst(lc);
+				lc = lnext(lc);
+			}
+		}
+
+		context->parttypid = partkey->parttypid;
+		context->partopfamily = partkey->partopfamily;
+		context->partcollation = partkey->partcollation;
+		context->partsupfunc = partkey->partsupfunc;
+		context->nparts = pinfo->nparts;
+		context->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey);
+
+		if (OidIsValid(get_default_oid_from_partdesc(partdesc)))
+			context->has_default_part = true;
+		else
+			context->has_default_part = false;
+
+		context->partition_qual = RelationGetPartitionQual(rel);
+
+		context->prmlist = node->ps.state->es_param_list_info;
+		context->econtext = node->ps.ps_ExprContext;
+		context->paramids = NULL;
+
+		generate_partition_clauses(context, pinfo->prunequal);
+
+		node->part_prune_params = bms_add_members(node->part_prune_params,
+												  context->paramids);
+
+		relation_close(rel, NoLock);
+
+		MemoryContextSwitchTo(oldContext);
+	}
+	else
+		context = ctxcache->context;
+
+	/*
+	 * Detect if any impossibilities were discovered during
+	 * generate_partition_clauses
+	 */
+	if (context->clauseinfo->constfalse)
+	{
+		bms_free(*validsubplans);
+		*validsubplans = NULL;
+		return;
+	}
+
+	/* Determine which partition indexes we need to scan */
+	partset = get_partitions_from_clauses(context);
+
+	/* Translate partset into subnode indexes */
+	i = -1;
+	while ((i = bms_next_member(partset, i)) >= 0)
+	{
+		if (pinfo->subnodeindex[i] >= 0)
+			*validsubplans = bms_add_member(*validsubplans,
+											pinfo->subnodeindex[i]);
+		else if (pinfo->subpartindex[i] != NULL)
+			set_valid_runtime_subplans_recurse(node, pinfo->subpartindex[i],
+											   &ctxcache->subcache[i],
+											   validsubplans);
+		else
+		{
+			/*
+			 * If this happens then we're somehow missing an Append subnode.
+			 * This shouldn't happen and could only happen if a more
+			 * restrictive clause list was used for partition elimination
+			 * during planning than was used here.
+			 */
+			elog(ERROR, "partition missing from Append subplans");
+		}
+	}
+
+	bms_free(partset);
+}
+
+/*
+ * mark_invalid_subplans_as_finished
+ *		Marks the ParallelAppendState's pa_finished as true for each invalid
+ *		subplan.
+ *
+ * This function should only be called for parallel Append with run-time
+ * pruning enabled.
+ */
+static void
+mark_invalid_subplans_as_finished(AppendState *node)
+{
+	int i;
+
+	/* Only valid to call this while in parallel Append mode */
+	Assert(node->as_pstate);
+
+	/* Shouldn't have been called when run-time pruning is not enabled */
+	Assert(node->part_prune_info != NULL);
+
+	/* Nothing to do if all plans are valid */
+	if (bms_num_members(node->as_valid_subplans) == node->as_nplans)
+		return;
+
+	/* Mark all non-valid plans as finished */
+	for (i = 0; i < node->as_nplans; i++)
+	{
+		if (!bms_is_member(i, node->as_valid_subplans))
+			node->as_pstate->pa_finished[i] = true;
+	}
+}
diff --git a/src/backend/nodes/bitmapset.c b/src/backend/nodes/bitmapset.c
index edcd19a..3578c8f 100644
--- a/src/backend/nodes/bitmapset.c
+++ b/src/backend/nodes/bitmapset.c
@@ -58,6 +58,9 @@
  * rightmost_one_pos[x] gives the bit number (0-7) of the rightmost one bit
  * in a nonzero byte value x.  The entry for x=0 is never used.
  *
+ * leftmost_ons_pos[x] gives the bit number (0-7) of the leftmost one bit in a
+ * nonzero byte value x.  The entry for x=0 is never used.
+ *
  * number_of_ones[x] gives the number of one-bits (0-8) in a byte value x.
  *
  * We could make these tables larger and reduce the number of iterations
@@ -84,6 +87,25 @@ static const uint8 rightmost_one_pos[256] = {
 	4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0
 };
 
+static const uint8 leftmost_one_pos[256] = {
+	0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3,
+	4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+	5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+	5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+	6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+	6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+	6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+	6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+	7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+	7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+	7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+	7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+	7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+	7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+	7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+	7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7
+};
+
 static const uint8 number_of_ones[256] = {
 	0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,
 	1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
@@ -1089,6 +1111,79 @@ bms_next_member(const Bitmapset *a, int prevbit)
 }
 
 /*
+ * bms_prev_member - find prev member of a set
+ *
+ * Returns largest member less than "prevbit", or -2 if there is none.
+ * "prevbit" must NOT be more than one above the highest possible bit that can
+ * be set at the Bitmapset at its current size.
+ *
+ * To ease finding the highest set bit for the initial loop, the special
+ * prevbit value of -1 can be passed to have the function find the highest
+ * valued member in the set.
+ *
+ * This is intended as support for iterating through the members of a set in
+ * reverse.  The typical pattern is
+ *
+ *			x = -1;
+ *			while ((x = bms_prev_member(inputset, x)) >= 0)
+ *				process member x;
+ *
+ * Notice that when there are no more members, we return -2, not -1 as you
+ * might expect.  The rationale for that is to allow distinguishing the
+ * loop-not-started state (x == -1) from the loop-completed state (x == -2).
+ * It makes no difference in simple loop usage, but complex iteration logic
+ * might need such an ability.
+ */
+
+int
+bms_prev_member(const Bitmapset *a, int prevbit)
+{
+	int			wordnum;
+	int			ushiftbits;
+	bitmapword	mask;
+
+	/*
+	 * If set is NULL or if there are no more bits to the right then we've
+	 * nothing to do.
+	 */
+	if (a == NULL || prevbit == 0)
+		return -2;
+
+	/* transform -1 to the highest possible bit we could have set */
+	if (prevbit == -1)
+		prevbit = a->nwords * BITS_PER_BITMAPWORD - 1;
+	else
+		prevbit--;
+
+	ushiftbits = BITS_PER_BITMAPWORD - (BITNUM(prevbit) + 1);
+	mask = (~(bitmapword) 0) >> ushiftbits;
+	for (wordnum = WORDNUM(prevbit); wordnum >= 0; wordnum--)
+	{
+		bitmapword	w = a->words[wordnum];
+
+		/* mask out bits left of prevbit */
+		w &= mask;
+
+		if (w != 0)
+		{
+			int			result;
+			int			shift = 24;
+			result = wordnum * BITS_PER_BITMAPWORD;
+
+			while ((w >> shift) == 0)
+				shift -= 8;
+
+			result += shift + leftmost_one_pos[(w >> shift) & 255];
+			return result;
+		}
+
+		/* in subsequent words, consider all bits */
+		mask = (~(bitmapword) 0);
+	}
+	return -2;
+}
+
+/*
  * bms_hash_value - compute a hash key for a Bitmapset
  *
  * Note: we must ensure that any two bitmapsets that are bms_equal() will
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index d844b8b..1dc7651 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -244,6 +244,7 @@ _copyAppend(const Append *from)
 	COPY_NODE_FIELD(partitioned_rels);
 	COPY_NODE_FIELD(appendplans);
 	COPY_SCALAR_FIELD(first_partial_plan);
+	COPY_NODE_FIELD(part_prune_info);
 
 	return newnode;
 }
@@ -2151,6 +2152,33 @@ _copyPartitionClauseInfo(const PartitionClauseInfo *from)
 	return newnode;
 }
 
+static PartitionPruneInfo *
+_copyPartitionPruneInfo(const PartitionPruneInfo *from)
+{
+	PartitionPruneInfo *newnode = makeNode(PartitionPruneInfo);
+	int i;
+
+	COPY_SCALAR_FIELD(relid);
+	COPY_SCALAR_FIELD(parentoid);
+	COPY_NODE_FIELD(prunequal);
+	COPY_SCALAR_FIELD(nparts);
+	COPY_POINTER_FIELD(subnodeindex, from->nparts * sizeof(int));
+	COPY_POINTER_FIELD(subpartindex, from->nparts *
+					   sizeof(PartitionPruneInfo *));
+
+	/*
+	 * The above copied the entire array, but we still need to create copies
+	 * of each PartitionPruneInfo contained in that array.
+	 */
+	for (i = 0; i < from->nparts; i++)
+	{
+		if (newnode->subpartindex[i] != NULL)
+			COPY_NODE_FIELD(subpartindex[i]);
+	}
+
+	return newnode;
+}
+
 /* ****************************************************************
  *						relation.h copy functions
  *
@@ -5049,6 +5077,9 @@ copyObjectImpl(const void *from)
 		case T_PlaceHolderInfo:
 			retval = _copyPlaceHolderInfo(from);
 			break;
+		case T_PartitionPruneInfo:
+			retval = _copyPartitionPruneInfo(from);
+			break;
 
 			/*
 			 * VALUE NODES
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index d40429a..8a02887 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -1587,7 +1587,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
 	 * if we have zero or one live subpath due to constraint exclusion.)
 	 */
 	if (subpaths_valid)
-		add_path(rel, (Path *) create_append_path(rel, subpaths, NIL,
+		add_path(rel, (Path *) create_append_path(root, rel, subpaths, NIL,
 												  NULL, 0, false,
 												  partitioned_rels, -1));
 
@@ -1629,8 +1629,8 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
 		Assert(parallel_workers > 0);
 
 		/* Generate a partial append path. */
-		appendpath = create_append_path(rel, NIL, partial_subpaths, NULL,
-										parallel_workers,
+		appendpath = create_append_path(root, rel, NIL, partial_subpaths,
+										NULL, parallel_workers,
 										enable_parallel_append,
 										partitioned_rels, -1);
 
@@ -1678,7 +1678,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
 							   max_parallel_workers_per_gather);
 		Assert(parallel_workers > 0);
 
-		appendpath = create_append_path(rel, pa_nonpartial_subpaths,
+		appendpath = create_append_path(root, rel, pa_nonpartial_subpaths,
 										pa_partial_subpaths,
 										NULL, parallel_workers, true,
 										partitioned_rels, partial_rows);
@@ -1734,7 +1734,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
 
 		if (subpaths_valid)
 			add_path(rel, (Path *)
-					 create_append_path(rel, subpaths, NIL,
+					 create_append_path(root, rel, subpaths, NIL,
 										required_outer, 0, false,
 										partitioned_rels, -1));
 	}
@@ -2000,7 +2000,7 @@ set_dummy_rel_pathlist(RelOptInfo *rel)
 	rel->pathlist = NIL;
 	rel->partial_pathlist = NIL;
 
-	add_path(rel, (Path *) create_append_path(rel, NIL, NIL, NULL,
+	add_path(rel, (Path *) create_append_path(NULL, rel, NIL, NIL, NULL,
 											  0, false, NIL, -1));
 
 	/*
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 3f1c1b3..2e289d4 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -1230,7 +1230,7 @@ mark_dummy_rel(RelOptInfo *rel)
 	rel->partial_pathlist = NIL;
 
 	/* Set up the dummy path */
-	add_path(rel, (Path *) create_append_path(rel, NIL, NIL, NULL,
+	add_path(rel, (Path *) create_append_path(NULL, rel, NIL, NIL, NULL,
 											  0, false, NIL, -1));
 
 	/* Set or update cheapest_total_path and related fields */
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index da0cc7f..d82fee7 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -29,6 +29,7 @@
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/partprune.h"
 #include "optimizer/paths.h"
 #include "optimizer/placeholder.h"
 #include "optimizer/plancat.h"
@@ -204,7 +205,8 @@ static NamedTuplestoreScan *make_namedtuplestorescan(List *qptlist, List *qpqual
 static WorkTableScan *make_worktablescan(List *qptlist, List *qpqual,
 				   Index scanrelid, int wtParam);
 static Append *make_append(List *appendplans, int first_partial_plan,
-			List *tlist, List *partitioned_rels);
+			List *tlist, List *partitioned_rels,
+			PartitionPruneInfo *partpruneinfo);
 static RecursiveUnion *make_recursive_union(List *tlist,
 					 Plan *lefttree,
 					 Plan *righttree,
@@ -1022,6 +1024,8 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path)
 	List	   *tlist = build_path_tlist(root, &best_path->path);
 	List	   *subplans = NIL;
 	ListCell   *subpaths;
+	RelOptInfo *rel = best_path->path.parent;
+	PartitionPruneInfo *pinfo = NULL;
 
 	/*
 	 * The subpaths list could be empty, if every child was proven empty by
@@ -1059,6 +1063,41 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path)
 		subplans = lappend(subplans, subplan);
 	}
 
+
+	if (best_path->trypartitionprune)
+	{
+		List	   *prunequal;
+
+		/* Not for join rels */
+		Assert(bms_membership(rel->relids) == BMS_SINGLETON);
+
+		prunequal =
+			extract_actual_clauses(best_path->path.parent->baserestrictinfo,
+								   false);
+
+		if (best_path->path.param_info)
+		{
+
+			List	   *prmquals = best_path->path.param_info->ppi_clauses;
+
+			prmquals = extract_actual_clauses(prmquals, false);
+			prmquals = (List *) replace_nestloop_params(root,
+														(Node *) prmquals);
+
+			prunequal = list_concat(prunequal, prmquals);
+		}
+
+		/*
+		 * If any quals exist that could possibly be useful to use for
+		 * performing further partition pruning during execution, then
+		 * we'll generate a PartitionPruneInfo to store these quals and
+		 * allow translation of partition indexes into subpath indexes.
+		 */
+		if (prunequal != NIL)
+			pinfo = make_partition_pruneinfo(root, best_path->path.parent,
+											 best_path->partitioned_rels,
+											 best_path->subpaths, prunequal);
+	}
 	/*
 	 * XXX ideally, if there's just one child, we'd not bother to generate an
 	 * Append node but just return the single child.  At the moment this does
@@ -1067,7 +1106,8 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path)
 	 */
 
 	plan = make_append(subplans, best_path->first_partial_path,
-					   tlist, best_path->partitioned_rels);
+					   tlist, best_path->partitioned_rels,
+					   pinfo);
 
 	copy_generic_path_info(&plan->plan, (Path *) best_path);
 
@@ -5320,7 +5360,8 @@ make_foreignscan(List *qptlist,
 
 static Append *
 make_append(List *appendplans, int first_partial_plan,
-			List *tlist, List *partitioned_rels)
+			List *tlist, List *partitioned_rels,
+			PartitionPruneInfo *partpruneinfo)
 {
 	Append	   *node = makeNode(Append);
 	Plan	   *plan = &node->plan;
@@ -5332,7 +5373,7 @@ make_append(List *appendplans, int first_partial_plan,
 	node->partitioned_rels = partitioned_rels;
 	node->appendplans = appendplans;
 	node->first_partial_plan = first_partial_plan;
-
+	node->part_prune_info = partpruneinfo;
 	return node;
 }
 
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 646d118..f720e8d 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -3717,7 +3717,8 @@ create_grouping_paths(PlannerInfo *root,
 				paths = lappend(paths, path);
 			}
 			path = (Path *)
-				create_append_path(grouped_rel,
+				create_append_path(root,
+								   grouped_rel,
 								   paths,
 								   NIL,
 								   NULL,
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index f01119e..146e202 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -593,7 +593,7 @@ generate_union_path(SetOperationStmt *op, PlannerInfo *root,
 	/*
 	 * Append the child results together.
 	 */
-	path = (Path *) create_append_path(result_rel, pathlist, NIL,
+	path = (Path *) create_append_path(root, result_rel, pathlist, NIL,
 									   NULL, 0, false, NIL, -1);
 	/* We have to manually jam the right tlist into the path; ick */
 	path->pathtarget = create_pathtarget(root, tlist);
@@ -705,7 +705,7 @@ generate_nonunion_path(SetOperationStmt *op, PlannerInfo *root,
 	/*
 	 * Append the child results together.
 	 */
-	path = (Path *) create_append_path(result_rel, pathlist, NIL,
+	path = (Path *) create_append_path(root, result_rel, pathlist, NIL,
 									   NULL, 0, false, NIL, -1);
 
 	/* We have to manually jam the right tlist into the path; ick */
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 0c1f239..dccbcc1 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2473,6 +2473,25 @@ eval_const_expressions(PlannerInfo *root, Node *node)
 }
 
 /*--------------------
+ * eval_const_expressions_from_list
+ *
+ * This is similar to eval_const_expression except that it takes ParamListInfo
+ * argument instead of PlannerInfo to create the context.
+ */
+Node *
+eval_const_expressions_from_list(ParamListInfo prmlist, Node *node)
+{
+	eval_const_expressions_context context;
+
+	context.boundParams = prmlist;	/* bound Params */
+	context.root = NULL;
+	context.active_fns = NIL;	/* nothing being recursively simplified */
+	context.case_val = NULL;	/* no CASE being examined */
+	context.estimate = false;	/* safe transformations only */
+	return eval_const_expressions_mutator(node, &context);
+}
+
+/*--------------------
  * estimate_expression_value
  *
  * This function attempts to estimate the value of an expression for
diff --git a/src/backend/optimizer/util/partprune.c b/src/backend/optimizer/util/partprune.c
index 71a7b7b..683dd5d 100644
--- a/src/backend/optimizer/util/partprune.c
+++ b/src/backend/optimizer/util/partprune.c
@@ -56,12 +56,16 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_type.h"
+#include "executor/nodeSubplan.h"
+#include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/partprune.h"
+#include "optimizer/pathnode.h"
 #include "optimizer/planner.h"
 #include "optimizer/predtest.h"
+#include "optimizer/prep.h"
 #include "parser/parse_coerce.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteManip.h"
@@ -101,14 +105,15 @@ static Bitmapset *get_partitions_from_or_args(PartitionPruneContext *context,
 								   List *or_args);
 static void remove_redundant_clauses(PartitionPruneContext *context,
 						 List **minimalclauses);
-static bool partition_cmp_args(Oid parttypid, Oid partopfamily,
-				   PartClause *pc, PartClause *leftarg, PartClause *rightarg,
-				   bool *result);
+static bool partition_cmp_args(PartitionPruneContext *context, Oid parttypid,
+				   Oid partopfamily, PartClause *pc, PartClause *leftarg,
+				   PartClause *rightarg, bool *result);
 static bool extract_bounding_datums(PartitionPruneContext *context,
 						List **minimalclauses, PartScanKeyInfo *keys);
 static PartOpStrategy partition_op_strategy(char part_strategy,
 					PartClause *pc, bool *incl);
-static bool partkey_datum_from_expr(Oid parttypid, Expr *expr, Datum *value);
+static bool partkey_datum_from_expr(PartitionPruneContext *context, Oid parttypid,
+						Expr *expr, Datum *value);
 
 /*
  * prune_append_rel_partitions
@@ -154,6 +159,9 @@ prune_append_rel_partitions(PlannerInfo *root, RelOptInfo *rel)
 		context.boundinfo = rel->boundinfo;
 		context.has_default_part = rel->has_default_part;
 		context.partition_qual = rel->partition_qual;
+		context.prmlist = NULL;
+		context.econtext = NULL;
+		context.paramids = NULL;
 
 		/* process clauses; context.clauseinfo will be set */
 		generate_partition_clauses(&context, clauses);
@@ -487,6 +495,10 @@ extract_partition_clauses(PartitionPruneContext *context, List *clauses)
 				pc->inputcollid = opclause->inputcollid;
 				pc->value = valueexpr;
 
+				if (IsA(valueexpr, Param))
+					context->paramids = bms_add_member(context->paramids,
+											((Param *) valueexpr)->paramid);
+
 				/*
 				 * We don't turn a <> operator clause into a key right away.
 				 * Instead, the caller will hand over such clauses to
@@ -641,6 +653,11 @@ extract_partition_clauses(PartitionPruneContext *context, List *clauses)
 														 leftop, rightop,
 														 InvalidOid,
 														 saop_coll);
+
+					if (IsA(rightop, Param))
+						context->paramids = bms_add_member(context->paramids,
+												((Param *) rightop)->paramid);
+
 					elem_clauses = lappend(elem_clauses, elem_clause);
 				}
 
@@ -891,7 +908,8 @@ remove_redundant_clauses(PartitionPruneContext *context,
 				if (hash_clause == NULL)
 					hash_clause = pc;
 				/* check if another clause would contradict the one we have */
-				else if (partition_cmp_args(context->parttypid[i],
+				else if (partition_cmp_args(context,
+											context->parttypid[i],
 											context->partopfamily[i],
 											pc, pc, hash_clause,
 											&test_result))
@@ -948,7 +966,8 @@ remove_redundant_clauses(PartitionPruneContext *context,
 				 * then because 7 < 5 is false, we leave a < 5 where it is and
 				 * effectively discard a < 7 as being redundant.
 				 */
-				if (partition_cmp_args(context->parttypid[i],
+				if (partition_cmp_args(context,
+									   context->parttypid[i],
 									   context->partopfamily[i],
 									   pc, pc, btree_clauses[s],
 									   &test_result))
@@ -1005,7 +1024,8 @@ remove_redundant_clauses(PartitionPruneContext *context,
 				 * eq clause is a = 3, then because 3 < 5, we no longer need
 				 * a < 5, because a = 3 is more restrictive.
 				 */
-				if (partition_cmp_args(context->parttypid[i],
+				if (partition_cmp_args(context,
+									   context->parttypid[i],
 									   context->partopfamily[i],
 									   chk, eq, chk,
 									   &test_result))
@@ -1036,7 +1056,8 @@ remove_redundant_clauses(PartitionPruneContext *context,
 			PartClause *lt = btree_clauses[BTLessStrategyNumber - 1],
 					   *le = btree_clauses[BTLessEqualStrategyNumber - 1];
 
-			if (partition_cmp_args(context->parttypid[i],
+			if (partition_cmp_args(context,
+								   context->parttypid[i],
 								   context->partopfamily[i],
 								   le, lt, le,
 								   &test_result))
@@ -1055,7 +1076,8 @@ remove_redundant_clauses(PartitionPruneContext *context,
 			PartClause *gt = btree_clauses[BTGreaterStrategyNumber - 1],
 					   *ge = btree_clauses[BTGreaterEqualStrategyNumber - 1];
 
-			if (partition_cmp_args(context->parttypid[i],
+			if (partition_cmp_args(context,
+								   context->parttypid[i],
 								   context->partopfamily[i],
 								   ge, gt, ge,
 								   &test_result))
@@ -1093,9 +1115,9 @@ remove_redundant_clauses(PartitionPruneContext *context,
  * incompatible with the operator.
  */
 static bool
-partition_cmp_args(Oid parttypid, Oid partopfamily,
-				   PartClause *pc, PartClause *leftarg, PartClause *rightarg,
-				   bool *result)
+partition_cmp_args(PartitionPruneContext *context, Oid parttypid,
+				   Oid partopfamily, PartClause *pc, PartClause *leftarg,
+				   PartClause *rightarg, bool *result)
 {
 	Datum	left_value;
 	Datum	right_value;
@@ -1106,10 +1128,12 @@ partition_cmp_args(Oid parttypid, Oid partopfamily,
 	 * Try to extract an actual value from each arg.  This may fail if the
 	 * value is unknown in this context, in which case we cannot compare.
 	 */
-	if (!partkey_datum_from_expr(parttypid, leftarg->value, &left_value))
+	if (!partkey_datum_from_expr(context, parttypid, leftarg->value,
+		&left_value))
 		return false;
 
-	if (!partkey_datum_from_expr(parttypid, rightarg->value, &right_value))
+	if (!partkey_datum_from_expr(context, parttypid, rightarg->value,
+		&right_value))
 		return false;
 
 	/*
@@ -1232,12 +1256,16 @@ extract_bounding_datums(PartitionPruneContext *context,
 				case PART_OP_EQUAL:
 					Assert(incl);
 					if (need_next_eq &&
-						partkey_datum_from_expr(context->parttypid[i], value,
+						partkey_datum_from_expr(context,
+												context->parttypid[i],
+												value,
 												&keys->eqkeys[i]))
 						keys->n_eqkeys++;
 
 					if (need_next_max &&
-						partkey_datum_from_expr(context->parttypid[i], value,
+						partkey_datum_from_expr(context,
+												context->parttypid[i],
+												value,
 												&keys->maxkeys[i]))
 					{
 						keys->n_maxkeys++;
@@ -1245,7 +1273,9 @@ extract_bounding_datums(PartitionPruneContext *context,
 					}
 
 					if (need_next_min &&
-						partkey_datum_from_expr(context->parttypid[i], value,
+						partkey_datum_from_expr(context,
+												context->parttypid[i],
+												value,
 												&keys->minkeys[i]))
 					{
 						keys->n_minkeys++;
@@ -1255,7 +1285,9 @@ extract_bounding_datums(PartitionPruneContext *context,
 
 				case PART_OP_LESS:
 					if (need_next_max &&
-						partkey_datum_from_expr(context->parttypid[i], value,
+						partkey_datum_from_expr(context,
+												context->parttypid[i],
+												value,
 												&keys->maxkeys[i]))
 					{
 						keys->n_maxkeys++;
@@ -1267,7 +1299,9 @@ extract_bounding_datums(PartitionPruneContext *context,
 
 				case PART_OP_GREATER:
 					if (need_next_min &&
-						partkey_datum_from_expr(context->parttypid[i], value,
+						partkey_datum_from_expr(context,
+												context->parttypid[i],
+												value,
 												&keys->minkeys[i]))
 					{
 						keys->n_minkeys++;
@@ -1314,8 +1348,8 @@ extract_bounding_datums(PartitionPruneContext *context,
 			PartClause *pc = (PartClause *) lfirst(lc);
 			Datum	datum;
 
-			if (partkey_datum_from_expr(context->parttypid[0], pc->value,
-										&datum))
+			if (partkey_datum_from_expr(context, context->parttypid[0],
+										pc->value, &datum))
 				keys->ne_datums[i++] = datum;
 		}
 		keys->n_ne_datums = i;
@@ -1391,7 +1425,8 @@ partition_op_strategy(char part_strategy, PartClause *pc, bool *incl)
  * set.  True is returned otherwise.
  */
 static bool
-partkey_datum_from_expr(Oid parttypid, Expr *expr, Datum *value)
+partkey_datum_from_expr(PartitionPruneContext *context, Oid parttypid,
+						Expr *expr, Datum *value)
 {
 	Oid		exprtype = exprType((Node *) expr);
 
@@ -1429,11 +1464,177 @@ partkey_datum_from_expr(Oid parttypid, Expr *expr, Datum *value)
 	 * Add more expression types here as needed to support the requirements
 	 * of the higher-level code.
 	 */
-	if (IsA(expr, Const))
+	switch (nodeTag(expr))
 	{
-		*value = ((Const *) expr)->constvalue;
-		return true;
+		case T_Const:
+			*value = ((Const *) expr)->constvalue;
+			return true;
+
+		case T_Param:
+			switch (((Param *) expr)->paramkind)
+			{
+				case PARAM_EXTERN:
+					if (context->prmlist)
+					{
+						Node	   *node;
+						Param	   *param = (Param *) expr;
+						ParamListInfo prmlist = context->prmlist;
+
+						node = eval_const_expressions_from_list(prmlist,
+																(Node *) param);
+						if (IsA(node, Const))
+						{
+							*value = ((Const *) node)->constvalue;
+							return true;
+						}
+					}
+
+				case PARAM_EXEC:
+					if (context->econtext)
+					{
+						Param	   *param = (Param *) expr;
+						ParamExecData *prm;
+						ExprContext *econtext = context->econtext;
+
+						prm = &(econtext->ecxt_param_exec_vals[param->paramid]);
+						if (unlikely(prm->execPlan != NULL))
+						{
+							ExecSetParamPlan((SubPlanState *) prm->execPlan,
+											 econtext);
+							Assert(prm->execPlan == NULL);
+						}
+						*value = prm->value;
+						return true;
+					}
+			}
 	}
 
 	return false;
 }
+
+/*
+ * make_partition_pruneinfo
+ *		Build PartitionPruneInfo tree to allow the output of
+ *		get_partitions_from_clauses to be translated into
+ *		'subpaths' indexes.  This is required in order to allow
+ *		us to perform any further partition pruning during execution.
+ */
+PartitionPruneInfo *
+make_partition_pruneinfo(PlannerInfo *root, RelOptInfo *rel,
+						 List *partition_rels, List *subpaths,
+						 List *prunequal)
+{
+	PartitionPruneInfo *pinfo;
+	AppendRelInfo	   *appinfo;
+	RangeTblEntry	   *rte;
+	ListCell		   *lc;
+	int					i;
+	int					partidx;
+	int					nparts = rel->nparts;
+
+	check_stack_depth();
+
+	rte = root->simple_rte_array[rel->relid];
+
+	pinfo = makeNode(PartitionPruneInfo);
+	pinfo->relid = rel->relid;
+	pinfo->parentoid = rte->relid;
+	pinfo->prunequal = prunequal;
+	pinfo->nparts = nparts;
+	pinfo->subnodeindex = (int *) palloc(sizeof(int) * nparts);
+	pinfo->subpartindex = (PartitionPruneInfo **)
+						palloc0(sizeof(PartitionPruneInfo *) * nparts);
+	/*
+	 * -1 represents a partition that has been pruned.  Set them all to this
+	 * initially.  We'll determine the subpath index for the non-pruned
+	 * ones below.
+	 */
+	for (i = 0; i < nparts; i++)
+		pinfo->subnodeindex[i] = -1;
+
+	i = -1;
+	foreach(lc, subpaths)
+	{
+		Path *path = (Path *) lfirst(lc);
+
+		i++; /* track subnode index */
+
+		/* Find the AppendRelInfo for the Append child */
+		appinfo = find_childrel_appendrelinfo(root, path->parent);
+
+		/*
+		 * Skip subpaths which belong to relations not directly parented by
+		 * rel.  We'll process any we skip here below when looping through
+		 * partition_rels
+		 */
+		if (appinfo->parent_relid != rel->relid)
+			continue;
+
+		/* Determine the element in part_rel which belongs to this subpath. */
+		for (partidx = 0; partidx < nparts; partidx++)
+		{
+			if (rel->part_rels[partidx]->relid != appinfo->child_relid)
+				continue;
+
+			/* found it!  Save the subnode index */
+			pinfo->subnodeindex[partidx] = i;
+			break;
+		}
+	}
+
+	/*
+	 * Some of the relations returned by get_partitions_from_clauses may be
+	 * other partitioned tables.  Unlike the case above, these won't be
+	 * subpaths of the Append.  To handle these we must create a
+	 * sub-PartitionPruneInfo to allow us to determine if subnodes which
+	 * belong to sub-partitioned tables are required during partition pruning.
+	 */
+	foreach(lc, partition_rels)
+	{
+		Index rti = lfirst_int(lc);
+		RelOptInfo *subpart = find_base_rel(root, rti);
+
+		/*
+		 * partition_rels contains the rti of the base relation being queried.
+		 * We only care about sub-partition parents here, so skip this.
+		 */
+		if (subpart->reloptkind == RELOPT_BASEREL)
+			continue;
+
+		appinfo = find_childrel_appendrelinfo(root, subpart);
+
+		/*
+		 * We only want to deal with sub-partition parents that are directly
+		 * below rel.  We'll deal with any we skip here later in a recursive
+		 * call which is made below.
+		 */
+		if (appinfo->parent_relid != rel->relid)
+			continue;
+
+		/*
+		 * Handle sub-partition parents by building a sub-PartitionPruneInfo.
+		 */
+		for (partidx = 0; partidx < nparts; partidx++)
+		{
+			List *subprunequal;
+
+			if (rel->part_rels[partidx]->relid != appinfo->child_relid)
+				continue;
+
+			/* Adjust the prune qual to be compatible with this subpartition */
+			subprunequal = (List *) adjust_appendrel_attrs(root,
+														(Node *) prunequal,
+														1,
+														&appinfo);
+
+			pinfo->subpartindex[partidx] = make_partition_pruneinfo(root,
+																	subpart,
+															partition_rels,
+																	subpaths,
+																	subprunequal);
+			break;
+		}
+	}
+
+	return pinfo;
+}
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index fe3b458..f3ab0d9 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1210,7 +1210,8 @@ create_tidscan_path(PlannerInfo *root, RelOptInfo *rel, List *tidquals,
  * Note that we must handle subpaths = NIL, representing a dummy access path.
  */
 AppendPath *
-create_append_path(RelOptInfo *rel,
+create_append_path(PlannerInfo *root,
+				   RelOptInfo *rel,
 				   List *subpaths, List *partial_subpaths,
 				   Relids required_outer,
 				   int parallel_workers, bool parallel_aware,
@@ -1224,8 +1225,36 @@ create_append_path(RelOptInfo *rel,
 	pathnode->path.pathtype = T_Append;
 	pathnode->path.parent = rel;
 	pathnode->path.pathtarget = rel->reltarget;
-	pathnode->path.param_info = get_appendrel_parampathinfo(rel,
-															required_outer);
+	pathnode->trypartitionprune = false;
+
+	/*
+	 * When generating an Append path for a partitioned table we'll try to
+	 * enable additional partition pruning at run-time. Useful pruning quals
+	 * may be in parameterized path quals, so we'll go all the way and
+	 * generate the qual list for the Append's parameterized paths. We need
+	 * only bother trying this for RELOPT_BASEREL rels, as
+	 * RELOPT_OTHER_MEMBER_REL's Append paths are merged into the base rel's
+	 * Append subpaths.
+	 */
+	if (rel->reloptkind == RELOPT_BASEREL && root)
+	{
+		RangeTblEntry *rte = planner_rt_fetch(rel->relid, root);
+
+		if (rte->rtekind == RTE_RELATION &&
+			rte->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			pathnode->path.param_info = get_baserel_parampathinfo(root,
+																  rel,
+																  required_outer);
+			pathnode->trypartitionprune = true;
+		}
+		else
+			pathnode->path.param_info = get_appendrel_parampathinfo(rel,
+																	required_outer);
+	}
+	else
+		pathnode->path.param_info = get_appendrel_parampathinfo(rel,
+																required_outer);
 	pathnode->path.parallel_aware = parallel_aware;
 	pathnode->path.parallel_safe = rel->consider_parallel;
 	pathnode->path.parallel_workers = parallel_workers;
@@ -3567,7 +3596,7 @@ reparameterize_path(PlannerInfo *root, Path *path,
 					i++;
 				}
 				return (Path *)
-					create_append_path(rel, childpaths, partialpaths,
+					create_append_path(root, rel, childpaths, partialpaths,
 									   required_outer,
 									   apath->path.parallel_workers,
 									   apath->path.parallel_aware,
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 0dd6bd3..27d70fc 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -16,6 +16,7 @@
 #include "fmgr.h"
 #include "executor/tuptable.h"
 #include "nodes/execnodes.h"
+#include "nodes/relation.h"
 #include "parser/parse_node.h"
 #include "utils/rel.h"
 
@@ -70,6 +71,11 @@ typedef struct PartitionPruneContext
 
 	/* Information about matched clauses */
 	PartitionClauseInfo *clauseinfo;
+	ParamListInfo prmlist;
+	ExprContext *econtext;
+
+	/* ParamIds of clauses being used to determine partitions */
+	Bitmapset *paramids;
 } PartitionPruneContext;
 
 /*
diff --git a/src/include/nodes/bitmapset.h b/src/include/nodes/bitmapset.h
index 67e8920..b6f1a9e 100644
--- a/src/include/nodes/bitmapset.h
+++ b/src/include/nodes/bitmapset.h
@@ -99,6 +99,7 @@ extern Bitmapset *bms_join(Bitmapset *a, Bitmapset *b);
 /* support for iterating through the integer elements of a set: */
 extern int	bms_first_member(Bitmapset *a);
 extern int	bms_next_member(const Bitmapset *a, int prevbit);
+extern int	bms_prev_member(const Bitmapset *a, int prevbit);
 
 /* support for hashtables using Bitmapsets as keys: */
 extern uint32 bms_hash_value(const Bitmapset *a);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index a953820..7db3a79 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1010,11 +1010,14 @@ typedef struct ModifyTableState
  *
  *		nplans			how many plans are in the array
  *		whichplan		which plan is being executed (0 .. n-1)
+ *		valid_subplans	for runtime pruning, valid appendplans indexes to scan
  * ----------------
  */
 
 struct AppendState;
 typedef struct AppendState AppendState;
+struct PartitionPruneContextCache;
+typedef struct PartitionPruneContextCache PartitionPruneContextCache;
 struct ParallelAppendState;
 typedef struct ParallelAppendState ParallelAppendState;
 
@@ -1026,6 +1029,11 @@ struct AppendState
 	int			as_whichplan;
 	ParallelAppendState *as_pstate; /* parallel coordination info */
 	Size		pstate_len;		/* size of parallel coordination info */
+	Bitmapset  *as_valid_subplans; /* mask of non-pruned subplans */
+	Bitmapset  *part_prune_params; /* ParamIds useful for partition pruning */
+	PartitionPruneInfo *part_prune_info; /* details for partition pruning */
+	PartitionPruneContextCache *contextcache; /* cache of prune contexts */
+	MemoryContext prune_context; /* used when calling planner pruning code */
 	bool		(*choose_next_subplan) (AppendState *);
 };
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 81d223c..1ca7d64 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -192,6 +192,7 @@ typedef enum NodeTag
 	T_OnConflictExpr,
 	T_PartitionClauseInfo,
 	T_IntoClause,
+	T_PartitionPruneInfo,
 
 	/*
 	 * TAGS FOR EXPRESSION STATE NODES (execnodes.h)
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index f2e19ea..a3aeb95 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -250,6 +250,13 @@ typedef struct Append
 	List	   *partitioned_rels;
 	List	   *appendplans;
 	int			first_partial_plan;
+
+	/*
+	 * Mapping details for run-time subplan pruning. This allows translation
+	 * from partition numbers into subplan indexes. This is set to NULL when
+	 * run-time subplan pruning is disabled.
+	 */
+	PartitionPruneInfo *part_prune_info;
 } Append;
 
 /* ----------------
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 6cfb876..9f538d2 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1546,4 +1546,26 @@ typedef struct PartitionClauseInfo
 	bool	constfalse;
 } PartitionClauseInfo;
 
+/*----------
+ * PartitionPruneInfo - Allows pruning of Append subplans
+ *
+ * Here we store mapping details to allow translation of a partitioned table's
+ * id number into an Append node's subplan index.  This structure is used
+ * to recursively search for all subplan nodes when there are sub-partitioned
+ * tables in the Append plan.
+ *----------
+ */
+typedef struct PartitionPruneInfo
+{
+	NodeTag		type;
+	int			relid;		/* relation index of parent partition rel */
+	Oid			parentoid;	/* Oid of parent partition rel */
+	List	   *prunequal;	/* qual list for pruning partitions */
+	int			nparts;		/* length of the following arrays */
+	int		   *subnodeindex;	/* subnode index indexed by partition id */
+
+	/* sub-PartitionPruneInfo indexed by partition id */
+	struct PartitionPruneInfo **subpartindex;
+} PartitionPruneInfo;
+
 #endif							/* PRIMNODES_H */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 5579940..4d17f9a 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -1294,6 +1294,8 @@ typedef struct AppendPath
 
 	/* Index of first partial path in subpaths */
 	int			first_partial_path;
+
+	bool		trypartitionprune; /* Attempt to enable partition pruning? */
 } AppendPath;
 
 #define IS_DUMMY_PATH(p) \
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 3c2f549..bedffc4 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -80,6 +80,9 @@ extern void CommuteRowCompareExpr(RowCompareExpr *clause);
 
 extern Node *eval_const_expressions(PlannerInfo *root, Node *node);
 
+extern Node *eval_const_expressions_from_list(ParamListInfo prmlist,
+								 Node *node);
+
 extern Node *estimate_expression_value(PlannerInfo *root, Node *node);
 
 extern Query *inline_set_returning_function(PlannerInfo *root,
diff --git a/src/include/optimizer/partprune.h b/src/include/optimizer/partprune.h
index 5c0d469..57701e4 100644
--- a/src/include/optimizer/partprune.h
+++ b/src/include/optimizer/partprune.h
@@ -22,4 +22,9 @@ extern void generate_partition_clauses(PartitionPruneContext *context,
 							List *clauses);
 extern Bitmapset *get_partitions_from_clauses(PartitionPruneContext *context);
 
+extern PartitionPruneInfo *make_partition_pruneinfo(PlannerInfo *root,
+						 RelOptInfo *rel,
+						 List *partition_rels, List *subpaths,
+						 List *prunequal);
+
 #endif							/* PARTPRUNE_H */
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index ef7173f..bde1858 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -64,7 +64,7 @@ extern BitmapOrPath *create_bitmap_or_path(PlannerInfo *root,
 					  List *bitmapquals);
 extern TidPath *create_tidscan_path(PlannerInfo *root, RelOptInfo *rel,
 					List *tidquals, Relids required_outer);
-extern AppendPath *create_append_path(RelOptInfo *rel,
+extern AppendPath *create_append_path(PlannerInfo *root, RelOptInfo *rel,
 				   List *subpaths, List *partial_subpaths,
 				   Relids required_outer,
 				   int parallel_workers, bool parallel_aware,
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
index bc9ff38..478820f 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -1419,3 +1419,959 @@ explain (costs off) select * from rlp where a = 15 and b <> 'ab' and b <> 'cd' a
 (5 rows)
 
 drop table lp, coll_pruning, rlp, mc3p, mc2p, boolpart, hp, rp;
+--
+-- Test runtime partition pruning
+--
+create table ab (a int not null, b int not null) partition by list (a);
+create table ab_a2 partition of ab for values in(2) partition by list (b);
+create table ab_a2_b1 partition of ab_a2 for values in (1);
+create table ab_a2_b2 partition of ab_a2 for values in (2);
+create table ab_a2_b3 partition of ab_a2 for values in (3);
+create table ab_a1 partition of ab for values in(1) partition by list (b);
+create table ab_a1_b1 partition of ab_a1 for values in (1);
+create table ab_a1_b2 partition of ab_a1 for values in (2);
+create table ab_a1_b3 partition of ab_a1 for values in (3);
+create table ab_a3 partition of ab for values in(3) partition by list (b);
+create table ab_a3_b1 partition of ab_a3 for values in (1);
+create table ab_a3_b2 partition of ab_a3 for values in (2);
+create table ab_a3_b3 partition of ab_a3 for values in (3);
+prepare ab_q1 (int, int, int) as
+select * from ab where a between $1 and $2 and b <= $3;
+-- Execute query 5 times to allow choose_custom_plan
+-- to start considering a generic plan.
+execute ab_q1 (1, 8, 3);
+ a | b 
+---+---
+(0 rows)
+
+execute ab_q1 (1, 8, 3);
+ a | b 
+---+---
+(0 rows)
+
+execute ab_q1 (1, 8, 3);
+ a | b 
+---+---
+(0 rows)
+
+execute ab_q1 (1, 8, 3);
+ a | b 
+---+---
+(0 rows)
+
+execute ab_q1 (1, 8, 3);
+ a | b 
+---+---
+(0 rows)
+
+explain (analyze, costs off, summary off, timing off) execute ab_q1 (2, 2, 3);
+                       QUERY PLAN                        
+---------------------------------------------------------
+ Append (actual rows=0 loops=1)
+   ->  Seq Scan on ab_a1_b1 (never executed)
+         Filter: ((a >= $1) AND (a <= $2) AND (b <= $3))
+   ->  Seq Scan on ab_a1_b2 (never executed)
+         Filter: ((a >= $1) AND (a <= $2) AND (b <= $3))
+   ->  Seq Scan on ab_a1_b3 (never executed)
+         Filter: ((a >= $1) AND (a <= $2) AND (b <= $3))
+   ->  Seq Scan on ab_a2_b1 (actual rows=0 loops=1)
+         Filter: ((a >= $1) AND (a <= $2) AND (b <= $3))
+   ->  Seq Scan on ab_a2_b2 (actual rows=0 loops=1)
+         Filter: ((a >= $1) AND (a <= $2) AND (b <= $3))
+   ->  Seq Scan on ab_a2_b3 (actual rows=0 loops=1)
+         Filter: ((a >= $1) AND (a <= $2) AND (b <= $3))
+   ->  Seq Scan on ab_a3_b1 (never executed)
+         Filter: ((a >= $1) AND (a <= $2) AND (b <= $3))
+   ->  Seq Scan on ab_a3_b2 (never executed)
+         Filter: ((a >= $1) AND (a <= $2) AND (b <= $3))
+   ->  Seq Scan on ab_a3_b3 (never executed)
+         Filter: ((a >= $1) AND (a <= $2) AND (b <= $3))
+(19 rows)
+
+explain (analyze, costs off, summary off, timing off) execute ab_q1 (1, 2, 3);
+                       QUERY PLAN                        
+---------------------------------------------------------
+ Append (actual rows=0 loops=1)
+   ->  Seq Scan on ab_a1_b1 (actual rows=0 loops=1)
+         Filter: ((a >= $1) AND (a <= $2) AND (b <= $3))
+   ->  Seq Scan on ab_a1_b2 (actual rows=0 loops=1)
+         Filter: ((a >= $1) AND (a <= $2) AND (b <= $3))
+   ->  Seq Scan on ab_a1_b3 (actual rows=0 loops=1)
+         Filter: ((a >= $1) AND (a <= $2) AND (b <= $3))
+   ->  Seq Scan on ab_a2_b1 (actual rows=0 loops=1)
+         Filter: ((a >= $1) AND (a <= $2) AND (b <= $3))
+   ->  Seq Scan on ab_a2_b2 (actual rows=0 loops=1)
+         Filter: ((a >= $1) AND (a <= $2) AND (b <= $3))
+   ->  Seq Scan on ab_a2_b3 (actual rows=0 loops=1)
+         Filter: ((a >= $1) AND (a <= $2) AND (b <= $3))
+   ->  Seq Scan on ab_a3_b1 (never executed)
+         Filter: ((a >= $1) AND (a <= $2) AND (b <= $3))
+   ->  Seq Scan on ab_a3_b2 (never executed)
+         Filter: ((a >= $1) AND (a <= $2) AND (b <= $3))
+   ->  Seq Scan on ab_a3_b3 (never executed)
+         Filter: ((a >= $1) AND (a <= $2) AND (b <= $3))
+(19 rows)
+
+deallocate ab_q1;
+-- runtime pruning after optimizer pruning
+prepare ab_q1 (int, int) as
+select a from ab where a between $1 and $2 and b < 3;
+-- Execute query 5 times to allow choose_custom_plan
+-- to start considering a generic plan.
+execute ab_q1 (1, 8);
+ a 
+---
+(0 rows)
+
+execute ab_q1 (1, 8);
+ a 
+---
+(0 rows)
+
+execute ab_q1 (1, 8);
+ a 
+---
+(0 rows)
+
+execute ab_q1 (1, 8);
+ a 
+---
+(0 rows)
+
+execute ab_q1 (1, 8);
+ a 
+---
+(0 rows)
+
+explain (analyze, costs off, summary off, timing off) execute ab_q1 (2, 2);
+                      QUERY PLAN                       
+-------------------------------------------------------
+ Append (actual rows=0 loops=1)
+   ->  Seq Scan on ab_a1_b1 (never executed)
+         Filter: ((a >= $1) AND (a <= $2) AND (b < 3))
+   ->  Seq Scan on ab_a1_b2 (never executed)
+         Filter: ((a >= $1) AND (a <= $2) AND (b < 3))
+   ->  Seq Scan on ab_a2_b1 (actual rows=0 loops=1)
+         Filter: ((a >= $1) AND (a <= $2) AND (b < 3))
+   ->  Seq Scan on ab_a2_b2 (actual rows=0 loops=1)
+         Filter: ((a >= $1) AND (a <= $2) AND (b < 3))
+   ->  Seq Scan on ab_a3_b1 (never executed)
+         Filter: ((a >= $1) AND (a <= $2) AND (b < 3))
+   ->  Seq Scan on ab_a3_b2 (never executed)
+         Filter: ((a >= $1) AND (a <= $2) AND (b < 3))
+(13 rows)
+
+explain (analyze, costs off, summary off, timing off) execute ab_q1 (2, 4);
+                      QUERY PLAN                       
+-------------------------------------------------------
+ Append (actual rows=0 loops=1)
+   ->  Seq Scan on ab_a1_b1 (never executed)
+         Filter: ((a >= $1) AND (a <= $2) AND (b < 3))
+   ->  Seq Scan on ab_a1_b2 (never executed)
+         Filter: ((a >= $1) AND (a <= $2) AND (b < 3))
+   ->  Seq Scan on ab_a2_b1 (actual rows=0 loops=1)
+         Filter: ((a >= $1) AND (a <= $2) AND (b < 3))
+   ->  Seq Scan on ab_a2_b2 (actual rows=0 loops=1)
+         Filter: ((a >= $1) AND (a <= $2) AND (b < 3))
+   ->  Seq Scan on ab_a3_b1 (actual rows=0 loops=1)
+         Filter: ((a >= $1) AND (a <= $2) AND (b < 3))
+   ->  Seq Scan on ab_a3_b2 (actual rows=0 loops=1)
+         Filter: ((a >= $1) AND (a <= $2) AND (b < 3))
+(13 rows)
+
+-- parallel append
+prepare ab_q2 (int, int) as
+select avg(a) from ab where a between $1 and $2 and b < 4;
+-- encourage use of parallel plans
+set parallel_setup_cost = 0;
+set parallel_tuple_cost = 0;
+set min_parallel_table_scan_size = 0;
+set max_parallel_workers_per_gather = 2;
+-- Execute query 5 times to allow choose_custom_plan
+-- to start considering a generic plan.
+execute ab_q2 (1, 8);
+ avg 
+-----
+    
+(1 row)
+
+execute ab_q2 (1, 8);
+ avg 
+-----
+    
+(1 row)
+
+execute ab_q2 (1, 8);
+ avg 
+-----
+    
+(1 row)
+
+execute ab_q2 (1, 8);
+ avg 
+-----
+    
+(1 row)
+
+execute ab_q2 (1, 8);
+ avg 
+-----
+    
+(1 row)
+
+explain (analyze, costs off, summary off, timing off) execute ab_q2 (2, 2);
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ Finalize Aggregate (actual rows=1 loops=1)
+   ->  Gather (actual rows=3 loops=1)
+         Workers Planned: 2
+         Workers Launched: 2
+         ->  Partial Aggregate (actual rows=1 loops=3)
+               ->  Parallel Append (actual rows=0 loops=3)
+                     ->  Parallel Seq Scan on ab_a1_b1 (never executed)
+                           Filter: ((a >= $1) AND (a <= $2) AND (b < 4))
+                     ->  Parallel Seq Scan on ab_a1_b2 (never executed)
+                           Filter: ((a >= $1) AND (a <= $2) AND (b < 4))
+                     ->  Parallel Seq Scan on ab_a1_b3 (never executed)
+                           Filter: ((a >= $1) AND (a <= $2) AND (b < 4))
+                     ->  Parallel Seq Scan on ab_a2_b1 (actual rows=0 loops=1)
+                           Filter: ((a >= $1) AND (a <= $2) AND (b < 4))
+                     ->  Parallel Seq Scan on ab_a2_b2 (actual rows=0 loops=1)
+                           Filter: ((a >= $1) AND (a <= $2) AND (b < 4))
+                     ->  Parallel Seq Scan on ab_a2_b3 (actual rows=0 loops=1)
+                           Filter: ((a >= $1) AND (a <= $2) AND (b < 4))
+                     ->  Parallel Seq Scan on ab_a3_b1 (never executed)
+                           Filter: ((a >= $1) AND (a <= $2) AND (b < 4))
+                     ->  Parallel Seq Scan on ab_a3_b2 (never executed)
+                           Filter: ((a >= $1) AND (a <= $2) AND (b < 4))
+                     ->  Parallel Seq Scan on ab_a3_b3 (never executed)
+                           Filter: ((a >= $1) AND (a <= $2) AND (b < 4))
+(24 rows)
+
+-- Test run-time pruning with IN lists.
+prepare ab_q3 (int, int, int) as
+select avg(a) from ab where a in($1,$2,$3) and b < 4;
+-- Execute query 5 times to allow choose_custom_plan
+-- to start considering a generic plan.
+execute ab_q3 (1, 2, 3);
+ avg 
+-----
+    
+(1 row)
+
+execute ab_q3 (1, 2, 3);
+ avg 
+-----
+    
+(1 row)
+
+execute ab_q3 (1, 2, 3);
+ avg 
+-----
+    
+(1 row)
+
+execute ab_q3 (1, 2, 3);
+ avg 
+-----
+    
+(1 row)
+
+execute ab_q3 (1, 2, 3);
+ avg 
+-----
+    
+(1 row)
+
+explain (analyze, costs off, summary off, timing off) execute ab_q3 (1, 1, 1);
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ Finalize Aggregate (actual rows=1 loops=1)
+   ->  Gather (actual rows=3 loops=1)
+         Workers Planned: 2
+         Workers Launched: 2
+         ->  Partial Aggregate (actual rows=1 loops=3)
+               ->  Parallel Append (actual rows=0 loops=3)
+                     ->  Parallel Seq Scan on ab_a1_b1 (actual rows=0 loops=1)
+                           Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+                     ->  Parallel Seq Scan on ab_a1_b2 (actual rows=0 loops=1)
+                           Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+                     ->  Parallel Seq Scan on ab_a1_b3 (actual rows=0 loops=1)
+                           Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+                     ->  Parallel Seq Scan on ab_a2_b1 (never executed)
+                           Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+                     ->  Parallel Seq Scan on ab_a2_b2 (never executed)
+                           Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+                     ->  Parallel Seq Scan on ab_a2_b3 (never executed)
+                           Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+                     ->  Parallel Seq Scan on ab_a3_b1 (never executed)
+                           Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+                     ->  Parallel Seq Scan on ab_a3_b2 (never executed)
+                           Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+                     ->  Parallel Seq Scan on ab_a3_b3 (never executed)
+                           Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+(24 rows)
+
+explain (analyze, costs off, summary off, timing off) execute ab_q3 (2, 3, 3);
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ Finalize Aggregate (actual rows=1 loops=1)
+   ->  Gather (actual rows=3 loops=1)
+         Workers Planned: 2
+         Workers Launched: 2
+         ->  Partial Aggregate (actual rows=1 loops=3)
+               ->  Parallel Append (actual rows=0 loops=3)
+                     ->  Parallel Seq Scan on ab_a1_b1 (never executed)
+                           Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+                     ->  Parallel Seq Scan on ab_a1_b2 (never executed)
+                           Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+                     ->  Parallel Seq Scan on ab_a1_b3 (never executed)
+                           Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+                     ->  Parallel Seq Scan on ab_a2_b1 (actual rows=0 loops=1)
+                           Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+                     ->  Parallel Seq Scan on ab_a2_b2 (actual rows=0 loops=1)
+                           Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+                     ->  Parallel Seq Scan on ab_a2_b3 (actual rows=0 loops=1)
+                           Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+                     ->  Parallel Seq Scan on ab_a3_b1 (actual rows=0 loops=1)
+                           Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+                     ->  Parallel Seq Scan on ab_a3_b2 (actual rows=0 loops=1)
+                           Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+                     ->  Parallel Seq Scan on ab_a3_b3 (actual rows=0 loops=1)
+                           Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+(24 rows)
+
+-- try some params whose values do not belong to any partition
+explain (analyze, costs off, summary off, timing off) execute ab_q3 (33, 44, 55);
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ Finalize Aggregate (actual rows=1 loops=1)
+   ->  Gather (actual rows=3 loops=1)
+         Workers Planned: 2
+         Workers Launched: 2
+         ->  Partial Aggregate (actual rows=1 loops=3)
+               ->  Parallel Append (actual rows=0 loops=3)
+                     ->  Parallel Seq Scan on ab_a1_b1 (never executed)
+                           Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+                     ->  Parallel Seq Scan on ab_a1_b2 (never executed)
+                           Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+                     ->  Parallel Seq Scan on ab_a1_b3 (never executed)
+                           Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+                     ->  Parallel Seq Scan on ab_a2_b1 (never executed)
+                           Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+                     ->  Parallel Seq Scan on ab_a2_b2 (never executed)
+                           Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+                     ->  Parallel Seq Scan on ab_a2_b3 (never executed)
+                           Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+                     ->  Parallel Seq Scan on ab_a3_b1 (never executed)
+                           Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+                     ->  Parallel Seq Scan on ab_a3_b2 (never executed)
+                           Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+                     ->  Parallel Seq Scan on ab_a3_b3 (never executed)
+                           Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3])))
+(24 rows)
+
+-- test parallel Append with IN list and parameterized nested loops
+create table lprt_a (a int not null);
+-- insert some values we won't find in ab
+insert into lprt_a select 0 from generate_series(1,100);
+-- and insert some values that we should find.
+insert into lprt_a values(1),(1);
+analyze lprt_a;
+create index ab_a2_b1_a_idx on ab_a2_b1 (a);
+create index ab_a2_b2_a_idx on ab_a2_b2 (a);
+create index ab_a2_b3_a_idx on ab_a2_b3 (a);
+create index ab_a1_b1_a_idx on ab_a1_b1 (a);
+create index ab_a1_b2_a_idx on ab_a1_b2 (a);
+create index ab_a1_b3_a_idx on ab_a1_b3 (a);
+create index ab_a3_b1_a_idx on ab_a3_b1 (a);
+create index ab_a3_b2_a_idx on ab_a3_b2 (a);
+create index ab_a3_b3_a_idx on ab_a3_b3 (a);
+set enable_hashjoin = 0;
+set enable_mergejoin = 0;
+prepare ab_q4 (int, int, int) as
+select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in($1,$2,$3);
+execute ab_q4 (1, 2, 3);
+ avg 
+-----
+    
+(1 row)
+
+execute ab_q4 (1, 2, 3);
+ avg 
+-----
+    
+(1 row)
+
+execute ab_q4 (1, 2, 3);
+ avg 
+-----
+    
+(1 row)
+
+execute ab_q4 (1, 2, 3);
+ avg 
+-----
+    
+(1 row)
+
+execute ab_q4 (1, 2, 3);
+ avg 
+-----
+    
+(1 row)
+
+explain (analyze, costs off, summary off, timing off) execute ab_q4 (0, 0, 1);
+                                               QUERY PLAN                                               
+--------------------------------------------------------------------------------------------------------
+ Finalize Aggregate (actual rows=1 loops=1)
+   ->  Gather (actual rows=2 loops=1)
+         Workers Planned: 1
+         Workers Launched: 1
+         ->  Partial Aggregate (actual rows=1 loops=2)
+               ->  Nested Loop (actual rows=0 loops=2)
+                     ->  Parallel Seq Scan on lprt_a a (actual rows=51 loops=2)
+                           Filter: (a = ANY ('{0,0,1}'::integer[]))
+                     ->  Append (actual rows=0 loops=102)
+                           ->  Index Only Scan using ab_a1_b1_a_idx on ab_a1_b1 (actual rows=0 loops=2)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a1_b2_a_idx on ab_a1_b2 (actual rows=0 loops=2)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a1_b3_a_idx on ab_a1_b3 (actual rows=0 loops=2)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a2_b1_a_idx on ab_a2_b1 (never executed)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a2_b2_a_idx on ab_a2_b2 (never executed)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a2_b3_a_idx on ab_a2_b3 (never executed)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a3_b1_a_idx on ab_a3_b1 (never executed)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a3_b2_a_idx on ab_a3_b2 (never executed)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a3_b3_a_idx on ab_a3_b3 (never executed)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+(36 rows)
+
+insert into lprt_a values(3),(3);
+explain (analyze, costs off, summary off, timing off) execute ab_q4 (1, 0, 3);
+                                               QUERY PLAN                                               
+--------------------------------------------------------------------------------------------------------
+ Finalize Aggregate (actual rows=1 loops=1)
+   ->  Gather (actual rows=2 loops=1)
+         Workers Planned: 1
+         Workers Launched: 1
+         ->  Partial Aggregate (actual rows=1 loops=2)
+               ->  Nested Loop (actual rows=0 loops=2)
+                     ->  Parallel Seq Scan on lprt_a a (actual rows=52 loops=2)
+                           Filter: (a = ANY ('{1,0,3}'::integer[]))
+                     ->  Append (actual rows=0 loops=104)
+                           ->  Index Only Scan using ab_a1_b1_a_idx on ab_a1_b1 (actual rows=0 loops=2)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a1_b2_a_idx on ab_a1_b2 (actual rows=0 loops=2)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a1_b3_a_idx on ab_a1_b3 (actual rows=0 loops=2)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a2_b1_a_idx on ab_a2_b1 (never executed)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a2_b2_a_idx on ab_a2_b2 (never executed)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a2_b3_a_idx on ab_a2_b3 (never executed)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a3_b1_a_idx on ab_a3_b1 (actual rows=0 loops=2)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a3_b2_a_idx on ab_a3_b2 (actual rows=0 loops=2)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a3_b3_a_idx on ab_a3_b3 (actual rows=0 loops=2)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+(36 rows)
+
+explain (analyze, costs off, summary off, timing off) execute ab_q4 (1, 0, 0);
+                                               QUERY PLAN                                               
+--------------------------------------------------------------------------------------------------------
+ Finalize Aggregate (actual rows=1 loops=1)
+   ->  Gather (actual rows=2 loops=1)
+         Workers Planned: 1
+         Workers Launched: 1
+         ->  Partial Aggregate (actual rows=1 loops=2)
+               ->  Nested Loop (actual rows=0 loops=2)
+                     ->  Parallel Seq Scan on lprt_a a (actual rows=51 loops=2)
+                           Filter: (a = ANY ('{1,0,0}'::integer[]))
+                           Rows Removed by Filter: 1
+                     ->  Append (actual rows=0 loops=102)
+                           ->  Index Only Scan using ab_a1_b1_a_idx on ab_a1_b1 (actual rows=0 loops=2)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a1_b2_a_idx on ab_a1_b2 (actual rows=0 loops=2)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a1_b3_a_idx on ab_a1_b3 (actual rows=0 loops=2)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a2_b1_a_idx on ab_a2_b1 (never executed)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a2_b2_a_idx on ab_a2_b2 (never executed)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a2_b3_a_idx on ab_a2_b3 (never executed)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a3_b1_a_idx on ab_a3_b1 (never executed)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a3_b2_a_idx on ab_a3_b2 (never executed)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a3_b3_a_idx on ab_a3_b3 (never executed)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+(37 rows)
+
+delete from lprt_a where a = 1;
+explain (analyze, costs off, summary off, timing off) execute ab_q4 (1, 0, 0);
+                                           QUERY PLAN                                            
+-------------------------------------------------------------------------------------------------
+ Finalize Aggregate (actual rows=1 loops=1)
+   ->  Gather (actual rows=2 loops=1)
+         Workers Planned: 1
+         Workers Launched: 1
+         ->  Partial Aggregate (actual rows=1 loops=2)
+               ->  Nested Loop (actual rows=0 loops=2)
+                     ->  Parallel Seq Scan on lprt_a a (actual rows=50 loops=2)
+                           Filter: (a = ANY ('{1,0,0}'::integer[]))
+                           Rows Removed by Filter: 1
+                     ->  Append (actual rows=0 loops=100)
+                           ->  Index Only Scan using ab_a1_b1_a_idx on ab_a1_b1 (never executed)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a1_b2_a_idx on ab_a1_b2 (never executed)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a1_b3_a_idx on ab_a1_b3 (never executed)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a2_b1_a_idx on ab_a2_b1 (never executed)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a2_b2_a_idx on ab_a2_b2 (never executed)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a2_b3_a_idx on ab_a2_b3 (never executed)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a3_b1_a_idx on ab_a3_b1 (never executed)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a3_b2_a_idx on ab_a3_b2 (never executed)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+                           ->  Index Only Scan using ab_a3_b3_a_idx on ab_a3_b3 (never executed)
+                                 Index Cond: (a = a.a)
+                                 Heap Fetches: 0
+(37 rows)
+
+reset enable_hashjoin;
+reset enable_mergejoin;
+reset parallel_setup_cost;
+reset parallel_tuple_cost;
+reset min_parallel_table_scan_size;
+reset max_parallel_workers_per_gather;
+-- Test run-time partition pruning with an initplan
+explain (analyze, costs off, summary off, timing off)
+select * from ab where a = (select max(a) from lprt_a) and b = (select max(a)-1 from lprt_a);
+                               QUERY PLAN                                
+-------------------------------------------------------------------------
+ Append (actual rows=0 loops=1)
+   InitPlan 1 (returns $0)
+     ->  Aggregate (actual rows=1 loops=1)
+           ->  Seq Scan on lprt_a (actual rows=102 loops=1)
+   InitPlan 2 (returns $1)
+     ->  Aggregate (actual rows=1 loops=1)
+           ->  Seq Scan on lprt_a lprt_a_1 (actual rows=102 loops=1)
+   ->  Bitmap Heap Scan on ab_a1_b1 (never executed)
+         Recheck Cond: (a = $0)
+         Filter: (b = $1)
+         ->  Bitmap Index Scan on ab_a1_b1_a_idx (never executed)
+               Index Cond: (a = $0)
+   ->  Bitmap Heap Scan on ab_a1_b2 (never executed)
+         Recheck Cond: (a = $0)
+         Filter: (b = $1)
+         ->  Bitmap Index Scan on ab_a1_b2_a_idx (never executed)
+               Index Cond: (a = $0)
+   ->  Bitmap Heap Scan on ab_a1_b3 (never executed)
+         Recheck Cond: (a = $0)
+         Filter: (b = $1)
+         ->  Bitmap Index Scan on ab_a1_b3_a_idx (never executed)
+               Index Cond: (a = $0)
+   ->  Bitmap Heap Scan on ab_a2_b1 (never executed)
+         Recheck Cond: (a = $0)
+         Filter: (b = $1)
+         ->  Bitmap Index Scan on ab_a2_b1_a_idx (never executed)
+               Index Cond: (a = $0)
+   ->  Bitmap Heap Scan on ab_a2_b2 (never executed)
+         Recheck Cond: (a = $0)
+         Filter: (b = $1)
+         ->  Bitmap Index Scan on ab_a2_b2_a_idx (never executed)
+               Index Cond: (a = $0)
+   ->  Bitmap Heap Scan on ab_a2_b3 (never executed)
+         Recheck Cond: (a = $0)
+         Filter: (b = $1)
+         ->  Bitmap Index Scan on ab_a2_b3_a_idx (never executed)
+               Index Cond: (a = $0)
+   ->  Bitmap Heap Scan on ab_a3_b1 (never executed)
+         Recheck Cond: (a = $0)
+         Filter: (b = $1)
+         ->  Bitmap Index Scan on ab_a3_b1_a_idx (never executed)
+               Index Cond: (a = $0)
+   ->  Bitmap Heap Scan on ab_a3_b2 (actual rows=0 loops=1)
+         Recheck Cond: (a = $0)
+         Filter: (b = $1)
+         ->  Bitmap Index Scan on ab_a3_b2_a_idx (actual rows=0 loops=1)
+               Index Cond: (a = $0)
+   ->  Bitmap Heap Scan on ab_a3_b3 (never executed)
+         Recheck Cond: (a = $0)
+         Filter: (b = $1)
+         ->  Bitmap Index Scan on ab_a3_b3_a_idx (never executed)
+               Index Cond: (a = $0)
+(52 rows)
+
+deallocate ab_q1;
+deallocate ab_q2;
+deallocate ab_q3;
+deallocate ab_q4;
+drop table ab, lprt_a;
+-- join
+create table tbl1(col1 int);
+insert into tbl1 values (501), (505);
+-- basic table
+create table tprt (col1 int) partition by range (col1);
+create table tprt_1 partition of tprt for values from (1) to (501);
+create table tprt_2 partition of tprt for values from (501) to (1001);
+create table tprt_3 partition of tprt for values from (1001) to (2001);
+create table tprt_4 partition of tprt for values from (2001) to (3001);
+create table tprt_5 partition of tprt for values from (3001) to (4001);
+create table tprt_6 partition of tprt for values from (4001) to (5001);
+create index tprt1_idx on tprt_1 (col1);
+create index tprt2_idx on tprt_2 (col1);
+create index tprt3_idx on tprt_3 (col1);
+create index tprt4_idx on tprt_4 (col1);
+create index tprt5_idx on tprt_5 (col1);
+create index tprt6_idx on tprt_6 (col1);
+insert into tprt values (10), (20), (501), (502), (505), (1001), (4500);
+set enable_hashjoin = off;
+set enable_mergejoin = off;
+explain (analyze, costs off, summary off, timing off)
+select * from tbl1 join tprt on tbl1.col1 > tprt.col1;
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ Nested Loop (actual rows=6 loops=1)
+   ->  Seq Scan on tbl1 (actual rows=2 loops=1)
+   ->  Append (actual rows=3 loops=2)
+         ->  Index Only Scan using tprt1_idx on tprt_1 (actual rows=2 loops=2)
+               Index Cond: (col1 < tbl1.col1)
+               Heap Fetches: 4
+         ->  Index Only Scan using tprt2_idx on tprt_2 (actual rows=2 loops=1)
+               Index Cond: (col1 < tbl1.col1)
+               Heap Fetches: 2
+         ->  Index Only Scan using tprt3_idx on tprt_3 (never executed)
+               Index Cond: (col1 < tbl1.col1)
+               Heap Fetches: 0
+         ->  Index Only Scan using tprt4_idx on tprt_4 (never executed)
+               Index Cond: (col1 < tbl1.col1)
+               Heap Fetches: 0
+         ->  Index Only Scan using tprt5_idx on tprt_5 (never executed)
+               Index Cond: (col1 < tbl1.col1)
+               Heap Fetches: 0
+         ->  Index Only Scan using tprt6_idx on tprt_6 (never executed)
+               Index Cond: (col1 < tbl1.col1)
+               Heap Fetches: 0
+(21 rows)
+
+explain (analyze, costs off, summary off, timing off)
+select * from tbl1 join tprt on tbl1.col1 = tprt.col1;
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ Nested Loop (actual rows=2 loops=1)
+   ->  Seq Scan on tbl1 (actual rows=2 loops=1)
+   ->  Append (actual rows=1 loops=2)
+         ->  Index Only Scan using tprt1_idx on tprt_1 (never executed)
+               Index Cond: (col1 = tbl1.col1)
+               Heap Fetches: 0
+         ->  Index Only Scan using tprt2_idx on tprt_2 (actual rows=1 loops=2)
+               Index Cond: (col1 = tbl1.col1)
+               Heap Fetches: 2
+         ->  Index Only Scan using tprt3_idx on tprt_3 (never executed)
+               Index Cond: (col1 = tbl1.col1)
+               Heap Fetches: 0
+         ->  Index Only Scan using tprt4_idx on tprt_4 (never executed)
+               Index Cond: (col1 = tbl1.col1)
+               Heap Fetches: 0
+         ->  Index Only Scan using tprt5_idx on tprt_5 (never executed)
+               Index Cond: (col1 = tbl1.col1)
+               Heap Fetches: 0
+         ->  Index Only Scan using tprt6_idx on tprt_6 (never executed)
+               Index Cond: (col1 = tbl1.col1)
+               Heap Fetches: 0
+(21 rows)
+
+select tbl1.col1, tprt.col1 from tbl1
+inner join tprt on tbl1.col1 > tprt.col1
+order by tbl1.col1, tprt.col1;
+ col1 | col1 
+------+------
+  501 |   10
+  501 |   20
+  505 |   10
+  505 |   20
+  505 |  501
+  505 |  502
+(6 rows)
+
+select tbl1.col1, tprt.col1 from tbl1
+inner join tprt on tbl1.col1 = tprt.col1
+order by tbl1.col1, tprt.col1;
+ col1 | col1 
+------+------
+  501 |  501
+  505 |  505
+(2 rows)
+
+-- multiple partitions
+insert into tbl1 values (1001), (1010), (1011);
+explain (analyze, costs off, summary off, timing off)
+select * from tbl1 inner join tprt on tbl1.col1 > tprt.col1;
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ Nested Loop (actual rows=23 loops=1)
+   ->  Seq Scan on tbl1 (actual rows=5 loops=1)
+   ->  Append (actual rows=5 loops=5)
+         ->  Index Only Scan using tprt1_idx on tprt_1 (actual rows=2 loops=5)
+               Index Cond: (col1 < tbl1.col1)
+               Heap Fetches: 10
+         ->  Index Only Scan using tprt2_idx on tprt_2 (actual rows=3 loops=4)
+               Index Cond: (col1 < tbl1.col1)
+               Heap Fetches: 11
+         ->  Index Only Scan using tprt3_idx on tprt_3 (actual rows=1 loops=2)
+               Index Cond: (col1 < tbl1.col1)
+               Heap Fetches: 2
+         ->  Index Only Scan using tprt4_idx on tprt_4 (never executed)
+               Index Cond: (col1 < tbl1.col1)
+               Heap Fetches: 0
+         ->  Index Only Scan using tprt5_idx on tprt_5 (never executed)
+               Index Cond: (col1 < tbl1.col1)
+               Heap Fetches: 0
+         ->  Index Only Scan using tprt6_idx on tprt_6 (never executed)
+               Index Cond: (col1 < tbl1.col1)
+               Heap Fetches: 0
+(21 rows)
+
+explain (analyze, costs off, summary off, timing off)
+select * from tbl1 inner join tprt on tbl1.col1 = tprt.col1;
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ Nested Loop (actual rows=3 loops=1)
+   ->  Seq Scan on tbl1 (actual rows=5 loops=1)
+   ->  Append (actual rows=1 loops=5)
+         ->  Index Only Scan using tprt1_idx on tprt_1 (never executed)
+               Index Cond: (col1 = tbl1.col1)
+               Heap Fetches: 0
+         ->  Index Only Scan using tprt2_idx on tprt_2 (actual rows=1 loops=2)
+               Index Cond: (col1 = tbl1.col1)
+               Heap Fetches: 2
+         ->  Index Only Scan using tprt3_idx on tprt_3 (actual rows=0 loops=3)
+               Index Cond: (col1 = tbl1.col1)
+               Heap Fetches: 1
+         ->  Index Only Scan using tprt4_idx on tprt_4 (never executed)
+               Index Cond: (col1 = tbl1.col1)
+               Heap Fetches: 0
+         ->  Index Only Scan using tprt5_idx on tprt_5 (never executed)
+               Index Cond: (col1 = tbl1.col1)
+               Heap Fetches: 0
+         ->  Index Only Scan using tprt6_idx on tprt_6 (never executed)
+               Index Cond: (col1 = tbl1.col1)
+               Heap Fetches: 0
+(21 rows)
+
+select tbl1.col1, tprt.col1 from tbl1
+inner join tprt on tbl1.col1 > tprt.col1
+order by tbl1.col1, tprt.col1;
+ col1 | col1 
+------+------
+  501 |   10
+  501 |   20
+  505 |   10
+  505 |   20
+  505 |  501
+  505 |  502
+ 1001 |   10
+ 1001 |   20
+ 1001 |  501
+ 1001 |  502
+ 1001 |  505
+ 1010 |   10
+ 1010 |   20
+ 1010 |  501
+ 1010 |  502
+ 1010 |  505
+ 1010 | 1001
+ 1011 |   10
+ 1011 |   20
+ 1011 |  501
+ 1011 |  502
+ 1011 |  505
+ 1011 | 1001
+(23 rows)
+
+select tbl1.col1, tprt.col1 from tbl1
+inner join tprt on tbl1.col1 = tprt.col1
+order by tbl1.col1, tprt.col1;
+ col1 | col1 
+------+------
+  501 |  501
+  505 |  505
+ 1001 | 1001
+(3 rows)
+
+-- last partition
+delete from tbl1;
+insert into tbl1 values (4400);
+explain (analyze, costs off, summary off, timing off)
+select * from tbl1 join tprt on tbl1.col1 < tprt.col1;
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ Nested Loop (actual rows=1 loops=1)
+   ->  Seq Scan on tbl1 (actual rows=1 loops=1)
+   ->  Append (actual rows=1 loops=1)
+         ->  Index Only Scan using tprt1_idx on tprt_1 (never executed)
+               Index Cond: (col1 > tbl1.col1)
+               Heap Fetches: 0
+         ->  Index Only Scan using tprt2_idx on tprt_2 (never executed)
+               Index Cond: (col1 > tbl1.col1)
+               Heap Fetches: 0
+         ->  Index Only Scan using tprt3_idx on tprt_3 (never executed)
+               Index Cond: (col1 > tbl1.col1)
+               Heap Fetches: 0
+         ->  Index Only Scan using tprt4_idx on tprt_4 (never executed)
+               Index Cond: (col1 > tbl1.col1)
+               Heap Fetches: 0
+         ->  Index Only Scan using tprt5_idx on tprt_5 (never executed)
+               Index Cond: (col1 > tbl1.col1)
+               Heap Fetches: 0
+         ->  Index Only Scan using tprt6_idx on tprt_6 (actual rows=1 loops=1)
+               Index Cond: (col1 > tbl1.col1)
+               Heap Fetches: 1
+(21 rows)
+
+select tbl1.col1, tprt.col1 from tbl1
+inner join tprt on tbl1.col1 < tprt.col1
+order by tbl1.col1, tprt.col1;
+ col1 | col1 
+------+------
+ 4400 | 4500
+(1 row)
+
+-- no matching partition
+delete from tbl1;
+insert into tbl1 values (10000);
+explain (analyze, costs off, summary off, timing off)
+select * from tbl1 join tprt on tbl1.col1 = tprt.col1;
+                               QUERY PLAN                               
+------------------------------------------------------------------------
+ Nested Loop (actual rows=0 loops=1)
+   ->  Seq Scan on tbl1 (actual rows=1 loops=1)
+   ->  Append (actual rows=0 loops=1)
+         ->  Index Only Scan using tprt1_idx on tprt_1 (never executed)
+               Index Cond: (col1 = tbl1.col1)
+               Heap Fetches: 0
+         ->  Index Only Scan using tprt2_idx on tprt_2 (never executed)
+               Index Cond: (col1 = tbl1.col1)
+               Heap Fetches: 0
+         ->  Index Only Scan using tprt3_idx on tprt_3 (never executed)
+               Index Cond: (col1 = tbl1.col1)
+               Heap Fetches: 0
+         ->  Index Only Scan using tprt4_idx on tprt_4 (never executed)
+               Index Cond: (col1 = tbl1.col1)
+               Heap Fetches: 0
+         ->  Index Only Scan using tprt5_idx on tprt_5 (never executed)
+               Index Cond: (col1 = tbl1.col1)
+               Heap Fetches: 0
+         ->  Index Only Scan using tprt6_idx on tprt_6 (never executed)
+               Index Cond: (col1 = tbl1.col1)
+               Heap Fetches: 0
+(21 rows)
+
+select tbl1.col1, tprt.col1 from tbl1
+inner join tprt on tbl1.col1 = tprt.col1
+order by tbl1.col1, tprt.col1;
+ col1 | col1 
+------+------
+(0 rows)
+
+drop table tbl1, tprt;
+-- test with columns defined in varying orders between each level
+create table part_abc (a int not null, b int not null, c int not null) partition by list (a);
+create table part_bac (b int not null, a int not null, c int not null) partition by list (b);
+create table part_cab (c int not null, a int not null, b int not null) partition by list (c);
+create table part_abc_p1 (a int not null, b int not null, c int not null);
+alter table part_abc attach partition part_bac for values in(1);
+alter table part_bac attach partition part_cab for values in(2);
+alter table part_cab attach partition part_abc_p1 for values in(3);
+prepare part_abc_q1 (int, int, int) as
+select * from part_abc where a = $1 and b = $2 and c = $3;
+-- Execute query 5 times to allow choose_custom_plan
+-- to start considering a generic plan.
+execute part_abc_q1 (1, 2, 3);
+ a | b | c 
+---+---+---
+(0 rows)
+
+execute part_abc_q1 (1, 2, 3);
+ a | b | c 
+---+---+---
+(0 rows)
+
+execute part_abc_q1 (1, 2, 3);
+ a | b | c 
+---+---+---
+(0 rows)
+
+execute part_abc_q1 (1, 2, 3);
+ a | b | c 
+---+---+---
+(0 rows)
+
+execute part_abc_q1 (1, 2, 3);
+ a | b | c 
+---+---+---
+(0 rows)
+
+-- single partition should be scanned.
+explain (analyze, costs off, summary off, timing off) execute part_abc_q1 (1, 2, 3);
+                      QUERY PLAN                       
+-------------------------------------------------------
+ Append (actual rows=0 loops=1)
+   ->  Seq Scan on part_abc_p1 (actual rows=0 loops=1)
+         Filter: ((a = $1) AND (b = $2) AND (c = $3))
+(3 rows)
+
+deallocate part_abc_q1;
+drop table part_abc;
diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql
index b7c5abf..f66c193 100644
--- a/src/test/regress/sql/partition_prune.sql
+++ b/src/test/regress/sql/partition_prune.sql
@@ -228,3 +228,255 @@ explain (costs off) select * from lp where (a <> 'a' and a <> 'd') or a is null;
 explain (costs off) select * from rlp where a = 15 and b <> 'ab' and b <> 'cd' and b <> 'xy' and b is not null;
 
 drop table lp, coll_pruning, rlp, mc3p, mc2p, boolpart, hp, rp;
+
+--
+-- Test runtime partition pruning
+--
+create table ab (a int not null, b int not null) partition by list (a);
+create table ab_a2 partition of ab for values in(2) partition by list (b);
+create table ab_a2_b1 partition of ab_a2 for values in (1);
+create table ab_a2_b2 partition of ab_a2 for values in (2);
+create table ab_a2_b3 partition of ab_a2 for values in (3);
+create table ab_a1 partition of ab for values in(1) partition by list (b);
+create table ab_a1_b1 partition of ab_a1 for values in (1);
+create table ab_a1_b2 partition of ab_a1 for values in (2);
+create table ab_a1_b3 partition of ab_a1 for values in (3);
+create table ab_a3 partition of ab for values in(3) partition by list (b);
+create table ab_a3_b1 partition of ab_a3 for values in (1);
+create table ab_a3_b2 partition of ab_a3 for values in (2);
+create table ab_a3_b3 partition of ab_a3 for values in (3);
+
+prepare ab_q1 (int, int, int) as
+select * from ab where a between $1 and $2 and b <= $3;
+
+-- Execute query 5 times to allow choose_custom_plan
+-- to start considering a generic plan.
+execute ab_q1 (1, 8, 3);
+execute ab_q1 (1, 8, 3);
+execute ab_q1 (1, 8, 3);
+execute ab_q1 (1, 8, 3);
+execute ab_q1 (1, 8, 3);
+
+explain (analyze, costs off, summary off, timing off) execute ab_q1 (2, 2, 3);
+explain (analyze, costs off, summary off, timing off) execute ab_q1 (1, 2, 3);
+
+deallocate ab_q1;
+
+-- runtime pruning after optimizer pruning
+prepare ab_q1 (int, int) as
+select a from ab where a between $1 and $2 and b < 3;
+
+-- Execute query 5 times to allow choose_custom_plan
+-- to start considering a generic plan.
+execute ab_q1 (1, 8);
+execute ab_q1 (1, 8);
+execute ab_q1 (1, 8);
+execute ab_q1 (1, 8);
+execute ab_q1 (1, 8);
+
+explain (analyze, costs off, summary off, timing off) execute ab_q1 (2, 2);
+explain (analyze, costs off, summary off, timing off) execute ab_q1 (2, 4);
+
+-- parallel append
+prepare ab_q2 (int, int) as
+select avg(a) from ab where a between $1 and $2 and b < 4;
+
+-- encourage use of parallel plans
+set parallel_setup_cost = 0;
+set parallel_tuple_cost = 0;
+set min_parallel_table_scan_size = 0;
+set max_parallel_workers_per_gather = 2;
+
+-- Execute query 5 times to allow choose_custom_plan
+-- to start considering a generic plan.
+execute ab_q2 (1, 8);
+execute ab_q2 (1, 8);
+execute ab_q2 (1, 8);
+execute ab_q2 (1, 8);
+execute ab_q2 (1, 8);
+
+explain (analyze, costs off, summary off, timing off) execute ab_q2 (2, 2);
+
+-- Test run-time pruning with IN lists.
+prepare ab_q3 (int, int, int) as
+select avg(a) from ab where a in($1,$2,$3) and b < 4;
+
+-- Execute query 5 times to allow choose_custom_plan
+-- to start considering a generic plan.
+execute ab_q3 (1, 2, 3);
+execute ab_q3 (1, 2, 3);
+execute ab_q3 (1, 2, 3);
+execute ab_q3 (1, 2, 3);
+execute ab_q3 (1, 2, 3);
+
+explain (analyze, costs off, summary off, timing off) execute ab_q3 (1, 1, 1);
+explain (analyze, costs off, summary off, timing off) execute ab_q3 (2, 3, 3);
+
+-- try some params whose values do not belong to any partition
+explain (analyze, costs off, summary off, timing off) execute ab_q3 (33, 44, 55);
+
+-- test parallel Append with IN list and parameterized nested loops
+create table lprt_a (a int not null);
+-- insert some values we won't find in ab
+insert into lprt_a select 0 from generate_series(1,100);
+
+-- and insert some values that we should find.
+insert into lprt_a values(1),(1);
+
+analyze lprt_a;
+
+create index ab_a2_b1_a_idx on ab_a2_b1 (a);
+create index ab_a2_b2_a_idx on ab_a2_b2 (a);
+create index ab_a2_b3_a_idx on ab_a2_b3 (a);
+create index ab_a1_b1_a_idx on ab_a1_b1 (a);
+create index ab_a1_b2_a_idx on ab_a1_b2 (a);
+create index ab_a1_b3_a_idx on ab_a1_b3 (a);
+create index ab_a3_b1_a_idx on ab_a3_b1 (a);
+create index ab_a3_b2_a_idx on ab_a3_b2 (a);
+create index ab_a3_b3_a_idx on ab_a3_b3 (a);
+
+set enable_hashjoin = 0;
+set enable_mergejoin = 0;
+
+prepare ab_q4 (int, int, int) as
+select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in($1,$2,$3);
+execute ab_q4 (1, 2, 3);
+execute ab_q4 (1, 2, 3);
+execute ab_q4 (1, 2, 3);
+execute ab_q4 (1, 2, 3);
+execute ab_q4 (1, 2, 3);
+
+explain (analyze, costs off, summary off, timing off) execute ab_q4 (0, 0, 1);
+
+insert into lprt_a values(3),(3);
+
+explain (analyze, costs off, summary off, timing off) execute ab_q4 (1, 0, 3);
+explain (analyze, costs off, summary off, timing off) execute ab_q4 (1, 0, 0);
+
+delete from lprt_a where a = 1;
+
+explain (analyze, costs off, summary off, timing off) execute ab_q4 (1, 0, 0);
+
+reset enable_hashjoin;
+reset enable_mergejoin;
+reset parallel_setup_cost;
+reset parallel_tuple_cost;
+reset min_parallel_table_scan_size;
+reset max_parallel_workers_per_gather;
+
+-- Test run-time partition pruning with an initplan
+explain (analyze, costs off, summary off, timing off)
+select * from ab where a = (select max(a) from lprt_a) and b = (select max(a)-1 from lprt_a);
+
+deallocate ab_q1;
+deallocate ab_q2;
+deallocate ab_q3;
+deallocate ab_q4;
+
+drop table ab, lprt_a;
+
+-- join
+create table tbl1(col1 int);
+insert into tbl1 values (501), (505);
+
+-- basic table
+create table tprt (col1 int) partition by range (col1);
+create table tprt_1 partition of tprt for values from (1) to (501);
+create table tprt_2 partition of tprt for values from (501) to (1001);
+create table tprt_3 partition of tprt for values from (1001) to (2001);
+create table tprt_4 partition of tprt for values from (2001) to (3001);
+create table tprt_5 partition of tprt for values from (3001) to (4001);
+create table tprt_6 partition of tprt for values from (4001) to (5001);
+
+create index tprt1_idx on tprt_1 (col1);
+create index tprt2_idx on tprt_2 (col1);
+create index tprt3_idx on tprt_3 (col1);
+create index tprt4_idx on tprt_4 (col1);
+create index tprt5_idx on tprt_5 (col1);
+create index tprt6_idx on tprt_6 (col1);
+
+insert into tprt values (10), (20), (501), (502), (505), (1001), (4500);
+
+set enable_hashjoin = off;
+set enable_mergejoin = off;
+
+explain (analyze, costs off, summary off, timing off)
+select * from tbl1 join tprt on tbl1.col1 > tprt.col1;
+
+explain (analyze, costs off, summary off, timing off)
+select * from tbl1 join tprt on tbl1.col1 = tprt.col1;
+
+select tbl1.col1, tprt.col1 from tbl1
+inner join tprt on tbl1.col1 > tprt.col1
+order by tbl1.col1, tprt.col1;
+
+select tbl1.col1, tprt.col1 from tbl1
+inner join tprt on tbl1.col1 = tprt.col1
+order by tbl1.col1, tprt.col1;
+
+-- multiple partitions
+insert into tbl1 values (1001), (1010), (1011);
+explain (analyze, costs off, summary off, timing off)
+select * from tbl1 inner join tprt on tbl1.col1 > tprt.col1;
+
+explain (analyze, costs off, summary off, timing off)
+select * from tbl1 inner join tprt on tbl1.col1 = tprt.col1;
+
+select tbl1.col1, tprt.col1 from tbl1
+inner join tprt on tbl1.col1 > tprt.col1
+order by tbl1.col1, tprt.col1;
+
+select tbl1.col1, tprt.col1 from tbl1
+inner join tprt on tbl1.col1 = tprt.col1
+order by tbl1.col1, tprt.col1;
+
+-- last partition
+delete from tbl1;
+insert into tbl1 values (4400);
+explain (analyze, costs off, summary off, timing off)
+select * from tbl1 join tprt on tbl1.col1 < tprt.col1;
+
+select tbl1.col1, tprt.col1 from tbl1
+inner join tprt on tbl1.col1 < tprt.col1
+order by tbl1.col1, tprt.col1;
+
+-- no matching partition
+delete from tbl1;
+insert into tbl1 values (10000);
+explain (analyze, costs off, summary off, timing off)
+select * from tbl1 join tprt on tbl1.col1 = tprt.col1;
+
+select tbl1.col1, tprt.col1 from tbl1
+inner join tprt on tbl1.col1 = tprt.col1
+order by tbl1.col1, tprt.col1;
+
+drop table tbl1, tprt;
+
+-- test with columns defined in varying orders between each level
+
+create table part_abc (a int not null, b int not null, c int not null) partition by list (a);
+create table part_bac (b int not null, a int not null, c int not null) partition by list (b);
+create table part_cab (c int not null, a int not null, b int not null) partition by list (c);
+create table part_abc_p1 (a int not null, b int not null, c int not null);
+
+alter table part_abc attach partition part_bac for values in(1);
+alter table part_bac attach partition part_cab for values in(2);
+alter table part_cab attach partition part_abc_p1 for values in(3);
+
+prepare part_abc_q1 (int, int, int) as
+select * from part_abc where a = $1 and b = $2 and c = $3;
+
+-- Execute query 5 times to allow choose_custom_plan
+-- to start considering a generic plan.
+execute part_abc_q1 (1, 2, 3);
+execute part_abc_q1 (1, 2, 3);
+execute part_abc_q1 (1, 2, 3);
+execute part_abc_q1 (1, 2, 3);
+execute part_abc_q1 (1, 2, 3);
+
+-- single partition should be scanned.
+explain (analyze, costs off, summary off, timing off) execute part_abc_q1 (1, 2, 3);
+
+deallocate part_abc_q1;
+
+drop table part_abc;
