From 41273c8cfa4d954553ad37de49b4805c893134b9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=B8=80=E6=8C=83?= <yizhi.fzh@alibaba-inc.com>
Date: Mon, 2 Nov 2020 11:49:24 +0800
Subject: [PATCH v1] Adjust Append Path cost model for runtime partition prune
 case.

Currently the cost of Append Path ignored the runtime partition prune
totally which may cause some troubles.  However it looks like we can
estimated how many partitions will live accurately, but luckily the
most common case would be some case  partykey = $1, which can we estimate
well, this patch is aim to handle that part specially.
---
 src/backend/optimizer/path/costsize.c         | 121 +++++-
 src/backend/optimizer/util/pathnode.c         |   2 +-
 src/backend/partitioning/partprune.c          | 367 +++++++++++++++---
 src/include/nodes/nodes.h                     |   1 +
 src/include/nodes/plannodes.h                 |  28 +-
 src/include/optimizer/cost.h                  |   2 +-
 src/include/partitioning/partprune.h          |   6 +
 src/test/regress/expected/partition_prune.out |  54 +--
 8 files changed, 488 insertions(+), 93 deletions(-)

diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index f1dfdc1a4a..4c0fb3b65e 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -92,6 +92,7 @@
 #include "optimizer/planmain.h"
 #include "optimizer/restrictinfo.h"
 #include "parser/parsetree.h"
+#include "partitioning/partprune.h"
 #include "utils/lsyscache.h"
 #include "utils/selfuncs.h"
 #include "utils/spccache.h"
@@ -2036,14 +2037,76 @@ append_nonpartial_cost(List *subpaths, int numpaths, int parallel_workers)
 	return costarr[max_index];
 }
 
+
+/*
+ * find_partitioned_rel_offset
+ *   Given a relid from a subpath, return the offset in partitioned_rel which its
+ * parent in.
+ */
+static int
+find_partitioned_rel_offset(PlannerInfo *root, List *partitioned_rel,  int relid)
+{
+	int i = 0;
+	ListCell *lc;
+	int parent_relid = -1;
+	if (root->append_rel_array[relid] != NULL)
+		parent_relid = root->append_rel_array[relid]->parent_relid;
+	else
+		return -1;
+	foreach(lc,  partitioned_rel)
+	{
+		Relids relids = (Relids)lfirst(lc);
+		if (bms_is_member(parent_relid, relids))
+			return i;
+		i++;
+	}
+	return -1;
+}
+
+
 /*
  * cost_append
  *	  Determines and returns the cost of an Append node.
  */
 void
-cost_append(AppendPath *apath)
+cost_append(AppendPath *apath, PlannerInfo *root)
 {
 	ListCell   *l;
+	RelOptInfo *rel = apath->path.parent;
+	List	*partitioned_rels = apath->partitioned_rels;
+	double	*part_live_ratio = NULL;
+	int len;
+	bool can_runtime_prune;
+
+	/*
+	 * Check if we can have run-time prune, if so, we should reduce
+	 * some cost because of that.
+	 */
+	can_runtime_prune = enable_partition_pruning
+		&& rel->reloptkind == RELOPT_BASEREL
+		&& partitioned_rels != NIL
+		&& apath->subpaths != NIL
+		/* This is just for easy test, should be removed in the final patch */
+		&& enable_geqo;
+
+
+	if (can_runtime_prune)
+	{
+		int i = 0;
+		ListCell	*lc;
+		len = list_length(partitioned_rels);
+		part_live_ratio = palloc0(sizeof(double) * len);
+
+		foreach(lc, partitioned_rels)
+		{
+			part_live_ratio[i++] = est_runtime_part_prune(root,
+														  rel,
+														  (Relids) lfirst(lc),
+														  apath->path.param_info ?
+														  apath->path.param_info->ppi_clauses : NIL);
+
+		}
+	}
 
 	apath->path.startup_cost = 0;
 	apath->path.total_cost = 0;
@@ -2055,14 +2118,17 @@ cost_append(AppendPath *apath)
 	if (!apath->path.parallel_aware)
 	{
 		List	   *pathkeys = apath->path.pathkeys;
-
+		int			offset;
 		if (pathkeys == NIL)
 		{
 			Path	   *subpath = (Path *) linitial(apath->subpaths);
 
 			/*
 			 * For an unordered, non-parallel-aware Append we take the startup
-			 * cost as the startup cost of the first subpath.
+			 * cost as the startup cost of the first subpath when run time partition
+			 * would not happen. However when run time partition prune happens
+			 * we can't know which partition will survived, so still keep the
+			 * startup as the first path's startup_cost.
 			 */
 			apath->path.startup_cost = subpath->startup_cost;
 
@@ -2070,9 +2136,25 @@ cost_append(AppendPath *apath)
 			foreach(l, apath->subpaths)
 			{
 				Path	   *subpath = (Path *) lfirst(l);
-
-				apath->path.rows += subpath->rows;
-				apath->path.total_cost += subpath->total_cost;
+				if (can_runtime_prune)
+				{
+					offset = find_partitioned_rel_offset(root, partitioned_rels, subpath->parent->relid);
+					if (offset != -1)
+					{
+						apath->path.rows += subpath->rows * part_live_ratio[offset];
+						apath->path.total_cost += subpath->total_cost * part_live_ratio[offset];
+					}
+					else
+					{
+						apath->path.rows += subpath->rows;
+						apath->path.total_cost += subpath->total_cost;
+					}
+				}
+				else
+				{
+					apath->path.rows += subpath->rows;
+					apath->path.total_cost += subpath->total_cost;
+				}
 			}
 		}
 		else
@@ -2099,6 +2181,7 @@ cost_append(AppendPath *apath)
 			{
 				Path	   *subpath = (Path *) lfirst(l);
 				Path		sort_path;	/* dummy for result of cost_sort */
+				int			relid = subpath->parent->relid;
 
 				if (!pathkeys_contained_in(pathkeys, subpath->pathkeys))
 				{
@@ -2119,10 +2202,28 @@ cost_append(AppendPath *apath)
 							  apath->limit_tuples);
 					subpath = &sort_path;
 				}
-
-				apath->path.rows += subpath->rows;
-				apath->path.startup_cost += subpath->startup_cost;
-				apath->path.total_cost += subpath->total_cost;
+				if (can_runtime_prune)
+				{
+					offset = find_partitioned_rel_offset(root, partitioned_rels, relid);
+					if (offset != -1)
+					{
+						apath->path.rows += subpath->rows * part_live_ratio[offset];
+						apath->path.total_cost += subpath->total_cost * part_live_ratio[offset];
+						apath->path.startup_cost += subpath->startup_cost * part_live_ratio[offset];
+					}
+					else
+					{
+						apath->path.rows += subpath->rows;
+						apath->path.startup_cost += subpath->startup_cost;
+						apath->path.total_cost += subpath->total_cost;
+					}
+				}
+				else
+				{
+					apath->path.rows += subpath->rows;
+					apath->path.startup_cost += subpath->startup_cost;
+					apath->path.total_cost += subpath->total_cost;
+				}
 			}
 		}
 	}
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 5281a2f998..5bef994eeb 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1315,7 +1315,7 @@ create_append_path(PlannerInfo *root,
 		pathnode->path.pathkeys = child->pathkeys;
 	}
 	else
-		cost_append(pathnode);
+		cost_append(pathnode, root);
 
 	/* If the caller provided a row estimate, override the computed value. */
 	if (rows >= 0)
diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c
index 8e1187e31f..26ed155231 100644
--- a/src/backend/partitioning/partprune.c
+++ b/src/backend/partitioning/partprune.c
@@ -49,6 +49,7 @@
 #include "optimizer/cost.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
+#include "optimizer/restrictinfo.h"
 #include "parser/parsetree.h"
 #include "partitioning/partbounds.h"
 #include "partitioning/partprune.h"
@@ -321,6 +322,44 @@ make_partition_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
 	return pruneinfo;
 }
 
+
+static List *
+translate_appendrel_vars(PlannerInfo *root,
+						 RelOptInfo *topmostrel,
+						 RelOptInfo *subpart,
+						 RelOptInfo **currel,
+						 List **prunequal)
+{
+	if (!*currel)
+	{
+		*currel = subpart;
+		if (!bms_equal(topmostrel->relids, subpart->relids))
+		{
+			int			nappinfos;
+			AppendRelInfo **appinfos = find_appinfos_by_relids(root,
+															   subpart->relids,
+															   &nappinfos);
+
+			*prunequal = (List *) adjust_appendrel_attrs(root, (Node *)
+														 *prunequal,
+														 nappinfos,
+														 appinfos);
+
+			pfree(appinfos);
+		}
+		return *prunequal;
+	}
+	else
+	{
+		return (List *)	adjust_appendrel_attrs_multilevel(root,
+														  (Node *)(*prunequal),
+														  subpart->relids,
+														  (*currel)->relids);
+	}
+	return NIL;
+}
+
+
 /*
  * make_partitionedrel_pruneinfo
  *		Build a List of PartitionedRelPruneInfos, one for each partitioned
@@ -387,53 +426,11 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
 		Assert(rti < root->simple_rel_array_size);
 		relid_subpart_map[rti] = i++;
 
-		/*
-		 * Translate pruning qual, if necessary, for this partition.
-		 *
-		 * The first item in the list is the target partitioned relation.
-		 */
-		if (!targetpart)
-		{
-			targetpart = subpart;
-
-			/*
-			 * The prunequal is presented to us as a qual for 'parentrel'.
-			 * Frequently this rel is the same as targetpart, so we can skip
-			 * an adjust_appendrel_attrs step.  But it might not be, and then
-			 * we have to translate.  We update the prunequal parameter here,
-			 * because in later iterations of the loop for child partitions,
-			 * we want to translate from parent to child variables.
-			 */
-			if (!bms_equal(parentrel->relids, subpart->relids))
-			{
-				int			nappinfos;
-				AppendRelInfo **appinfos = find_appinfos_by_relids(root,
-																   subpart->relids,
-																   &nappinfos);
-
-				prunequal = (List *) adjust_appendrel_attrs(root, (Node *)
-															prunequal,
-															nappinfos,
-															appinfos);
-
-				pfree(appinfos);
-			}
-
-			partprunequal = prunequal;
-		}
-		else
-		{
-			/*
-			 * For sub-partitioned tables the columns may not be in the same
-			 * order as the parent, so we must translate the prunequal to make
-			 * it compatible with this relation.
-			 */
-			partprunequal = (List *)
-				adjust_appendrel_attrs_multilevel(root,
-												  (Node *) prunequal,
-												  subpart->relids,
-												  targetpart->relids);
-		}
+		partprunequal = translate_appendrel_vars(root,
+											  parentrel,
+											  subpart,
+											  &targetpart,
+											  &prunequal);
 
 		/*
 		 * Convert pruning qual to pruning steps.  We may need to do this
@@ -3600,3 +3597,279 @@ partkey_datum_from_expr(PartitionPruneContext *context,
 		*value = ExecEvalExprSwitchContext(exprstate, ectx, isnull);
 	}
 }
+
+/*
+ * get_clause_strategy_num
+ *
+ *	Given a clause and check if it is OK to be used to run time partition prune
+ * estimation. If yes, return the StrategyNumber. It is possible that a clause
+ * can be used as partition prune but can't be used as partition prune estimation
+ * at the current implementation since we have to put more effort on such cases
+ * and we don't know how to estimate it well even so.  So it is still tuned to
+ * support such case or not.
+ */
+static int
+get_clause_strategy_num(RelOptInfo *rel, Expr *clause, Expr *partkey, int partkeyidx)
+{
+	PartitionScheme part_scheme = rel->part_scheme;
+	Oid			partopfamily = part_scheme->partopfamily[partkeyidx],
+		partcoll = part_scheme->partcollation[partkeyidx];
+
+	Expr *leftop, *rightop;
+	Oid			opno,
+		op_lefttype,
+		op_righttype,
+		negator = InvalidOid;
+	int			op_strategy = InvalidStrategy;
+	bool		is_opne_listp = false;
+
+	if (IsA(clause, OpExpr))
+	{
+		OpExpr *opexpr = (OpExpr *)clause;
+		leftop = (Expr *) get_leftop(clause);
+		if (IsA(leftop, RelabelType))
+			leftop = ((RelabelType *) leftop)->arg;
+		rightop = (Expr *) get_rightop(clause);
+		if (IsA(rightop, RelabelType))
+			rightop = ((RelabelType *) rightop)->arg;
+		opno = opexpr->opno;
+
+		/* I don't care about the expr actually */
+		if (!equal(leftop, partkey) && !equal(rightop, partkey))
+			return InvalidStrategy;
+
+		/* collation mismatch */
+		if (!PartCollMatchesExprColl(partcoll, opexpr->inputcollid))
+			return InvalidStrategy;
+
+		if (op_in_opfamily(opno, partopfamily))
+		{
+			get_op_opfamily_properties(opno, partopfamily,
+									   false, &op_strategy,
+									   &op_lefttype, &op_righttype);
+		}
+		else
+		{
+			if (part_scheme->strategy != PARTITION_STRATEGY_LIST)
+				return InvalidStrategy;
+
+			/* See if the negator is equality */
+			negator = get_negator(opno);
+			if (OidIsValid(negator) && op_in_opfamily(negator, partopfamily))
+			{
+				get_op_opfamily_properties(negator, partopfamily, false,
+										   &op_strategy, &op_lefttype,
+										   &op_righttype);
+				if (op_strategy == BTEqualStrategyNumber)
+					is_opne_listp = true;	/* bingo */
+			}
+
+			if (!is_opne_listp)
+				return InvalidStrategy;
+		}
+
+		if (!op_strict(opno))
+			return InvalidStrategy;
+
+		return op_strategy;
+	}
+	else if (IsA(clause, ScalarArrayOpExpr))
+	{
+		/* still tuned */
+	}
+
+	else
+	{
+		/* still tuned */
+	}
+
+	return InvalidStrategy;
+}
+
+
+/*
+ * gen_partitioned_rel_quals
+ *
+ *	We have got the translated quals for a partitioned rel, now we will match it
+ * partkey and return a PartitionedRelPruneQual for run-time partition prune
+ * estimation.
+ */
+static PartitionedRelPruneQual *
+gen_partitioned_rel_quals(RelOptInfo *rel, List *all_quals)
+{
+	int *strategy_nums = NULL;
+	ListCell *lc;
+	PartitionScheme part_scheme = rel->part_scheme;
+	int i;
+	PartitionedRelPruneQual *res;
+	foreach(lc, all_quals)
+	{
+		Expr	*clause = (Expr *) lfirst(lc);
+
+		if (IsA(clause, RestrictInfo))
+			clause = ((RestrictInfo *) clause)->clause;
+
+		for (i = 0;  i < part_scheme->partnatts; i++)
+		{
+			Expr	   *partkey = linitial(rel->partexprs[i]);
+			int op_strategy = get_clause_strategy_num(rel, clause, partkey, i);
+			if (op_strategy != InvalidStrategy)
+			{
+				if (strategy_nums == NULL)
+				{
+					strategy_nums = palloc0(part_scheme->partnatts * sizeof(int));
+				}
+				strategy_nums[i] = op_strategy;
+				break;
+			}
+		}
+	}
+
+	res = makeNode(PartitionedRelPruneQual);
+	res->rtindex = rel->relid;
+	res->strategy_number = strategy_nums;
+	return res;
+}
+
+
+/*
+ * get_lived_partition
+ *	 Given PartitionedRelPruneQual and RelOptInfo, we estimate how many
+ * partition will survive after run time prune. use `double` here just
+ * because it is a estimated value and double is more accurate than int.
+ * All the cases that I made it as 0.75 * total_parts is still tuned. But
+ * if we decide more complex cases to handle, the definition of PartitionedRelPruneQual
+ * probably need an enhancement. Replace the 0.75 with 1 will work same as the current situation.
+ * return live_parts and total_parts rather than a ratio because that
+ * will make the sub-partition estimation easier.
+ */
+static double
+get_lived_partition(struct PlannerInfo *root,
+					PartitionedRelPruneQual *prinfo,
+					double *total_parts,
+					RelOptInfo *rel)
+{
+	/* All the partition key have a BTEqualStrategyNumber is perfect */
+	bool	is_perfect = true;
+	int i;
+	Assert(IS_PARTITIONED_REL(rel));
+
+	/*
+	 * The total partition here is all the lived partition after plan time
+	 * partition prune.  It has some issue in the following example,
+	 * but I think the impact would be OK.
+	 * partition by (partkey1, partkey2), query is where partkey1 = Const.
+	 * In this case, we already have some plan time partition prune for partkey1
+	 * but we will count the partition prune info again here. However for the common
+	 * case like partkey1 = Const1 and partkey2 = Const2. or partkey1 = $1 and
+	 * partkey2 = $2, there is no impact at all.
+	 * And I can't use the total partition from partition definition since the
+	 * we use the calculate the prune_ratio which impacts on the total_cost/rows
+	 * which is from the partitions after the plan time partition prune.
+	 */
+	*total_parts = bms_num_members(rel->all_partrels);
+
+	/* No partkey quals at all. */
+	if (prinfo->strategy_number == NULL)
+		return *total_parts;
+
+	for (i = 0; i < rel->part_scheme->partnatts; i++)
+	{
+		if (prinfo->strategy_number[i] != BTEqualStrategyNumber)
+		{
+			is_perfect = false;
+			break;
+		}
+	}
+
+	/*
+	 * If we replace the 0.75 with 1 below, the behavior will be same as before
+	 * for such case.
+	 */
+	return is_perfect ? 1 : 0.75 * (*total_parts);
+}
+
+
+/*
+ * est_runtime_part_prune_internal
+ *
+ *	Given a list of PartitionedRelPruneQual, we estimate a prune factor
+ * for the partitioned rel.
+ */
+static double
+est_runtime_part_prune_internal(struct PlannerInfo *root,
+								List *prune_quals)
+{
+	ListCell *lc;
+	RelOptInfo *rel = NULL;
+	double total_parent_parts = 0, total_sub_parts = 0,
+		live_parent_parts = 0, live_sub_parts = 0;
+	double parent_live_ratio;
+	if (prune_quals == NIL)
+		return 1;
+	foreach(lc, prune_quals)
+	{
+		PartitionedRelPruneQual *pinfo = lfirst_node(PartitionedRelPruneQual, lc);
+		double live_parts, total_parts;
+		rel = find_base_rel(root, pinfo->rtindex);
+		Assert(IS_PARTITIONED_REL(rel));
+		live_parts = get_lived_partition(root, pinfo, &total_parts, rel);
+
+		if (rel->reloptkind == RELOPT_BASEREL)
+		{
+			Assert(total_parent_parts == 0);
+			Assert(live_parent_parts == 0);
+			total_parent_parts = total_parts;
+			live_parent_parts = live_parts;
+		}
+		else
+		{
+			total_sub_parts += total_parts;
+			live_sub_parts += live_parts;
+		}
+	}
+	if (total_parent_parts)
+	{
+		parent_live_ratio = live_parent_parts * 1.0 / total_parent_parts;		
+	}
+	else
+		parent_live_ratio = 1;
+
+	if (total_sub_parts > 0)
+		return live_sub_parts * 1.0 / total_sub_parts * parent_live_ratio;
+	return parent_live_ratio;
+}
+
+/*
+ * est_runtime_part_prune
+ *	estimate a runtime partition prune ratio for a partitioned_rel.
+ */
+double
+est_runtime_part_prune(struct PlannerInfo *root,
+					   struct RelOptInfo *topmostrel,
+					   Relids partitioned_rel,
+					   List *prmquals)
+{
+	List	*prunequal;
+	RelOptInfo *currel = NULL;
+	List	*rel_prune_quals = NIL;
+	int rti = -1;
+
+	prunequal = extract_actual_clauses(topmostrel->baserestrictinfo, false);
+	prunequal = list_concat(prunequal, prmquals);
+
+	while((rti = bms_next_member(partitioned_rel, rti)) >= 0)
+	{
+		RelOptInfo *subpart = find_base_rel(root, rti);
+		List *translated_quals = translate_appendrel_vars(root,
+														  topmostrel,
+														  subpart,
+														  &currel,
+														  &prunequal);
+		PartitionedRelPruneQual* partqual  = gen_partitioned_rel_quals(subpart,
+																	   translated_quals);
+		rel_prune_quals = lappend(rel_prune_quals, partqual);
+	}
+
+	return est_runtime_part_prune_internal(root, rel_prune_quals);
+}
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 7ddd8c011b..8e1a16a842 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -89,6 +89,7 @@ typedef enum NodeTag
 	T_NestLoopParam,
 	T_PlanRowMark,
 	T_PartitionPruneInfo,
+	T_PartitionedRelPruneQual,
 	T_PartitionedRelPruneInfo,
 	T_PartitionPruneStepOp,
 	T_PartitionPruneStepCombine,
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 7e6b10f86b..554336a7ec 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -1122,9 +1122,35 @@ typedef struct PartitionPruneInfo
 	Bitmapset  *other_subplans;
 } PartitionPruneInfo;
 
+
+
+/*
+ * PartitionedRelPruneQual
+ *   Used to estimate the run time partition prune ratio for a given partitioned
+ * rel's qual.
+ *
+ * strategy_number is a array of `int` for now, indexed by partattrno.  it is OK
+ * for most of the cases,
+ * however it can't represent some more complex cases, like partkey between
+ * $1 and $2. OR (partkey = $1 or partkey = $2).  Since even when know this,
+ * we still can't estimate a good number for that, so I am not sure if it worth
+ * the trouble.  If we need that, we can change `int` to a struct which can include
+ * more info, so still tuned.
+ */
+
+typedef struct PartitionedRelPruneQual
+{
+	NodeTag		type;
+	int			rtindex;
+	int		*strategy_number;
+} PartitionedRelPruneQual;
+
+
 /*
  * PartitionedRelPruneInfo - Details required to allow the executor to prune
- * partitions for a single partitioned table.
+ * partitions for a single partitioned table or allow planner to estimate
+ * how many partition can survive after run time partition prune. In the
+ * later case, only part of the fields are set, see est_runtime_part_prune
  *
  * subplan_map[] and subpart_map[] are indexed by partition index of the
  * partitioned table referenced by 'rtindex', the partition index being the
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index 6141654e47..112308268a 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -107,7 +107,7 @@ extern void cost_incremental_sort(Path *path,
 								  Cost input_startup_cost, Cost input_total_cost,
 								  double input_tuples, int width, Cost comparison_cost, int sort_mem,
 								  double limit_tuples);
-extern void cost_append(AppendPath *path);
+extern void cost_append(AppendPath *path, PlannerInfo *root);
 extern void cost_merge_append(Path *path, PlannerInfo *root,
 							  List *pathkeys, int n_streams,
 							  Cost input_startup_cost, Cost input_total_cost,
diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h
index babdad2c3e..1e16bcedc8 100644
--- a/src/include/partitioning/partprune.h
+++ b/src/include/partitioning/partprune.h
@@ -15,6 +15,7 @@
 #define PARTPRUNE_H
 
 #include "nodes/execnodes.h"
+#include "nodes/pathnodes.h"
 #include "partitioning/partdefs.h"
 
 struct PlannerInfo;				/* avoid including pathnodes.h here */
@@ -77,4 +78,9 @@ extern Bitmapset *prune_append_rel_partitions(struct RelOptInfo *rel);
 extern Bitmapset *get_matching_partitions(PartitionPruneContext *context,
 										  List *pruning_steps);
 
+extern double est_runtime_part_prune(struct PlannerInfo *root,
+									 struct RelOptInfo *topmostrel,
+									 Relids partitioned_rel,
+									 List *prmquals);
+
 #endif							/* PARTPRUNE_H */
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
index c72a6d051f..f71a0bbca9 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -3685,20 +3685,16 @@ alter table listp_12_1 set (parallel_workers = 0);
 -- Ensure that listp_12_2 is not scanned.  (The nested Append is not seen in
 -- the plan as it's pulled in setref.c due to having just a single subnode).
 select explain_parallel_append('select * from listp where a = (select 1);');
-                       explain_parallel_append                        
-----------------------------------------------------------------------
- Gather (actual rows=N loops=N)
-   Workers Planned: 2
-   Params Evaluated: $0
-   Workers Launched: N
+                   explain_parallel_append                    
+--------------------------------------------------------------
+ Append (actual rows=N loops=N)
    InitPlan 1 (returns $0)
      ->  Result (actual rows=N loops=N)
-   ->  Parallel Append (actual rows=N loops=N)
-         ->  Seq Scan on listp_12_1 listp_1 (actual rows=N loops=N)
-               Filter: (a = $0)
-         ->  Parallel Seq Scan on listp_12_2 listp_2 (never executed)
-               Filter: (a = $0)
-(11 rows)
+   ->  Seq Scan on listp_12_1 listp_1 (actual rows=N loops=N)
+         Filter: (a = $0)
+   ->  Seq Scan on listp_12_2 listp_2 (never executed)
+         Filter: (a = $0)
+(7 rows)
 
 -- Like the above but throw some more complexity at the planner by adding
 -- a UNION ALL.  We expect both sides of the union not to scan the
@@ -3707,32 +3703,24 @@ select explain_parallel_append(
 'select * from listp where a = (select 1)
   union all
 select * from listp where a = (select 2);');
-                              explain_parallel_append                              
------------------------------------------------------------------------------------
+                      explain_parallel_append                       
+--------------------------------------------------------------------
  Append (actual rows=N loops=N)
-   ->  Gather (actual rows=N loops=N)
-         Workers Planned: 2
-         Params Evaluated: $0
-         Workers Launched: N
+   ->  Append (actual rows=N loops=N)
          InitPlan 1 (returns $0)
            ->  Result (actual rows=N loops=N)
-         ->  Parallel Append (actual rows=N loops=N)
-               ->  Seq Scan on listp_12_1 listp_1 (actual rows=N loops=N)
-                     Filter: (a = $0)
-               ->  Parallel Seq Scan on listp_12_2 listp_2 (never executed)
-                     Filter: (a = $0)
-   ->  Gather (actual rows=N loops=N)
-         Workers Planned: 2
-         Params Evaluated: $1
-         Workers Launched: N
+         ->  Seq Scan on listp_12_1 listp_1 (actual rows=N loops=N)
+               Filter: (a = $0)
+         ->  Seq Scan on listp_12_2 listp_2 (never executed)
+               Filter: (a = $0)
+   ->  Append (actual rows=N loops=N)
          InitPlan 2 (returns $1)
            ->  Result (actual rows=N loops=N)
-         ->  Parallel Append (actual rows=N loops=N)
-               ->  Seq Scan on listp_12_1 listp_4 (never executed)
-                     Filter: (a = $1)
-               ->  Parallel Seq Scan on listp_12_2 listp_5 (actual rows=N loops=N)
-                     Filter: (a = $1)
-(23 rows)
+         ->  Seq Scan on listp_12_1 listp_4 (never executed)
+               Filter: (a = $1)
+         ->  Seq Scan on listp_12_2 listp_5 (actual rows=N loops=N)
+               Filter: (a = $1)
+(15 rows)
 
 drop table listp;
 reset parallel_tuple_cost;
-- 
2.21.0

