From 3486e323fa71a5e3d653ebcef440bb1c5b32e9f6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=B8=80=E6=8C=83?= <yizhi.fzh@alibaba-inc.com>
Date: Sun, 24 Jan 2021 18:00:06 +0800
Subject: [PATCH v1 2/2] Build some implied pruning quals to extend the usecase
 of Planning

time partition pruning and init partition pruning.
---
 src/backend/optimizer/plan/createplan.c       |   3 +
 src/backend/optimizer/util/inherit.c          |  11 +-
 src/backend/partitioning/partprune.c          | 295 +++++++++++++-
 src/include/partitioning/partprune.h          |   4 +-
 .../regress/expected/partition_aggregate.out  |  21 +-
 src/test/regress/expected/partition_join.out  |  18 +-
 src/test/regress/expected/partition_prune.out | 383 +++++++++++++++---
 src/test/regress/sql/partition_prune.sql      | 110 +++++
 8 files changed, 759 insertions(+), 86 deletions(-)

diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 25d4750ca6..692e33e296 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -1245,6 +1245,9 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
 			prunequal = list_concat(prunequal, prmquals);
 		}
 
+		prunequal = list_concat(prunequal,
+								build_implied_pruning_quals(root, best_path->path.parent));
+
 		if (prunequal != NIL)
 			partpruneinfo =
 				make_partition_pruneinfo(root, rel,
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index be1c9ddd96..b816901cff 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -327,22 +327,13 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 	 * that survive pruning.  Below, we will initialize child objects for the
 	 * surviving partitions.
 	 */
-	live_parts = prune_append_rel_partitions(relinfo);
+	live_parts = prune_append_rel_partitions(root, relinfo);
 
 	/* Expand simple_rel_array and friends to hold child objects. */
 	num_live_parts = bms_num_members(live_parts);
 	if (num_live_parts > 0)
 		expand_planner_arrays(root, num_live_parts);
 
-	/*
-	 * We also store partition RelOptInfo pointers in the parent relation.
-	 * Since we're palloc0'ing, slots corresponding to pruned partitions will
-	 * contain NULL.
-	 */
-	Assert(relinfo->part_rels == NULL);
-	relinfo->part_rels = (RelOptInfo **)
-		palloc0(relinfo->nparts * sizeof(RelOptInfo *));
-
 	/*
 	 * Create a child RTE for each live partition.  Note that unlike
 	 * traditional inheritance, we don't need a child RTE for the partitioned
diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c
index fac921eea5..5ac1815719 100644
--- a/src/backend/partitioning/partprune.c
+++ b/src/backend/partitioning/partprune.c
@@ -48,7 +48,9 @@
 #include "optimizer/appendinfo.h"
 #include "optimizer/cost.h"
 #include "optimizer/optimizer.h"
+#include "optimizer/restrictinfo.h"
 #include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
 #include "parser/parsetree.h"
 #include "partitioning/partbounds.h"
 #include "partitioning/partprune.h"
@@ -652,9 +654,9 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, PartClauseTarget target,
  * Callers must ensure that 'rel' is a partitioned table.
  */
 Bitmapset *
-prune_append_rel_partitions(RelOptInfo *rel)
+prune_append_rel_partitions(PlannerInfo *root, RelOptInfo *rel)
 {
-	List	   *clauses = rel->baserestrictinfo;
+	List	   *clauses = extract_actual_clauses(rel->baserestrictinfo, false);
 	List	   *pruning_steps;
 	GeneratePruningStepsContext gcontext;
 	PartitionPruneContext context;
@@ -665,6 +667,19 @@ prune_append_rel_partitions(RelOptInfo *rel)
 	if (rel->nparts == 0)
 		return NULL;
 
+	/*
+	 * We also store partition RelOptInfo pointers in the parent relation.
+	 * Since we're palloc0'ing, slots corresponding to pruned partitions will
+	 * contain NULL. We have to set part_rels here so that the IS_PARTITIONED_REL
+	 * can be true sooner.  gen_extra_qual_for_pruning depends on it.
+	 */
+	Assert(rel->part_rels == NULL);
+	rel->part_rels = (RelOptInfo **)
+		palloc0(rel->nparts * sizeof(RelOptInfo *));
+
+	if (enable_partition_pruning)
+		clauses = list_concat(clauses, build_implied_pruning_quals(root, rel));
+
 	/*
 	 * If pruning is disabled or if there are no clauses to prune with, return
 	 * all partitions.
@@ -3600,3 +3615,279 @@ partkey_datum_from_expr(PartitionPruneContext *context,
 		*value = ExecEvalExprSwitchContext(exprstate, ectx, isnull);
 	}
 }
+
+
+/*
+ * expr_in_one_side
+ * 	return true if the expr is in one side of the clause.
+ */
+static bool
+expr_in_one_side(Expr *clause, Expr *expr)
+{
+	ListCell	*lc;
+	if (IsA(clause, OpExpr))
+	{
+		OpExpr	*opexpr = (OpExpr *)clause;
+		foreach(lc, opexpr->args)
+		{
+			Expr *lexpr = lfirst(lc);
+			while(IsA(lexpr, RelabelType))
+				lexpr = ((RelabelType *)lexpr)->arg;
+
+			if (equal(lexpr, expr))
+				return true;
+		}
+	}
+	else if (IsA(clause, BoolExpr))
+	{
+		BoolExpr	*bool_expr = (BoolExpr *)clause;
+		switch(bool_expr->boolop)
+		{
+			case OR_EXPR:
+				foreach(lc, bool_expr->args)
+				{
+					if (!expr_in_one_side(lfirst(lc), expr))
+						return false;
+				}
+				return true;
+			case AND_EXPR:
+				foreach(lc, bool_expr->args)
+				{
+					if (expr_in_one_side(lfirst(lc), expr))
+						return false;
+				}
+				return false;
+		    case NOT_EXPR:
+				return false;
+		}
+	}
+	else if (IsA(clause, ScalarArrayOpExpr))
+	{
+		ScalarArrayOpExpr *scalar_array_opexpr = (ScalarArrayOpExpr *) clause;
+		Expr	*leftop = linitial(scalar_array_opexpr->args);
+		while (IsA(leftop, RelabelType))
+			leftop = ((RelabelType *)leftop)->arg;
+		return equal(leftop, expr);
+	}
+	return false;
+}
+
+
+/*
+ * build_implied_quals
+ * 	Suppose this_var = other_var, so if this_var appears in rel->baserestrictinfo,
+ * we will build another RestrictInfo with this_var is replaced with other_var
+ * for other_rel.
+ */
+static List *
+build_implied_quals(RelOptInfo *rel,  Var *this_var,  Var *other_var)
+{
+	List	*res = NIL;
+	ListCell	*lc;
+	foreach(lc, rel->baserestrictinfo)
+	{
+		RestrictInfo *rinfo = lfirst(lc);
+		RestrictInfo *rinfo_cpy;
+		Expr *clause;
+
+		if (rinfo->pseudoconstant)
+			continue;
+
+		clause = rinfo->clause;
+		if (!expr_in_one_side(clause, (Expr *)this_var))
+			continue;
+
+		rinfo_cpy = copyObject(rinfo);
+		ChangeVarNodesExtend((Node *)rinfo_cpy->clause, this_var->varno,
+							 other_var->varno, other_var->varattno, 0);
+		res = lappend(res, rinfo_cpy);
+	}
+	return res;
+}
+
+
+/*
+ * build_implied_pruning_qual_from_eclass_join
+ */
+static List*
+build_implied_pruning_qual_from_eclass_join(PlannerInfo *root, RelOptInfo *rel)
+{
+    List	*res = NIL;
+    Bitmapset 	*matching_ecs = get_eclass_indexes_for_relids(root, rel->relids);
+    int i = -1;
+	while ((i = bms_next_member(matching_ecs, i)) >= 0)
+	{
+		EquivalenceClass *cur_ec = (EquivalenceClass *) list_nth(root->eq_classes, i);
+		EquivalenceMember *cur_em;
+		List *other_exprs = NIL;
+		Var	*self_expr = NULL;
+		ListCell	*lc;
+
+		if (cur_ec->ec_has_const || list_length(cur_ec->ec_members) <= 1)
+			continue;
+
+		foreach(lc, cur_ec->ec_members)
+		{
+			int relid;
+			Expr *expr;
+			cur_em = (EquivalenceMember *) lfirst(lc);
+
+			if (cur_em->em_is_child)
+				/* no need to handle */
+				continue;
+
+			if (!bms_is_empty(cur_em->em_nullable_relids))
+			{
+				/*
+                 * XXX: Not sure what em_nullable_relids is, even t1 left join t2
+                 * on t1.a = t2.a, the em_nullable_relids is empty. Just ignore
+                 * it for now for safety.
+                 */
+				continue;
+			}
+
+			if (!bms_get_singleton_member(cur_em->em_relids, &relid))
+			{
+				/* Not clear how to handle, and probably not worth the trouble. */
+				continue;
+			}
+			expr = cur_em->em_expr;
+
+			while (IsA(expr, RelabelType))
+				expr = ((RelabelType *)cur_em->em_expr)->arg;
+
+			if (!IsA(expr, Var))
+				/* See comments in build_implied_pruning_quals */
+				continue;
+
+			if (bms_equal(rel->relids, cur_em->em_relids))
+			{
+				if (match_expr_to_partition_keys(expr, rel, false) == -1)
+					/* Not a partition key, useless for us. */
+					continue;
+
+				self_expr = (Var *)expr;
+			}
+			else
+				other_exprs = lappend(other_exprs, expr);
+		}
+
+		if (!self_expr)
+			return NIL;
+
+		foreach(lc, other_exprs)
+		{
+			Var *other_var = lfirst_node(Var, lc);
+			RelOptInfo *other_rel = root->simple_rel_array[other_var->varno];
+			/* Now we will check if the other_var is used in other_rel->restrictinfo */
+			res = list_concat(res, build_implied_quals(other_rel, other_var, self_expr));
+		}
+    }
+    return res;
+}
+
+
+/*
+ * build_implied_pruning_quals_from_joininfo
+ *
+ * 	Just build pruning_quals if rel.partkey = other_rel.var.  Not including
+ *  other operators except the '='.
+ */
+static List *
+build_implied_pruning_quals_from_joininfo(PlannerInfo *root, RelOptInfo *rel)
+{
+    List	*res = NIL;
+	ListCell	*lc;
+	PartitionScheme part_scheme;
+	Assert(IS_PARTITIONED_REL(rel));
+
+	part_scheme = rel->part_scheme;
+	foreach(lc, rel->joininfo)
+	{
+		RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
+		OpExpr	*opexpr;
+		Expr *leftop, *rightop;
+		Var *part_key_var, *other_var;
+		int part_key_index = -1;
+		Oid	partopfamily, op_lefttype, op_righttype;
+		int op_strategy = InvalidStrategy;
+
+		if(bms_is_subset(rel->relids, rinfo->outer_relids))
+			/*
+			 * We can't build quals for a outer relation from the
+			 * other side's baserestictinfo
+			 */
+			continue;
+
+		if (!IsA(rinfo->clause, OpExpr))
+			/* Unclear how to handle it and probably not common */
+			continue;
+
+		opexpr = (OpExpr *) rinfo->clause;
+
+		leftop = (Expr *) get_leftop(opexpr);
+		while (IsA(leftop, RelabelType))
+			leftop = ((RelabelType *) leftop)->arg;
+		rightop = (Expr *) get_rightop(opexpr);
+		while (IsA(rightop, RelabelType))
+			rightop = ((RelabelType *) rightop)->arg;
+
+		if (!IsA(leftop, Var) || !IsA(rightop, Var))
+			continue;
+
+		if ((part_key_index = match_expr_to_partition_keys(leftop, rel, true)) != -1)
+		{
+			part_key_var = (Var *)leftop;
+			other_var = (Var *) rightop;
+		}
+		else if ((part_key_index = match_expr_to_partition_keys(rightop, rel, true)) != -1)
+		{
+			part_key_var = (Var *)rightop;
+			other_var = (Var *)leftop;
+		}
+		else
+			continue;
+
+		partopfamily = part_scheme->partopfamily[part_key_index];
+
+		get_op_opfamily_properties(opexpr->opno, partopfamily, false,
+								   &op_strategy, &op_lefttype, &op_righttype);
+
+		if (op_strategy != BTEqualStrategyNumber)
+			continue;
+
+		res = list_concat(res,
+			build_implied_quals(root->simple_rel_array[other_var->varno],
+								other_var, part_key_var));
+	}
+    return res;
+}
+
+
+/*
+ * build_implied_pruning_quals
+ *
+ *  build implied pruning quals for a partitioned rel, The general idea is
+ * if p.partkey = t.a and t.a appearing in t's baserestrictinfo, we can
+ * add another similar restrictinfo for p as pruning clauses. To make
+ * the copied and modified RestrictInfo easy to build, we only support
+ * partkey is Var and the other expr is Var as well (if so, we are able to
+ * build it with ChangeVarNodesExtend).
+ */
+List *
+build_implied_pruning_quals(PlannerInfo *root, RelOptInfo *rel)
+{
+    List	*res = NIL;
+
+    if (!IS_PARTITIONED_REL(rel))
+        return res;
+
+    if (rel->has_eclass_joins)
+        res = list_concat(res,
+                          build_implied_pruning_qual_from_eclass_join(root, rel));
+
+    if (rel->joininfo)
+        res = list_concat(res,
+                          build_implied_pruning_quals_from_joininfo(root, rel));
+    return res;
+}
diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h
index 1f9544c0dc..550b3ee169 100644
--- a/src/include/partitioning/partprune.h
+++ b/src/include/partitioning/partprune.h
@@ -68,12 +68,14 @@ typedef struct PartitionPruneContext
 #define PruneCxtStateIdx(partnatts, step_id, keyno) \
 	((partnatts) * (step_id) + (keyno))
 
+extern List *build_implied_pruning_quals(struct PlannerInfo *root, struct RelOptInfo *rel);
 extern PartitionPruneInfo *make_partition_pruneinfo(struct PlannerInfo *root,
 													struct RelOptInfo *parentrel,
 													List *subpaths,
 													List *partitioned_rels,
 													List *prunequal);
-extern Bitmapset *prune_append_rel_partitions(struct RelOptInfo *rel);
+extern Bitmapset *prune_append_rel_partitions(struct PlannerInfo *root,
+											  struct RelOptInfo *rel);
 extern Bitmapset *get_matching_partitions(PartitionPruneContext *context,
 										  List *pruning_steps);
 
diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out
index dfa4b036b5..97820d1673 100644
--- a/src/test/regress/expected/partition_aggregate.out
+++ b/src/test/regress/expected/partition_aggregate.out
@@ -727,12 +727,12 @@ SELECT a.x, sum(b.x) FROM pagg_tab1 a FULL OUTER JOIN pagg_tab2 b ON a.x = b.y G
 -- But right now we are unable to do partitionwise join in this case.
 EXPLAIN (COSTS OFF)
 SELECT a.x, b.y, count(*) FROM (SELECT * FROM pagg_tab1 WHERE x < 20) a LEFT JOIN (SELECT * FROM pagg_tab2 WHERE y > 10) b ON a.x = b.y WHERE a.x > 5 or b.y < 20  GROUP BY a.x, b.y ORDER BY 1, 2;
-                             QUERY PLAN                             
---------------------------------------------------------------------
- Sort
-   Sort Key: pagg_tab1.x, pagg_tab2.y
-   ->  HashAggregate
-         Group Key: pagg_tab1.x, pagg_tab2.y
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ GroupAggregate
+   Group Key: pagg_tab1.x, pagg_tab2.y
+   ->  Sort
+         Sort Key: pagg_tab1.x, pagg_tab2.y
          ->  Hash Left Join
                Hash Cond: (pagg_tab1.x = pagg_tab2.y)
                Filter: ((pagg_tab1.x > 5) OR (pagg_tab2.y < 20))
@@ -742,12 +742,9 @@ SELECT a.x, b.y, count(*) FROM (SELECT * FROM pagg_tab1 WHERE x < 20) a LEFT JOI
                      ->  Seq Scan on pagg_tab1_p2 pagg_tab1_2
                            Filter: (x < 20)
                ->  Hash
-                     ->  Append
-                           ->  Seq Scan on pagg_tab2_p2 pagg_tab2_1
-                                 Filter: (y > 10)
-                           ->  Seq Scan on pagg_tab2_p3 pagg_tab2_2
-                                 Filter: (y > 10)
-(18 rows)
+                     ->  Seq Scan on pagg_tab2_p2 pagg_tab2
+                           Filter: (y > 10)
+(15 rows)
 
 SELECT a.x, b.y, count(*) FROM (SELECT * FROM pagg_tab1 WHERE x < 20) a LEFT JOIN (SELECT * FROM pagg_tab2 WHERE y > 10) b ON a.x = b.y WHERE a.x > 5 or b.y < 20  GROUP BY a.x, b.y ORDER BY 1, 2;
  x  | y  | count 
diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out
index 0057f41caa..7286b8a72a 100644
--- a/src/test/regress/expected/partition_join.out
+++ b/src/test/regress/expected/partition_join.out
@@ -214,18 +214,15 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JO
    Sort Key: prt1.a, prt2.b
    ->  Hash Right Join
          Hash Cond: (prt2.b = prt1.a)
-         ->  Append
-               ->  Seq Scan on prt2_p2 prt2_1
-                     Filter: (b > 250)
-               ->  Seq Scan on prt2_p3 prt2_2
-                     Filter: (b > 250)
+         ->  Seq Scan on prt2_p2 prt2
+               Filter: (b > 250)
          ->  Hash
                ->  Append
                      ->  Seq Scan on prt1_p1 prt1_1
                            Filter: ((a < 450) AND (b = 0))
                      ->  Seq Scan on prt1_p2 prt1_2
                            Filter: ((a < 450) AND (b = 0))
-(15 rows)
+(12 rows)
 
 SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b;
   a  |  c   |  b  |  c   
@@ -1186,12 +1183,9 @@ SELECT t1.a, t2.b FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT *
                            Filter: ((a < 450) AND (b = 0))
          ->  Sort
                Sort Key: prt2.b
-               ->  Append
-                     ->  Seq Scan on prt2_p2 prt2_1
-                           Filter: (b > 250)
-                     ->  Seq Scan on prt2_p3 prt2_2
-                           Filter: (b > 250)
-(18 rows)
+               ->  Seq Scan on prt2_p2 prt2
+                     Filter: (b > 250)
+(15 rows)
 
 SELECT t1.a, t2.b FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b;
   a  |  b  
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
index c72a6d051f..fcbc64226e 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -2075,19 +2075,7 @@ select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on
                                  Index Cond: (a = a.a)
                            ->  Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (actual rows=N loops=N)
                                  Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (never executed)
-                                 Index Cond: (a = a.a)
-(27 rows)
+(15 rows)
 
 -- Ensure the same partitions are pruned when we make the nested loop
 -- parameter an Expr rather than a plain Param.
@@ -2142,19 +2130,13 @@ select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on
                                  Index Cond: (a = a.a)
                            ->  Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (actual rows=N loops=N)
                                  Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed)
+                           ->  Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_4 (actual rows=N loops=N)
                                  Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed)
+                           ->  Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_5 (actual rows=N loops=N)
                                  Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed)
+                           ->  Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_6 (actual rows=N loops=N)
                                  Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (actual rows=N loops=N)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (actual rows=N loops=N)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (actual rows=N loops=N)
-                                 Index Cond: (a = a.a)
-(27 rows)
+(21 rows)
 
 select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(1, 0, 0)');
                                         explain_parallel_append                                         
@@ -2175,19 +2157,7 @@ select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on
                                  Index Cond: (a = a.a)
                            ->  Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (actual rows=N loops=N)
                                  Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (never executed)
-                                 Index Cond: (a = a.a)
-(28 rows)
+(16 rows)
 
 delete from lprt_a where a = 1;
 select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(1, 0, 0)');
@@ -2209,19 +2179,7 @@ select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on
                                  Index Cond: (a = a.a)
                            ->  Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (never executed)
                                  Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (never executed)
-                                 Index Cond: (a = a.a)
-(28 rows)
+(16 rows)
 
 reset enable_hashjoin;
 reset enable_mergejoin;
@@ -3874,3 +3832,330 @@ explain (costs off) select * from hp_contradict_test where a === 1 and b === 1 a
 drop table hp_contradict_test;
 drop operator class part_test_int4_ops2 using hash;
 drop operator ===(int4, int4);
+create table p (a int, b int, c character varying(8)) partition by list(c);
+create table p1  partition of p for values in ('000001');
+create table p2  partition of p for values in ('000002');
+create table p3  partition of p for values in ('000003');
+create table q (a int, c character varying(8), b int) partition by list(c);
+create table q1  partition of q for values in ('000001');
+create table q2  partition of q for values in ('000002');
+create table q3  partition of q for values in ('000003');
+create table o (a int, c character varying(8), b int) partition by list(c);
+create table o1  partition of o for values in ('000001');
+create table o2 partition of o for values in ('000002');
+create table o3  partition of o for values in ('000003');
+analyze p;
+analyze q;
+analyze o;
+set plan_cache_mode to force_generic_plan ;
+-- inner join
+prepare s as select * from p, q where (q.c = $1 or q.c = $2) and p.c = q.c;
+explain (costs off) execute s('000001', '000002');
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Nested Loop
+   Join Filter: ((p.c)::text = (q.c)::text)
+   ->  Append
+         Subplans Removed: 1
+         ->  Seq Scan on p1 p_1
+         ->  Seq Scan on p2 p_2
+   ->  Append
+         Subplans Removed: 1
+         ->  Seq Scan on q1 q_1
+               Filter: (((c)::text = $1) OR ((c)::text = $2))
+         ->  Seq Scan on q2 q_2
+               Filter: (((c)::text = $1) OR ((c)::text = $2))
+(12 rows)
+
+prepare s2 as select * from p inner join q on (q.c = $1 or q.c = $2) and p.c = q.c;
+explain (costs off) execute s2('000001', '000002');
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Nested Loop
+   Join Filter: ((p.c)::text = (q.c)::text)
+   ->  Append
+         Subplans Removed: 1
+         ->  Seq Scan on p1 p_1
+         ->  Seq Scan on p2 p_2
+   ->  Append
+         Subplans Removed: 1
+         ->  Seq Scan on q1 q_1
+               Filter: (((c)::text = $1) OR ((c)::text = $2))
+         ->  Seq Scan on q2 q_2
+               Filter: (((c)::text = $1) OR ((c)::text = $2))
+(12 rows)
+
+-- left join
+-- q can have the init partititon prune, but p can't since it is the outer join.
+prepare s3 as select * from p left join q on (q.c = $1 or q.c = $2) and p.c = q.c;
+explain (costs off) execute s3('000001', '000002');
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Nested Loop Left Join
+   Join Filter: ((p.c)::text = (q.c)::text)
+   ->  Append
+         ->  Seq Scan on p1 p_1
+         ->  Seq Scan on p2 p_2
+         ->  Seq Scan on p3 p_3
+   ->  Append
+         Subplans Removed: 1
+         ->  Seq Scan on q1 q_1
+               Filter: (((c)::text = $1) OR ((c)::text = $2))
+         ->  Seq Scan on q2 q_2
+               Filter: (((c)::text = $1) OR ((c)::text = $2))
+(12 rows)
+
+-- p can't do the init partition prune since it is the outer join. so p can't as well.
+prepare s4 as select * from p left join q on (p.c = $1 or p.c = $2) and p.c = q.c;
+explain (costs off) execute s4('000001', '000002');
+                                         QUERY PLAN                                          
+---------------------------------------------------------------------------------------------
+ Nested Loop Left Join
+   Join Filter: (((p.c)::text = (q.c)::text) AND (((p.c)::text = $1) OR ((p.c)::text = $2)))
+   ->  Append
+         ->  Seq Scan on p1 p_1
+         ->  Seq Scan on p2 p_2
+         ->  Seq Scan on p3 p_3
+   ->  Append
+         ->  Seq Scan on q1 q_1
+         ->  Seq Scan on q2 q_2
+         ->  Seq Scan on q3 q_3
+(10 rows)
+
+-- left join is reduced to inner join, so both p and q can do the pruning.
+prepare s5 as select * from p left join q on p.c = q.c where (q.c = $1 or q.c = $2);
+explain (costs off) execute s5('000001', '000002');
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Nested Loop
+   Join Filter: ((p.c)::text = (q.c)::text)
+   ->  Append
+         Subplans Removed: 1
+         ->  Seq Scan on p1 p_1
+         ->  Seq Scan on p2 p_2
+   ->  Append
+         Subplans Removed: 1
+         ->  Seq Scan on q1 q_1
+               Filter: (((c)::text = $1) OR ((c)::text = $2))
+         ->  Seq Scan on q2 q_2
+               Filter: (((c)::text = $1) OR ((c)::text = $2))
+(12 rows)
+
+-- left join can't be reduced to inner join, but p.c is not in ON clause so q can.
+-- and q is right table in left join, so it can as well.
+prepare s6 as select * from p left join q on p.c = q.c where (p.c = $1 or p.c = $2);
+explain (costs off) execute s6('000001', '000002');
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Nested Loop Left Join
+   Join Filter: ((p.c)::text = (q.c)::text)
+   ->  Append
+         Subplans Removed: 1
+         ->  Seq Scan on p1 p_1
+               Filter: (((c)::text = $1) OR ((c)::text = $2))
+         ->  Seq Scan on p2 p_2
+               Filter: (((c)::text = $1) OR ((c)::text = $2))
+   ->  Append
+         Subplans Removed: 1
+         ->  Seq Scan on q1 q_1
+         ->  Seq Scan on q2 q_2
+(12 rows)
+
+-- full outer join.
+-- both p & q can't do the init partition prune.
+prepare s7 as select * from p full join q on p.c = q.c and (p.c = $1 or p.c = $2);
+explain (costs off) execute s7('000001', '000002');
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Hash Full Join
+   Hash Cond: ((p.c)::text = (q.c)::text)
+   Join Filter: (((p.c)::text = $1) OR ((p.c)::text = $2))
+   ->  Append
+         ->  Seq Scan on p1 p_1
+         ->  Seq Scan on p2 p_2
+         ->  Seq Scan on p3 p_3
+   ->  Hash
+         ->  Append
+               ->  Seq Scan on q1 q_1
+               ->  Seq Scan on q2 q_2
+               ->  Seq Scan on q3 q_3
+(12 rows)
+
+-- can be reduced to p left join q, so both can.
+prepare s8 as select * from p full join q on p.c = q.c where (p.c = $1 or p.c = $2);
+explain (costs off) execute s8('000001', '000002');
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Nested Loop Left Join
+   Join Filter: ((p.c)::text = (q.c)::text)
+   ->  Append
+         Subplans Removed: 1
+         ->  Seq Scan on p1 p_1
+               Filter: (((c)::text = $1) OR ((c)::text = $2))
+         ->  Seq Scan on p2 p_2
+               Filter: (((c)::text = $1) OR ((c)::text = $2))
+   ->  Append
+         Subplans Removed: 1
+         ->  Seq Scan on q1 q_1
+         ->  Seq Scan on q2 q_2
+(12 rows)
+
+deallocate s;
+deallocate s2;
+deallocate s3;
+deallocate s4;
+deallocate s5;
+deallocate s6;
+deallocate s7;
+deallocate s8;
+set plan_cache_mode to force_custom_plan;
+-- inner join
+prepare s as select * from p, q where (q.c = $1 or q.c = $2) and p.c = q.c;
+explain (costs off) execute s('000001', '000002');
+                                      QUERY PLAN                                      
+--------------------------------------------------------------------------------------
+ Nested Loop
+   Join Filter: ((p.c)::text = (q.c)::text)
+   ->  Append
+         ->  Seq Scan on p1 p_1
+         ->  Seq Scan on p2 p_2
+   ->  Append
+         ->  Seq Scan on q1 q_1
+               Filter: (((c)::text = '000001'::text) OR ((c)::text = '000002'::text))
+         ->  Seq Scan on q2 q_2
+               Filter: (((c)::text = '000001'::text) OR ((c)::text = '000002'::text))
+(10 rows)
+
+prepare s2 as select * from p inner join q on (q.c = $1 or q.c = $2) and p.c = q.c;
+explain (costs off) execute s2('000001', '000002');
+                                      QUERY PLAN                                      
+--------------------------------------------------------------------------------------
+ Nested Loop
+   Join Filter: ((p.c)::text = (q.c)::text)
+   ->  Append
+         ->  Seq Scan on p1 p_1
+         ->  Seq Scan on p2 p_2
+   ->  Append
+         ->  Seq Scan on q1 q_1
+               Filter: (((c)::text = '000001'::text) OR ((c)::text = '000002'::text))
+         ->  Seq Scan on q2 q_2
+               Filter: (((c)::text = '000001'::text) OR ((c)::text = '000002'::text))
+(10 rows)
+
+-- left join
+-- q can have the planning partititon prune, but p can't since it is the outer join.
+prepare s3 as select * from p left join q on (q.c = $1 or q.c = $2) and p.c = q.c;
+explain (costs off) execute s3('000001', '000002');
+                                      QUERY PLAN                                      
+--------------------------------------------------------------------------------------
+ Nested Loop Left Join
+   Join Filter: ((p.c)::text = (q.c)::text)
+   ->  Append
+         ->  Seq Scan on p1 p_1
+         ->  Seq Scan on p2 p_2
+         ->  Seq Scan on p3 p_3
+   ->  Append
+         ->  Seq Scan on q1 q_1
+               Filter: (((c)::text = '000001'::text) OR ((c)::text = '000002'::text))
+         ->  Seq Scan on q2 q_2
+               Filter: (((c)::text = '000001'::text) OR ((c)::text = '000002'::text))
+(11 rows)
+
+-- p can't do the planning partition prune since it is the outer join. so p can't as well.
+prepare s4 as select * from p left join q on (p.c = $1 or p.c = $2) and p.c = q.c;
+explain (costs off) execute s4('000001', '000002');
+                                                     QUERY PLAN                                                      
+---------------------------------------------------------------------------------------------------------------------
+ Nested Loop Left Join
+   Join Filter: (((p.c)::text = (q.c)::text) AND (((p.c)::text = '000001'::text) OR ((p.c)::text = '000002'::text)))
+   ->  Append
+         ->  Seq Scan on p1 p_1
+         ->  Seq Scan on p2 p_2
+         ->  Seq Scan on p3 p_3
+   ->  Append
+         ->  Seq Scan on q1 q_1
+         ->  Seq Scan on q2 q_2
+         ->  Seq Scan on q3 q_3
+(10 rows)
+
+-- left join is reduced to inner join, so both p and q can do the pruning.
+prepare s5 as select * from p left join q on p.c = q.c where (q.c = $1 or q.c = $2);
+explain (costs off) execute s5('000001', '000002');
+                                      QUERY PLAN                                      
+--------------------------------------------------------------------------------------
+ Nested Loop
+   Join Filter: ((p.c)::text = (q.c)::text)
+   ->  Append
+         ->  Seq Scan on p1 p_1
+         ->  Seq Scan on p2 p_2
+   ->  Append
+         ->  Seq Scan on q1 q_1
+               Filter: (((c)::text = '000001'::text) OR ((c)::text = '000002'::text))
+         ->  Seq Scan on q2 q_2
+               Filter: (((c)::text = '000001'::text) OR ((c)::text = '000002'::text))
+(10 rows)
+
+-- left join can't be reduced to inner join, but p.c is not in ON clause so q can.
+-- and q is right table in left join, so it can as well.
+prepare s6 as select * from p left join q on p.c = q.c where (p.c = $1 or p.c = $2);
+explain (costs off) execute s6('000001', '000002');
+                                      QUERY PLAN                                      
+--------------------------------------------------------------------------------------
+ Nested Loop Left Join
+   Join Filter: ((p.c)::text = (q.c)::text)
+   ->  Append
+         ->  Seq Scan on p1 p_1
+               Filter: (((c)::text = '000001'::text) OR ((c)::text = '000002'::text))
+         ->  Seq Scan on p2 p_2
+               Filter: (((c)::text = '000001'::text) OR ((c)::text = '000002'::text))
+   ->  Append
+         ->  Seq Scan on q1 q_1
+         ->  Seq Scan on q2 q_2
+(10 rows)
+
+-- full outer join.
+-- both p & q can't do the planning partition prune.
+prepare s7 as select * from p full join q on p.c = q.c and (p.c = $1 or p.c = $2);
+explain (costs off) execute s7('000001', '000002');
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Hash Full Join
+   Hash Cond: ((p.c)::text = (q.c)::text)
+   Join Filter: (((p.c)::text = '000001'::text) OR ((p.c)::text = '000002'::text))
+   ->  Append
+         ->  Seq Scan on p1 p_1
+         ->  Seq Scan on p2 p_2
+         ->  Seq Scan on p3 p_3
+   ->  Hash
+         ->  Append
+               ->  Seq Scan on q1 q_1
+               ->  Seq Scan on q2 q_2
+               ->  Seq Scan on q3 q_3
+(12 rows)
+
+-- can be reduced to p left join q, so both can.
+prepare s8 as select * from p full join q on p.c = q.c where (p.c = $1 or p.c = $2);
+explain (costs off) execute s8('000001', '000002');
+                                      QUERY PLAN                                      
+--------------------------------------------------------------------------------------
+ Nested Loop Left Join
+   Join Filter: ((p.c)::text = (q.c)::text)
+   ->  Append
+         ->  Seq Scan on p1 p_1
+               Filter: (((c)::text = '000001'::text) OR ((c)::text = '000002'::text))
+         ->  Seq Scan on p2 p_2
+               Filter: (((c)::text = '000001'::text) OR ((c)::text = '000002'::text))
+   ->  Append
+         ->  Seq Scan on q1 q_1
+         ->  Seq Scan on q2 q_2
+(10 rows)
+
+deallocate s;
+deallocate s2;
+deallocate s3;
+deallocate s4;
+deallocate s5;
+deallocate s6;
+deallocate s7;
+deallocate s8;
+reset plan_cache_mode;
diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql
index ffd5fe8b0d..bd86157cad 100644
--- a/src/test/regress/sql/partition_prune.sql
+++ b/src/test/regress/sql/partition_prune.sql
@@ -1174,3 +1174,113 @@ explain (costs off) select * from hp_contradict_test where a === 1 and b === 1 a
 drop table hp_contradict_test;
 drop operator class part_test_int4_ops2 using hash;
 drop operator ===(int4, int4);
+
+create table p (a int, b int, c character varying(8)) partition by list(c);
+create table p1  partition of p for values in ('000001');
+create table p2  partition of p for values in ('000002');
+create table p3  partition of p for values in ('000003');
+
+create table q (a int, c character varying(8), b int) partition by list(c);
+create table q1  partition of q for values in ('000001');
+create table q2  partition of q for values in ('000002');
+create table q3  partition of q for values in ('000003');
+
+create table o (a int, c character varying(8), b int) partition by list(c);
+create table o1  partition of o for values in ('000001');
+create table o2 partition of o for values in ('000002');
+create table o3  partition of o for values in ('000003');
+
+analyze p;
+analyze q;
+analyze o;
+
+set plan_cache_mode to force_generic_plan ;
+-- inner join
+prepare s as select * from p, q where (q.c = $1 or q.c = $2) and p.c = q.c;
+explain (costs off) execute s('000001', '000002');
+
+prepare s2 as select * from p inner join q on (q.c = $1 or q.c = $2) and p.c = q.c;
+explain (costs off) execute s2('000001', '000002');
+
+-- left join
+-- q can have the init partititon prune, but p can't since it is the outer join.
+prepare s3 as select * from p left join q on (q.c = $1 or q.c = $2) and p.c = q.c;
+explain (costs off) execute s3('000001', '000002');
+
+-- p can't do the init partition prune since it is the outer join. so p can't as well.
+prepare s4 as select * from p left join q on (p.c = $1 or p.c = $2) and p.c = q.c;
+explain (costs off) execute s4('000001', '000002');
+
+-- left join is reduced to inner join, so both p and q can do the pruning.
+prepare s5 as select * from p left join q on p.c = q.c where (q.c = $1 or q.c = $2);
+explain (costs off) execute s5('000001', '000002');
+
+-- left join can't be reduced to inner join, but p.c is not in ON clause so q can.
+-- and q is right table in left join, so it can as well.
+prepare s6 as select * from p left join q on p.c = q.c where (p.c = $1 or p.c = $2);
+explain (costs off) execute s6('000001', '000002');
+
+-- full outer join.
+-- both p & q can't do the init partition prune.
+prepare s7 as select * from p full join q on p.c = q.c and (p.c = $1 or p.c = $2);
+explain (costs off) execute s7('000001', '000002');
+
+-- can be reduced to p left join q, so both can.
+prepare s8 as select * from p full join q on p.c = q.c where (p.c = $1 or p.c = $2);
+explain (costs off) execute s8('000001', '000002');
+
+deallocate s;
+deallocate s2;
+deallocate s3;
+deallocate s4;
+deallocate s5;
+deallocate s6;
+deallocate s7;
+deallocate s8;
+
+set plan_cache_mode to force_custom_plan;
+-- inner join
+prepare s as select * from p, q where (q.c = $1 or q.c = $2) and p.c = q.c;
+explain (costs off) execute s('000001', '000002');
+
+prepare s2 as select * from p inner join q on (q.c = $1 or q.c = $2) and p.c = q.c;
+explain (costs off) execute s2('000001', '000002');
+
+-- left join
+-- q can have the planning partititon prune, but p can't since it is the outer join.
+prepare s3 as select * from p left join q on (q.c = $1 or q.c = $2) and p.c = q.c;
+explain (costs off) execute s3('000001', '000002');
+
+-- p can't do the planning partition prune since it is the outer join. so p can't as well.
+prepare s4 as select * from p left join q on (p.c = $1 or p.c = $2) and p.c = q.c;
+explain (costs off) execute s4('000001', '000002');
+
+-- left join is reduced to inner join, so both p and q can do the pruning.
+prepare s5 as select * from p left join q on p.c = q.c where (q.c = $1 or q.c = $2);
+explain (costs off) execute s5('000001', '000002');
+
+-- left join can't be reduced to inner join, but p.c is not in ON clause so q can.
+-- and q is right table in left join, so it can as well.
+prepare s6 as select * from p left join q on p.c = q.c where (p.c = $1 or p.c = $2);
+explain (costs off) execute s6('000001', '000002');
+
+-- full outer join.
+-- both p & q can't do the planning partition prune.
+prepare s7 as select * from p full join q on p.c = q.c and (p.c = $1 or p.c = $2);
+explain (costs off) execute s7('000001', '000002');
+
+-- can be reduced to p left join q, so both can.
+prepare s8 as select * from p full join q on p.c = q.c where (p.c = $1 or p.c = $2);
+explain (costs off) execute s8('000001', '000002');
+
+deallocate s;
+deallocate s2;
+deallocate s3;
+deallocate s4;
+deallocate s5;
+deallocate s6;
+deallocate s7;
+deallocate s8;
+
+
+reset plan_cache_mode;
-- 
2.21.0

