From e7748463df183f00ef07a32dab886b355039ef7d Mon Sep 17 00:00:00 2001
From: Richard Guo <riguo@pivotal.io>
Date: Tue, 4 Sep 2018 03:27:26 +0000
Subject: [PATCH] Implement predicate propagation for non-equivalence clauses.

Currently, planner will generate new restriction clauses that can be deduced
from equivalence clauses, with function generate_base_implied_equalities().

In this patch, we are trying to add predicate propagation for non-equivalence
clauses, that is, A=B AND f(A) implies A=B AND f(A) and f(B), under some
restrictions on f. It can be turned on/off by GUC enable_predicate_propagation.
This is ported from Greenplum database.

The idea is in distribute_qual_to_rels(), we collect a dedicated list
'non_eq_clauses' as part of PlannerInfo, which contains non-equivalence
clauses, as well as potential equivalence clauses that are rendered unsafe by
outer-join-delay considerations. For each RestrictInfo in 'non_eq_clauses', we
replace its expression with another equivalent one and make a new RestrictInfo.
For example, if a RestrictInfo stands for A>10, and if A and B are in the same
equivalence class, we generate a new RestrictInfo by replacing A with B, so we
get B>10.

This patch will introduce extra cost for relation scan, due to the cost of
evaluating the new implied quals. Meanwhile, since the extra filter may reduce
the number of tuples returned by the scan, it may lower the cost of following
joins. So, the total cost may or may not decrease, depends on the selectivity
of the implied quals. If the selectivity is low, the extra cost introduced by
the implied qual is more likely to be compromised by the cost reduced in join,
and vise versa.

Co-authored-by: Richard Guo <riguo@pivotal.io>
Co-authored-by: Alexandra Wang <lewang@pivotal.io>
---
 src/backend/optimizer/path/costsize.c         |   1 +
 src/backend/optimizer/plan/initsplan.c        | 316 ++++++++++++++++++++++++++
 src/backend/optimizer/plan/planmain.c         |   7 +
 src/backend/optimizer/plan/planner.c          |   1 +
 src/backend/optimizer/prep/prepjointree.c     |   1 +
 src/backend/utils/misc/guc.c                  |  11 +
 src/include/nodes/relation.h                  |   2 +
 src/include/optimizer/cost.h                  |   1 +
 src/include/optimizer/planmain.h              |   1 +
 src/test/regress/expected/equivclass.out      |   6 +-
 src/test/regress/expected/join.out            |  22 +-
 src/test/regress/expected/partition_join.out  |   8 +-
 src/test/regress/expected/partition_prune.out |  84 ++-----
 src/test/regress/expected/privileges.out      |   6 +-
 src/test/regress/expected/rowsecurity.out     |   8 +-
 src/test/regress/expected/sysviews.out        |   3 +-
 src/test/regress/expected/updatable_views.out |   4 +-
 src/test/regress/sql/privileges.sql           |   2 +
 18 files changed, 396 insertions(+), 88 deletions(-)

diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 7bf67a0..5dccb8a 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -136,6 +136,7 @@ bool		enable_partitionwise_aggregate = false;
 bool		enable_parallel_append = true;
 bool		enable_parallel_hash = true;
 bool		enable_partition_pruning = true;
+bool		enable_predicate_propagation = true;
 
 typedef struct
 {
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 01335db..464efbf 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -78,6 +78,14 @@ static bool check_redundant_nullability_qual(PlannerInfo *root, Node *clause);
 static void check_mergejoinable(RestrictInfo *restrictinfo);
 static void check_hashjoinable(RestrictInfo *restrictinfo);
 
+static bool subexpression_matching_walker(Node *hayStack, void *context);
+static bool subexpression_match(Expr *expr1, Expr *expr2);
+static Node *replace_expression_mutator(Node *node, void *context);
+static void gen_implied_qual(PlannerInfo *root,
+				 RestrictInfo *old_rinfo,
+				 Node *old_expr,
+				 Node *new_expr);
+static void gen_implied_quals(PlannerInfo *root, RestrictInfo *rinfo);
 
 /*****************************************************************************
  *
@@ -2019,6 +2027,14 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 
 	/* No EC special case applies, so push it into the clause lists */
 	distribute_restrictinfo_to_rels(root, restrictinfo);
+
+	/*
+	 * The predicate propagation code (gen_implied_quals()) might be able to
+	 * derive other clauses from this, though, so remember this qual for later.
+	 * (We cannot do predicate propagation yet, because we haven't built all
+	 * the equivalence classes yet.)
+	 */
+	root->non_eq_clauses = lappend(root->non_eq_clauses, restrictinfo);
 }
 
 /*
@@ -2588,6 +2604,306 @@ match_foreign_keys_to_quals(PlannerInfo *root)
 	root->fkey_list = newlist;
 }
 
+/*
+ * Structs and Methods to support searching of matching subexpressions.
+ */
+typedef struct subexpression_matching_context
+{
+	Expr *needle;	/* This is the expression being searched */
+} subexpression_matching_context;
+
+/*
+ * subexpression_matching_walker
+ *	  Check if the expression 'needle' in context is a sub-expression
+ *	  of hayStack.
+ */
+static bool
+subexpression_matching_walker(Node *hayStack, void *context)
+{
+	subexpression_matching_context *ctx =
+		(subexpression_matching_context *) context;
+
+	Assert(context);
+	Assert(ctx->needle);
+
+	if (!hayStack)
+	{
+		return false;
+	}
+
+	if (equal(ctx->needle, hayStack))
+	{
+		return true;
+	}
+
+	return expression_tree_walker(hayStack,
+					subexpression_matching_walker,
+					(void *) context);
+}
+
+/*
+ * subexpression_match
+ *	  Check if expr1 is a subexpression of expr2.
+ *	  For example, expr1 = (x + 2) and expr2 = (x + 2 ) * 100 + 20 would
+ *	  return true.
+ */
+static bool
+subexpression_match(Expr *expr1, Expr *expr2)
+{
+	subexpression_matching_context ctx;
+	ctx.needle = expr1;
+	return subexpression_matching_walker((Node *) expr2, (void *) &ctx);
+}
+
+/*
+ * Structs and Methods to support replacing expressions.
+ */
+typedef struct
+{
+	Node *replaceThis;
+	Node *withThis;
+	int numReplacementsDone;
+} ReplaceExpressionMutatorReplacement;
+
+/*
+ * replace_expression_mutator
+ *	  Copy an expression tree, but replace all occurrences of one node with
+ *	  another.
+ *
+ *	  The replacement is passed in the context as a pointer to
+ *	  ReplaceExpressionMutatorReplacement
+ *
+ *	  context should be ReplaceExpressionMutatorReplacement*
+ */
+static Node *
+replace_expression_mutator(Node *node, void *context)
+{
+	ReplaceExpressionMutatorReplacement *repl;
+
+	if (node == NULL)
+		return NULL;
+
+	if (IsA(node, RestrictInfo))
+	{
+		RestrictInfo *info = (RestrictInfo *) node;
+
+		return replace_expression_mutator((Node *) info->clause, context);
+	}
+
+	repl = (ReplaceExpressionMutatorReplacement *) context;
+	if (equal(node, repl->replaceThis))
+	{
+		repl->numReplacementsDone++;
+		return copyObject(repl->withThis);
+	}
+	return expression_tree_mutator(node,
+					replace_expression_mutator,
+					(void *) context);
+}
+
+/*
+ * Generate implied qual
+ * Input:
+ *	root - planner information
+ *	old_rinfo - old clause to infer from
+ *	old_expr - the expression to be replaced
+ *	new_expr - new expression replacing it
+ */
+static void
+gen_implied_qual(PlannerInfo *root,
+				 RestrictInfo *old_rinfo,
+				 Node *old_expr,
+				 Node *new_expr)
+{
+	Node	   *new_clause;
+	ReplaceExpressionMutatorReplacement ctx;
+	Relids		new_qualscope;
+	ListCell   *lc;
+	RestrictInfo *new_rinfo;
+
+	/* Expression types must match */
+	Assert(exprType(old_expr) == exprType(new_expr)
+		   && exprTypmod(old_expr) == exprTypmod(new_expr));
+
+	/*
+	 * Clone the clause, replacing first node with the second.
+	 */
+	ctx.replaceThis = old_expr;
+	ctx.withThis = new_expr;
+	ctx.numReplacementsDone = 0;
+	new_clause = (Node *) replace_expression_mutator((Node *) old_rinfo->clause, &ctx);
+
+	if (ctx.numReplacementsDone == 0)
+		return;
+
+	new_qualscope = pull_varnos(new_clause);
+
+	/* distribute_qual_to_rels doesn't accept pseudoconstants? XXX: doesn't it? */
+	if (new_qualscope == NULL)
+		return;
+
+	if (subexpression_match((Expr *) new_expr, old_rinfo->clause))
+		return;
+
+	/* No inferences may be performed across an outer join */
+	if (old_rinfo->outer_relids != NULL)
+		return;
+
+	/*
+	 * Have we seen this clause before? This is needed to avoid infinite
+	 * recursion.
+	 */
+	foreach(lc, root->non_eq_clauses)
+	{
+		RestrictInfo *r = (RestrictInfo *) lfirst(lc);
+
+		if (equal(r->clause, new_clause))
+			return;
+	}
+
+	/*
+	 * Ok, we're good to go. Construct a new RestrictInfo, and pass it to
+	 * distribute_qual_to_rels().
+	 */
+	new_rinfo = make_restrictinfo((Expr *) new_clause,
+								  old_rinfo->is_pushed_down,
+								  old_rinfo->outerjoin_delayed,
+								  old_rinfo->pseudoconstant,
+								  old_rinfo->security_level,
+								  new_qualscope,
+								  old_rinfo->outer_relids,
+								  old_rinfo->nullable_relids);
+
+	check_mergejoinable(new_rinfo);
+	check_hashjoinable(new_rinfo);
+
+	/*
+	 * If it's a join clause (either naturally, or because delayed by
+	 * outer-join rules), add vars used in the clause to targetlists of their
+	 * relations, so that they will be emitted by the plan nodes that scan
+	 * those relations (else they won't be available at the join node!).
+	 */
+	if (bms_membership(new_qualscope) == BMS_MULTIPLE)
+	{
+		List	   *vars = pull_var_clause(new_clause,
+										   PVC_RECURSE_AGGREGATES |
+										   PVC_INCLUDE_PLACEHOLDERS);
+
+		add_vars_to_targetlist(root, vars, new_qualscope, false);
+		list_free(vars);
+	}
+
+	/*
+	 * If the clause has a mergejoinable operator, set the EquivalenceClass
+	 * links. Otherwise, a mergejoinable operator with NULL left_ec/right_ec
+	 * will cause update_mergeclause_eclasses fails at assertion.
+	 */
+	if (new_rinfo->mergeopfamilies)
+		initialize_mergeclause_eclasses(root, new_rinfo);
+
+	distribute_restrictinfo_to_rels(root, new_rinfo);
+}
+
+/*
+ * gen_implied_quals
+ *	  Generate all qualifications that are implied by the given RestrictInfo and
+ *	  the equivalence classes.
+
+ * Input:
+ * - root: planner info structure
+ * - rinfo: clause to derive more quals from.
+ */
+static void
+gen_implied_quals(PlannerInfo *root, RestrictInfo *rinfo)
+{
+	ListCell   *lcec;
+
+	/*
+	 * Is it safe to infer from this clause?
+	 */
+	if (contain_volatile_functions((Node *) rinfo->clause) ||
+		contain_subplans((Node *) rinfo->clause))
+		return;
+
+	/*
+	 * Find every equivalence class that's relevant for this RestrictInfo.
+	 *
+	 * Relevant means that some member of the equivalence class appears in the
+	 * clause, that we can replace it with another member.
+	 */
+	foreach(lcec, root->eq_classes)
+	{
+		EquivalenceClass *eclass = (EquivalenceClass *) lfirst(lcec);
+		ListCell   *lcem1;
+
+		/* Single-member ECs won't generate any deductions */
+		if (list_length(eclass->ec_members) <= 1)
+			continue;
+
+		if (!bms_overlap(eclass->ec_relids, rinfo->clause_relids))
+			continue;
+
+		foreach(lcem1, eclass->ec_members)
+		{
+			EquivalenceMember *em1 = (EquivalenceMember *) lfirst(lcem1);
+			ListCell   *lcem2;
+
+			if (!bms_overlap(em1->em_relids, rinfo->clause_relids))
+				continue;
+
+			/* now try to apply to others in the equivalence class */
+			foreach(lcem2, eclass->ec_members)
+			{
+				EquivalenceMember *em2 = (EquivalenceMember *) lfirst(lcem2);
+
+				if (em2 == em1)
+					continue;
+
+				if (exprType((Node *) em1->em_expr) == exprType((Node *) em2->em_expr)
+					&& exprTypmod((Node *) em1->em_expr) == exprTypmod((Node *) em2->em_expr))
+				{
+					gen_implied_qual(root,
+									 rinfo,
+									 (Node *) em1->em_expr,
+									 (Node *) em2->em_expr);
+				}
+			}
+		}
+	}
+}
+
+/*
+ * generate_implied_quals
+ *	  Generate all qualifications that are implied by the RestrictInfo in
+ *	  root->non_eq_clauses and the equivalence classes.
+ *
+ *	  Note that we require types to be the same.  We could try converting them
+ *	  (introducing relabel nodes) as long as the conversion is a widening
+ *	  conversion (clause on int4 can be applied to int2 type by widening the
+ *	  int2 to an int4 when creating the replicated clause)
+ *	  likewise, is varchar(10) vs varchar(50) an issue at this point?
+ */
+void
+generate_implied_quals(PlannerInfo *root)
+{
+	ListCell   *lc;
+
+	if (!enable_predicate_propagation)
+		return;
+
+	foreach(lc, root->non_eq_clauses)
+	{
+		RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+		gen_implied_quals(root, rinfo);
+
+		/*
+		 * NOTE: gen_implied_quals() can append more quals to the list! We
+		 * will process those as well, as we iterate.
+		 */
+	}
+}
+
 
 /*****************************************************************************
  *
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index b05adc7..d498c22 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -169,6 +169,13 @@ query_planner(PlannerInfo *root, List *tlist,
 	reconsider_outer_join_clauses(root);
 
 	/*
+	 * Use the list of equijoined keys to transfer quals between relations.
+	 * For example: A=B AND f(A) implies A=B AND f(A) and f(B), under some
+	 * restrictions on f.
+	 */
+	generate_implied_quals(root);
+
+	/*
 	 * If we formed any equivalence classes, generate additional restriction
 	 * clauses as appropriate.  (Implied join clauses are formed on-the-fly
 	 * later.)
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 96bf060..af5d84f 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -614,6 +614,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	root->cte_plan_ids = NIL;
 	root->multiexpr_params = NIL;
 	root->eq_classes = NIL;
+	root->non_eq_clauses = NIL;
 	root->append_rel_list = NIL;
 	root->rowMarks = NIL;
 	memset(root->upper_rels, 0, sizeof(root->upper_rels));
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index c3f46a2..2be1be2 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -906,6 +906,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	subroot->cte_plan_ids = NIL;
 	subroot->multiexpr_params = NIL;
 	subroot->eq_classes = NIL;
+	subroot->non_eq_clauses = NIL;
 	subroot->append_rel_list = NIL;
 	subroot->rowMarks = NIL;
 	memset(subroot->upper_rels, 0, sizeof(subroot->upper_rels));
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 0625eff..ad787cf 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -981,6 +981,17 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
+		{"enable_predicate_propagation", PGC_USERSET, QUERY_TUNING_OTHER,
+			gettext_noop("When two expressions are equivalent (such as with "
+						 "equijoined keys) then the planner applies predicates "
+						 "on one expression to the other expression."),
+			gettext_noop("If false, planner does not copy predicates.")
+		},
+		&enable_predicate_propagation,
+		true,
+		NULL, NULL, NULL
+	},
+	{
 		{"geqo", PGC_USERSET, QUERY_TUNING_GEQO,
 			gettext_noop("Enables genetic query optimization."),
 			gettext_noop("This algorithm attempts to do planning without "
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index adb4265..6c3012a 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -258,6 +258,8 @@ typedef struct PlannerInfo
 
 	List	   *eq_classes;		/* list of active EquivalenceClasses */
 
+	List	   *non_eq_clauses;	/* list of non-equivalence clauses */
+
 	List	   *canon_pathkeys; /* list of "canonical" PathKeys */
 
 	List	   *left_join_clauses;	/* list of RestrictInfos for mergejoinable
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index 77ca7ff..3c76772 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -72,6 +72,7 @@ extern PGDLLIMPORT bool enable_partitionwise_aggregate;
 extern PGDLLIMPORT bool enable_parallel_append;
 extern PGDLLIMPORT bool enable_parallel_hash;
 extern PGDLLIMPORT bool enable_partition_pruning;
+extern PGDLLIMPORT bool enable_predicate_propagation;
 extern PGDLLIMPORT int constraint_exclusion;
 
 extern double clamp_row_est(double nrows);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index c8ab028..15ccd19 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -99,6 +99,7 @@ extern RestrictInfo *build_implied_join_equality(Oid opno,
 							Relids nullable_relids,
 							Index security_level);
 extern void match_foreign_keys_to_quals(PlannerInfo *root);
+extern void generate_implied_quals(PlannerInfo *root);
 
 /*
  * prototypes for plan/analyzejoins.c
diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out
index c448d85..be2088c 100644
--- a/src/test/regress/expected/equivclass.out
+++ b/src/test/regress/expected/equivclass.out
@@ -407,14 +407,14 @@ set session authorization regress_user_ectest;
 explain (costs off)
   select * from ec0 a, ec1 b
   where a.ff = b.ff and a.ff = 43::bigint::int8alias1;
-                 QUERY PLAN                  
----------------------------------------------
+                              QUERY PLAN                              
+----------------------------------------------------------------------
  Nested Loop
    ->  Index Scan using ec0_pkey on ec0 a
          Index Cond: (ff = '43'::int8alias1)
    ->  Index Scan using ec1_pkey on ec1 b
          Index Cond: (ff = a.ff)
-         Filter: (f1 < '5'::int8alias1)
+         Filter: ((f1 < '5'::int8alias1) AND (ff = '43'::int8alias1))
 (6 rows)
 
 reset session authorization;
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index dc6262b..2cedad0 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -3135,7 +3135,7 @@ where t1.unique2 < 42 and t1.stringu1 > t2.stringu2;
          Join Filter: ((0) = i1.f1)
          ->  Nested Loop
                ->  Nested Loop
-                     Join Filter: ((1) = (1))
+                     Join Filter: ((11 < 42) AND ((1) = (1)))
                      ->  Result
                      ->  Result
                ->  Index Scan using tenk1_unique2 on tenk1 t1
@@ -5917,23 +5917,22 @@ where exists (select 1 from tenk1 t3
 ---------------------------------------------------------------------------------
  Nested Loop
    Output: t1.unique1, t2.hundred
-   ->  Hash Join
+   ->  Nested Loop
          Output: t1.unique1, t3.tenthous
-         Hash Cond: (t3.thousand = t1.unique1)
+         Join Filter: (t1.unique1 = t3.thousand)
+         ->  Index Only Scan using onek_unique1 on public.onek t1
+               Output: t1.unique1
+               Index Cond: (t1.unique1 < 1)
          ->  HashAggregate
                Output: t3.thousand, t3.tenthous
                Group Key: t3.thousand, t3.tenthous
                ->  Index Only Scan using tenk1_thous_tenthous on public.tenk1 t3
                      Output: t3.thousand, t3.tenthous
-         ->  Hash
-               Output: t1.unique1
-               ->  Index Only Scan using onek_unique1 on public.onek t1
-                     Output: t1.unique1
-                     Index Cond: (t1.unique1 < 1)
+                     Index Cond: (t3.thousand < 1)
    ->  Index Only Scan using tenk1_hundred on public.tenk1 t2
          Output: t2.hundred
          Index Cond: (t2.hundred = t3.tenthous)
-(18 rows)
+(17 rows)
 
 -- ... unless it actually is unique
 create table j3 as select unique1, tenthous from onek;
@@ -5951,16 +5950,17 @@ where exists (select 1 from j3
    Output: t1.unique1, t2.hundred
    ->  Nested Loop
          Output: t1.unique1, j3.tenthous
+         Join Filter: (t1.unique1 = j3.unique1)
          ->  Index Only Scan using onek_unique1 on public.onek t1
                Output: t1.unique1
                Index Cond: (t1.unique1 < 1)
          ->  Index Only Scan using j3_unique1_tenthous_idx on public.j3
                Output: j3.unique1, j3.tenthous
-               Index Cond: (j3.unique1 = t1.unique1)
+               Index Cond: (j3.unique1 < 1)
    ->  Index Only Scan using tenk1_hundred on public.tenk1 t2
          Output: t2.hundred
          Index Cond: (t2.hundred = j3.tenthous)
-(13 rows)
+(14 rows)
 
 drop table j3;
 --
diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out
index 3ba3aaf..2ac858e 100644
--- a/src/test/regress/expected/partition_join.out
+++ b/src/test/regress/expected/partition_join.out
@@ -186,18 +186,18 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 50 phv, * FROM prt1 WHERE prt1.b = 0)
 -- Join with pruned partitions from joining relations
 EXPLAIN (COSTS OFF)
 SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.b = 0 ORDER BY t1.a, t2.b;
-                        QUERY PLAN                         
------------------------------------------------------------
+                               QUERY PLAN                                
+-------------------------------------------------------------------------
  Sort
    Sort Key: t1.a
    ->  Append
          ->  Hash Join
                Hash Cond: (t2.b = t1.a)
                ->  Seq Scan on prt2_p2 t2
-                     Filter: (b > 250)
+                     Filter: ((b > 250) AND (b < 450))
                ->  Hash
                      ->  Seq Scan on prt1_p2 t1
-                           Filter: ((a < 450) AND (b = 0))
+                           Filter: ((a < 450) AND (a > 250) AND (b = 0))
 (10 rows)
 
 SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.b = 0 ORDER BY t1.a, t2.b;
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
index 24313e8..e975275 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -2164,24 +2164,12 @@ select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on
                            Filter: (a = ANY ('{0,0,1}'::integer[]))
                      ->  Append (actual rows=0 loops=102)
                            ->  Index Scan using ab_a1_b1_a_idx on ab_a1_b1 (actual rows=0 loops=2)
-                                 Index Cond: (a = a.a)
+                                 Index Cond: ((a = a.a) AND (a = ANY ('{0,0,1}'::integer[])))
                            ->  Index Scan using ab_a1_b2_a_idx on ab_a1_b2 (actual rows=0 loops=2)
-                                 Index Cond: (a = a.a)
+                                 Index Cond: ((a = a.a) AND (a = ANY ('{0,0,1}'::integer[])))
                            ->  Index Scan using ab_a1_b3_a_idx on ab_a1_b3 (actual rows=0 loops=2)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b1_a_idx on ab_a2_b1 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b2_a_idx on ab_a2_b2 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b3_a_idx on ab_a2_b3 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b1_a_idx on ab_a3_b1 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b2_a_idx on ab_a3_b2 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b3_a_idx on ab_a3_b3 (never executed)
-                                 Index Cond: (a = a.a)
-(27 rows)
+                                 Index Cond: ((a = a.a) AND (a = ANY ('{0,0,1}'::integer[])))
+(15 rows)
 
 -- Ensure the same partitions are pruned when we make the nested loop
 -- parameter an Expr rather than a plain Param.
@@ -2231,24 +2219,18 @@ select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on
                            Filter: (a = ANY ('{1,0,3}'::integer[]))
                      ->  Append (actual rows=0 loops=104)
                            ->  Index Scan using ab_a1_b1_a_idx on ab_a1_b1 (actual rows=0 loops=2)
-                                 Index Cond: (a = a.a)
+                                 Index Cond: ((a = a.a) AND (a = ANY ('{1,0,3}'::integer[])))
                            ->  Index Scan using ab_a1_b2_a_idx on ab_a1_b2 (actual rows=0 loops=2)
-                                 Index Cond: (a = a.a)
+                                 Index Cond: ((a = a.a) AND (a = ANY ('{1,0,3}'::integer[])))
                            ->  Index Scan using ab_a1_b3_a_idx on ab_a1_b3 (actual rows=0 loops=2)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b1_a_idx on ab_a2_b1 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b2_a_idx on ab_a2_b2 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b3_a_idx on ab_a2_b3 (never executed)
-                                 Index Cond: (a = a.a)
+                                 Index Cond: ((a = a.a) AND (a = ANY ('{1,0,3}'::integer[])))
                            ->  Index Scan using ab_a3_b1_a_idx on ab_a3_b1 (actual rows=0 loops=2)
-                                 Index Cond: (a = a.a)
+                                 Index Cond: ((a = a.a) AND (a = ANY ('{1,0,3}'::integer[])))
                            ->  Index Scan using ab_a3_b2_a_idx on ab_a3_b2 (actual rows=0 loops=2)
-                                 Index Cond: (a = a.a)
+                                 Index Cond: ((a = a.a) AND (a = ANY ('{1,0,3}'::integer[])))
                            ->  Index Scan using ab_a3_b3_a_idx on ab_a3_b3 (actual rows=0 loops=2)
-                                 Index Cond: (a = a.a)
-(27 rows)
+                                 Index Cond: ((a = a.a) AND (a = ANY ('{1,0,3}'::integer[])))
+(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                                      
@@ -2264,29 +2246,17 @@ select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on
                            Rows Removed by Filter: 1
                      ->  Append (actual rows=0 loops=102)
                            ->  Index Scan using ab_a1_b1_a_idx on ab_a1_b1 (actual rows=0 loops=2)
-                                 Index Cond: (a = a.a)
+                                 Index Cond: ((a = a.a) AND (a = ANY ('{1,0,0}'::integer[])))
                            ->  Index Scan using ab_a1_b2_a_idx on ab_a1_b2 (actual rows=0 loops=2)
-                                 Index Cond: (a = a.a)
+                                 Index Cond: ((a = a.a) AND (a = ANY ('{1,0,0}'::integer[])))
                            ->  Index Scan using ab_a1_b3_a_idx on ab_a1_b3 (actual rows=0 loops=2)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b1_a_idx on ab_a2_b1 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b2_a_idx on ab_a2_b2 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b3_a_idx on ab_a2_b3 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b1_a_idx on ab_a3_b1 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b2_a_idx on ab_a3_b2 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b3_a_idx on ab_a3_b3 (never executed)
-                                 Index Cond: (a = a.a)
-(28 rows)
+                                 Index Cond: ((a = a.a) AND (a = ANY ('{1,0,0}'::integer[])))
+(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)');
-                                  explain_parallel_append                                   
---------------------------------------------------------------------------------------------
+                                   explain_parallel_append                                    
+----------------------------------------------------------------------------------------------
  Finalize Aggregate (actual rows=1 loops=1)
    ->  Gather (actual rows=2 loops=1)
          Workers Planned: 1
@@ -2298,24 +2268,12 @@ select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on
                            Rows Removed by Filter: 1
                      ->  Append (actual rows=0 loops=100)
                            ->  Index Scan using ab_a1_b1_a_idx on ab_a1_b1 (never executed)
-                                 Index Cond: (a = a.a)
+                                 Index Cond: ((a = a.a) AND (a = ANY ('{1,0,0}'::integer[])))
                            ->  Index Scan using ab_a1_b2_a_idx on ab_a1_b2 (never executed)
-                                 Index Cond: (a = a.a)
+                                 Index Cond: ((a = a.a) AND (a = ANY ('{1,0,0}'::integer[])))
                            ->  Index Scan using ab_a1_b3_a_idx on ab_a1_b3 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b1_a_idx on ab_a2_b1 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b2_a_idx on ab_a2_b2 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b3_a_idx on ab_a2_b3 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b1_a_idx on ab_a3_b1 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b2_a_idx on ab_a3_b2 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b3_a_idx on ab_a3_b3 (never executed)
-                                 Index Cond: (a = a.a)
-(28 rows)
+                                 Index Cond: ((a = a.a) AND (a = ANY ('{1,0,0}'::integer[])))
+(16 rows)
 
 reset enable_hashjoin;
 reset enable_mergejoin;
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index ac8968d..2157adb 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -210,7 +210,7 @@ EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b;
          Filter: (b <<< 5)
    ->  Index Scan using atest12_a_idx on atest12
          Index Cond: (a = atest12_1.b)
-         Filter: (b <<< 5)
+         Filter: ((b <<< 5) AND (a <<< 5))
 (6 rows)
 
 -- And this one.
@@ -235,6 +235,7 @@ CREATE OPERATOR >>> (procedure = leak2, leftarg = integer, rightarg = integer,
 -- This should not show any "leak" notices before failing.
 EXPLAIN (COSTS OFF) SELECT * FROM atest12 WHERE a >>> 0;
 ERROR:  permission denied for table atest12
+SET enable_predicate_propagation TO off;
 -- This plan should use hashjoin, as it will expect many rows to be selected.
 EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b;
                 QUERY PLAN                 
@@ -248,6 +249,7 @@ EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b;
                Filter: (b <<< 5)
 (7 rows)
 
+RESET enable_predicate_propagation;
 -- Now regress_priv_user1 grants sufficient access to regress_priv_user2.
 SET SESSION AUTHORIZATION regress_priv_user1;
 GRANT SELECT (a, b) ON atest12 TO PUBLIC;
@@ -261,7 +263,7 @@ EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b;
          Filter: (b <<< 5)
    ->  Index Scan using atest12_a_idx on atest12
          Index Cond: (a = atest12_1.b)
-         Filter: (b <<< 5)
+         Filter: ((b <<< 5) AND (a <<< 5))
 (6 rows)
 
 -- But not for this, due to lack of table-wide permissions needed
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index bc16ca4..2e9949b 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -346,11 +346,15 @@ EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dt
    InitPlan 1 (returns $0)
      ->  Index Scan using uaccount_pkey on uaccount
            Index Cond: (pguser = CURRENT_USER)
-   ->  Seq Scan on category
+   ->  Bitmap Heap Scan on category
+         Recheck Cond: (cid < 50)
+         Filter: ((cid <> 44) AND (cid <> 44))
+         ->  Bitmap Index Scan on category_pkey
+               Index Cond: (cid < 50)
    ->  Hash
          ->  Seq Scan on document
                Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))
-(9 rows)
+(13 rows)
 
 -- 44 would technically fail for both p2r and p1r, but we should get an error
 -- back from p1r for this because it sorts first
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index a1c90eb..608a94a 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -86,10 +86,11 @@ select name, setting from pg_settings where name like 'enable%';
  enable_partition_pruning       | on
  enable_partitionwise_aggregate | off
  enable_partitionwise_join      | off
+ enable_predicate_propagation   | on
  enable_seqscan                 | on
  enable_sort                    | on
  enable_tidscan                 | on
-(17 rows)
+(18 rows)
 
 -- Test that the pg_timezone_names and pg_timezone_abbrevs views are
 -- more-or-less working.  We can't test their contents in any great detail
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index e64d693..03afc34 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -662,7 +662,7 @@ EXPLAIN (costs off) UPDATE rw_view2 SET a=3 WHERE a=2;
  Update on base_tbl
    ->  Nested Loop
          ->  Index Scan using base_tbl_pkey on base_tbl
-               Index Cond: (a = 2)
+               Index Cond: ((a < 10) AND (a = 2))
          ->  Subquery Scan on rw_view1
                Filter: ((rw_view1.a < 10) AND (rw_view1.a = 2))
                ->  Bitmap Heap Scan on base_tbl base_tbl_1
@@ -677,7 +677,7 @@ EXPLAIN (costs off) DELETE FROM rw_view2 WHERE a=2;
  Delete on base_tbl
    ->  Nested Loop
          ->  Index Scan using base_tbl_pkey on base_tbl
-               Index Cond: (a = 2)
+               Index Cond: ((a < 10) AND (a = 2))
          ->  Subquery Scan on rw_view1
                Filter: ((rw_view1.a < 10) AND (rw_view1.a = 2))
                ->  Bitmap Heap Scan on base_tbl base_tbl_1
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index f7f3bbb..8506261 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -168,8 +168,10 @@ CREATE OPERATOR >>> (procedure = leak2, leftarg = integer, rightarg = integer,
 -- This should not show any "leak" notices before failing.
 EXPLAIN (COSTS OFF) SELECT * FROM atest12 WHERE a >>> 0;
 
+SET enable_predicate_propagation TO off;
 -- This plan should use hashjoin, as it will expect many rows to be selected.
 EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b;
+RESET enable_predicate_propagation;
 
 -- Now regress_priv_user1 grants sufficient access to regress_priv_user2.
 SET SESSION AUTHORIZATION regress_priv_user1;
-- 
2.7.4

