Hi hackers,

Consider this query plan:

create table t (i int, b bool);
create index on t(i, b);
set enable_bitmapscan to off;
explain select * from t where i = 300 and b;

                               QUERY PLAN
-------------------------------------------------------------------------
 Index Only Scan using t_i_b_idx on t  (cost=0.15..24.27 rows=6 width=5)
   Index Cond: ((i = 300) AND (b = true))
   Filter: b


The filter is not needed, why is it there? Turns out we can't recognize that the restriction clause 'b' and the index clause 'b = true' are equivalent. My first reaction was to patch operator_predicate_proof to handle this case, but there is a more straightforward way: mark the expanded index clause as potentially redundant when it is generated in expand_indexqual_conditions. There is already RestrictInfo.parent_ec that is used to mark redundant EC-derived join clauses. The patch renames it to rinfo_parent and uses it to mark the expanded index clauses. Namely, for both the expanded and the original clause, rinfo_parent points to the original clause or its generating EC, if set. The name is no good -- the original clause is not a parent of itself, after all. I considered something like redundancy_tag, but some places actually use the fact that it points to the generating EC, so I don't like this name either.

What do you think?

--
Alexander Kuzmenkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

>From 144dd4a72fd8278aa6d44865d97239b9fad6965d Mon Sep 17 00:00:00 2001
From: Alexander Kuzmenkov <a.kuzmen...@postgrespro.ru>
Date: Wed, 16 Jan 2019 14:31:26 +0300
Subject: [PATCH] Recognize that an expanded bool index clause is equivalent to
 the original one

---
 src/backend/nodes/copyfuncs.c              |  2 +-
 src/backend/nodes/outfuncs.c               |  2 +-
 src/backend/optimizer/path/costsize.c      |  8 ++---
 src/backend/optimizer/path/equivclass.c    | 28 ++++++++---------
 src/backend/optimizer/path/indxpath.c      | 22 ++++++++++----
 src/backend/optimizer/plan/createplan.c    | 48 +++++++++++++++---------------
 src/backend/optimizer/util/restrictinfo.c  |  2 +-
 src/include/nodes/relation.h               | 10 +++++--
 src/test/regress/expected/create_index.out |  9 ++----
 9 files changed, 71 insertions(+), 60 deletions(-)

diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 006a3d1..9acb081 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2243,7 +2243,7 @@ _copyRestrictInfo(const RestrictInfo *from)
 	COPY_BITMAPSET_FIELD(right_relids);
 	COPY_NODE_FIELD(orclause);
 	/* EquivalenceClasses are never copied, so shallow-copy the pointers */
-	COPY_SCALAR_FIELD(parent_ec);
+	COPY_SCALAR_FIELD(rinfo_parent);
 	COPY_SCALAR_FIELD(eval_cost);
 	COPY_SCALAR_FIELD(norm_selec);
 	COPY_SCALAR_FIELD(outer_selec);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 0fde876..980e9c6 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2436,7 +2436,7 @@ _outRestrictInfo(StringInfo str, const RestrictInfo *node)
 	WRITE_BITMAPSET_FIELD(left_relids);
 	WRITE_BITMAPSET_FIELD(right_relids);
 	WRITE_NODE_FIELD(orclause);
-	/* don't write parent_ec, leads to infinite recursion in plan tree dump */
+	/* don't write rinfo_parent, leads to infinite recursion in plan tree dump */
 	WRITE_FLOAT_FIELD(norm_selec, "%.4f");
 	WRITE_FLOAT_FIELD(outer_selec, "%.4f");
 	WRITE_NODE_FIELD(mergeopfamilies);
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 99c5ad9..f350919 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -4673,7 +4673,7 @@ get_foreign_key_join_selectivity(PlannerInfo *root,
 			/* Drop this clause if it matches any column of the FK */
 			for (i = 0; i < fkinfo->nkeys; i++)
 			{
-				if (rinfo->parent_ec)
+				if (rinfo->rinfo_parent)
 				{
 					/*
 					 * EC-derived clauses can only match by EC.  It is okay to
@@ -4683,12 +4683,12 @@ get_foreign_key_join_selectivity(PlannerInfo *root,
 					 * have generated one equating the FK's Vars.  So for
 					 * purposes of estimation, we can act as though it did so.
 					 *
-					 * Note: checking parent_ec is a bit of a cheat because
-					 * there are EC-derived clauses that don't have parent_ec
+					 * Note: checking parent EC is a bit of a cheat because
+					 * there are EC-derived clauses that don't have parent EC
 					 * set; but such clauses must compare expressions that
 					 * aren't just Vars, so they cannot match the FK anyway.
 					 */
-					if (fkinfo->eclass[i] == rinfo->parent_ec)
+					if ((Node *) fkinfo->eclass[i] == rinfo->rinfo_parent)
 					{
 						remove_it = true;
 						break;
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 6e134ae..b18d8b6 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -1054,7 +1054,7 @@ generate_base_implied_equalities_broken(PlannerInfo *root,
  * methods.  We do not worry here about selecting clauses that are optimal
  * for use in a parameterized indexscan.  indxpath.c makes its own selections
  * of clauses to use, and if the ones we pick here are redundant with those,
- * the extras will be eliminated at createplan time, using the parent_ec
+ * the extras will be eliminated at createplan time, using the rinfo_parent
  * markers that we provide (see is_redundant_derived_clause()).
  *
  * Because the same join clauses are likely to be needed multiple times as
@@ -1264,7 +1264,7 @@ generate_join_implied_equalities_normal(PlannerInfo *root,
 		}
 
 		/*
-		 * Create clause, setting parent_ec to mark it as redundant with other
+		 * Create clause, setting parent EC to mark it as redundant with other
 		 * joinclauses
 		 */
 		rinfo = create_join_clause(root, ec, best_eq_op,
@@ -1310,7 +1310,7 @@ generate_join_implied_equalities_normal(PlannerInfo *root,
 					ec->ec_broken = true;
 					return NIL;
 				}
-				/* do NOT set parent_ec, this qual is not redundant! */
+				/* do NOT set parent EC, this qual is not redundant! */
 				rinfo = create_join_clause(root, ec, eq_op,
 										   prev_em, cur_em,
 										   NULL);
@@ -1438,7 +1438,7 @@ create_join_clause(PlannerInfo *root,
 		rinfo = (RestrictInfo *) lfirst(lc);
 		if (rinfo->left_em == leftem &&
 			rinfo->right_em == rightem &&
-			rinfo->parent_ec == parent_ec &&
+			rinfo->rinfo_parent == (Node *) parent_ec &&
 			opno == ((OpExpr *) rinfo->clause)->opno)
 			return rinfo;
 	}
@@ -1448,7 +1448,7 @@ create_join_clause(PlannerInfo *root,
 		rinfo = (RestrictInfo *) lfirst(lc);
 		if (rinfo->left_em == leftem &&
 			rinfo->right_em == rightem &&
-			rinfo->parent_ec == parent_ec &&
+			rinfo->rinfo_parent == (Node *) parent_ec &&
 			opno == ((OpExpr *) rinfo->clause)->opno)
 			return rinfo;
 	}
@@ -1470,7 +1470,7 @@ create_join_clause(PlannerInfo *root,
 										ec->ec_min_security);
 
 	/* Mark the clause as redundant, or not */
-	rinfo->parent_ec = parent_ec;
+	rinfo->rinfo_parent = (Node *) parent_ec;
 
 	/*
 	 * We know the correct values for left_ec/right_ec, ie this particular EC,
@@ -2310,7 +2310,7 @@ generate_implied_equalities_for_column(PlannerInfo *root,
 			if (!OidIsValid(eq_op))
 				continue;
 
-			/* set parent_ec to mark as redundant with other joinclauses */
+			/* set parent EC to mark as redundant with other joinclauses */
 			rinfo = create_join_clause(root, cur_ec, eq_op,
 									   cur_em, other_em,
 									   cur_ec);
@@ -2487,25 +2487,25 @@ eclass_useful_for_merging(PlannerInfo *root,
 
 /*
  * is_redundant_derived_clause
- *		Test whether rinfo is derived from same EC as any clause in clauselist;
- *		if so, it can be presumed to represent a condition that's redundant
- *		with that member of the list.
+ *		Test whether rinfo is derived from same parent as any clause in
+ *		clauselist;	if so, it can be presumed to represent a condition
+ *		that's redundant with that member of the list.
  */
 bool
 is_redundant_derived_clause(RestrictInfo *rinfo, List *clauselist)
 {
-	EquivalenceClass *parent_ec = rinfo->parent_ec;
+	Node *rinfo_parent = rinfo->rinfo_parent;
 	ListCell   *lc;
 
-	/* Fail if it's not a potentially-redundant clause from some EC */
-	if (parent_ec == NULL)
+	/* Fail if it's not a potentially-redundant clause */
+	if (rinfo_parent == NULL)
 		return false;
 
 	foreach(lc, clauselist)
 	{
 		RestrictInfo *otherrinfo = (RestrictInfo *) lfirst(lc);
 
-		if (otherrinfo->parent_ec == parent_ec)
+		if (otherrinfo->rinfo_parent == rinfo_parent)
 			return true;
 	}
 
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index f8e674c..0c4141e 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -559,9 +559,10 @@ consider_index_join_outer_rels(PlannerInfo *root, RelOptInfo *rel,
 			 * parameterization; so skip if any clause derived from the same
 			 * eclass would already have been included when using oldrelids.
 			 */
-			if (rinfo->parent_ec &&
-				eclass_already_used(rinfo->parent_ec, oldrelids,
-									indexjoinclauses))
+			if (rinfo->rinfo_parent
+				&& IsA(rinfo->rinfo_parent, EquivalenceClass)
+				&& eclass_already_used((EquivalenceClass *) rinfo->rinfo_parent,
+									   oldrelids, indexjoinclauses))
 				continue;
 
 			/*
@@ -691,7 +692,7 @@ eclass_already_used(EquivalenceClass *parent_ec, Relids oldrelids,
 	{
 		RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
 
-		if (rinfo->parent_ec == parent_ec &&
+		if (rinfo->rinfo_parent == (Node *) parent_ec &&
 			bms_is_subset(rinfo->clause_relids, oldrelids))
 			return true;
 	}
@@ -3608,8 +3609,17 @@ expand_indexqual_conditions(IndexOptInfo *index,
 												   index);
 			if (boolqual)
 			{
-				indexquals = lappend(indexquals,
-									 make_simple_restrictinfo(boolqual));
+				RestrictInfo *newRinfo = make_simple_restrictinfo(boolqual);
+				/*
+				 * Mark the expanded and the original rinfo as redundant
+				 * by setting the same rinfo_parent for them. If the original
+				 * is itself derived from an EC, use that EC.
+				 */
+				if (!rinfo->rinfo_parent)
+					rinfo->rinfo_parent = (Node *) newRinfo;
+				newRinfo->rinfo_parent = rinfo->rinfo_parent;
+
+				indexquals = lappend(indexquals, newRinfo);
 				indexqualcols = lappend_int(indexqualcols, indexcol);
 				continue;
 			}
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 97d0c28..8d0e603 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -121,7 +121,7 @@ static BitmapHeapScan *create_bitmap_scan_plan(PlannerInfo *root,
 						BitmapHeapPath *best_path,
 						List *tlist, List *scan_clauses);
 static Plan *create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
-					  List **qual, List **indexqual, List **indexECs);
+					  List **qual, List **indexqual, List **indexParents);
 static void bitmap_subplan_mark_shared(Plan *plan);
 static TidScan *create_tidscan_plan(PlannerInfo *root, TidPath *best_path,
 					List *tlist, List *scan_clauses);
@@ -2755,7 +2755,7 @@ create_bitmap_scan_plan(PlannerInfo *root,
 	Plan	   *bitmapqualplan;
 	List	   *bitmapqualorig;
 	List	   *indexquals;
-	List	   *indexECs;
+	List	   *indexParents;
 	List	   *qpqual;
 	ListCell   *l;
 	BitmapHeapScan *scan_plan;
@@ -2767,7 +2767,7 @@ create_bitmap_scan_plan(PlannerInfo *root,
 	/* Process the bitmapqual tree into a Plan tree and qual lists */
 	bitmapqualplan = create_bitmap_subplan(root, best_path->bitmapqual,
 										   &bitmapqualorig, &indexquals,
-										   &indexECs);
+										   &indexParents);
 
 	if (best_path->path.parallel_aware)
 		bitmap_subplan_mark_shared(bitmapqualplan);
@@ -2808,7 +2808,7 @@ create_bitmap_scan_plan(PlannerInfo *root,
 			continue;			/* we may drop pseudoconstants here */
 		if (list_member(indexquals, clause))
 			continue;			/* simple duplicate */
-		if (rinfo->parent_ec && list_member_ptr(indexECs, rinfo->parent_ec))
+		if (rinfo->rinfo_parent && list_member_ptr(indexParents, rinfo->rinfo_parent))
 			continue;			/* derived from same EquivalenceClass */
 		if (!contain_mutable_functions(clause) &&
 			predicate_implied_by(list_make1(clause), indexquals, false))
@@ -2867,17 +2867,17 @@ create_bitmap_scan_plan(PlannerInfo *root,
  * predicates, because we have to recheck predicates as well as index
  * conditions if the bitmap scan becomes lossy.
  *
- * In addition, we return a list of EquivalenceClass pointers for all the
- * top-level indexquals that were possibly-redundantly derived from ECs.
- * This allows removal of scan_clauses that are redundant with such quals.
- * (We do not attempt to detect such redundancies for quals that are within
- * OR subtrees.  This could be done in a less hacky way if we returned the
- * indexquals in RestrictInfo form, but that would be slower and still pretty
- * messy, since we'd have to build new RestrictInfos in many cases.)
+ * In addition, we return a list of parent pointers for all the
+ * top-level indexquals that were possibly-redundantly derived from ECs or
+ * other clauses. This allows removal of scan_clauses that are redundant with
+ * such quals. (We do not attempt to detect such redundancies for quals that
+ * are within OR subtrees. This could be done in a less hacky way if we returned
+ * the indexquals in RestrictInfo form, but that would be slower and still
+ * pretty messy, since we'd have to build new RestrictInfos in many cases.)
  */
 static Plan *
 create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
-					  List **qual, List **indexqual, List **indexECs)
+					  List **qual, List **indexqual, List **indexParents)
 {
 	Plan	   *plan;
 
@@ -2887,7 +2887,7 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
 		List	   *subplans = NIL;
 		List	   *subquals = NIL;
 		List	   *subindexquals = NIL;
-		List	   *subindexECs = NIL;
+		List	   *subindexParents = NIL;
 		ListCell   *l;
 
 		/*
@@ -2902,16 +2902,16 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
 			Plan	   *subplan;
 			List	   *subqual;
 			List	   *subindexqual;
-			List	   *subindexEC;
+			List	   *subindexParent;
 
 			subplan = create_bitmap_subplan(root, (Path *) lfirst(l),
 											&subqual, &subindexqual,
-											&subindexEC);
+											&subindexParent);
 			subplans = lappend(subplans, subplan);
 			subquals = list_concat_unique(subquals, subqual);
 			subindexquals = list_concat_unique(subindexquals, subindexqual);
-			/* Duplicates in indexECs aren't worth getting rid of */
-			subindexECs = list_concat(subindexECs, subindexEC);
+			/* Duplicates in indexParents aren't worth getting rid of */
+			subindexParents = list_concat(subindexParents, subindexParent);
 		}
 		plan = (Plan *) make_bitmap_and(subplans);
 		plan->startup_cost = apath->path.startup_cost;
@@ -2923,7 +2923,7 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
 		plan->parallel_safe = apath->path.parallel_safe;
 		*qual = subquals;
 		*indexqual = subindexquals;
-		*indexECs = subindexECs;
+		*indexParents = subindexParents;
 	}
 	else if (IsA(bitmapqual, BitmapOrPath))
 	{
@@ -3004,13 +3004,13 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
 			*indexqual = subindexquals;
 		else
 			*indexqual = list_make1(make_orclause(subindexquals));
-		*indexECs = NIL;
+		*indexParents = NIL;
 	}
 	else if (IsA(bitmapqual, IndexPath))
 	{
 		IndexPath  *ipath = (IndexPath *) bitmapqual;
 		IndexScan  *iscan;
-		List	   *subindexECs;
+		List	   *subindexParents;
 		ListCell   *l;
 
 		/* Use the regular indexscan plan build machinery... */
@@ -3049,15 +3049,15 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
 				*indexqual = lappend(*indexqual, pred);
 			}
 		}
-		subindexECs = NIL;
+		subindexParents = NIL;
 		foreach(l, ipath->indexquals)
 		{
 			RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
 
-			if (rinfo->parent_ec)
-				subindexECs = lappend(subindexECs, rinfo->parent_ec);
+			if (rinfo->rinfo_parent)
+				subindexParents = lappend(subindexParents, rinfo->rinfo_parent);
 		}
-		*indexECs = subindexECs;
+		*indexParents = subindexParents;
 	}
 	else
 	{
diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c
index e633881..a1bc1cc 100644
--- a/src/backend/optimizer/util/restrictinfo.c
+++ b/src/backend/optimizer/util/restrictinfo.c
@@ -179,7 +179,7 @@ make_restrictinfo_internal(Expr *clause,
 	 * that happens only if it appears in the right context (top level of a
 	 * joinclause list).
 	 */
-	restrictinfo->parent_ec = NULL;
+	restrictinfo->rinfo_parent = NULL;
 
 	restrictinfo->eval_cost.startup = -1;
 	restrictinfo->norm_selec = -1;
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 3430061..a48da61 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -1875,9 +1875,13 @@ typedef struct LimitPath
  *
  * When join clauses are generated from EquivalenceClasses, there may be
  * several equally valid ways to enforce join equivalence, of which we need
- * apply only one.  We mark clauses of this kind by setting parent_ec to
+ * apply only one.  We mark clauses of this kind by setting rinfo_parent to
  * point to the generating EquivalenceClass.  Multiple clauses with the same
- * parent_ec in the same join are redundant.
+ * rinfo_parent in the same join are redundant.
+ *
+ * Another case when two clauses are marked as redundant is when we expand
+ * a clause to make it usable in index scans. The expanded clause may have
+ * the original clause as the parent (see expand_indexqual_conditions).
  */
 
 typedef struct RestrictInfo
@@ -1918,7 +1922,7 @@ typedef struct RestrictInfo
 	Expr	   *orclause;		/* modified clause with RestrictInfos */
 
 	/* This field is NULL unless clause is potentially redundant: */
-	EquivalenceClass *parent_ec;	/* generating EquivalenceClass */
+	Node *rinfo_parent;			/* generating EquivalenceClass or RestrictInfo */
 
 	/* cache space for cost and selectivity */
 	QualCost	eval_cost;		/* eval cost of clause; -1 if not yet set */
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index 46deb55..f08eefc 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -3181,8 +3181,7 @@ explain (costs off)
  Limit
    ->  Index Scan using boolindex_b_i_key on boolindex
          Index Cond: (b = true)
-         Filter: b
-(4 rows)
+(3 rows)
 
 explain (costs off)
   select * from boolindex where b = true order by i desc limit 10;
@@ -3191,8 +3190,7 @@ explain (costs off)
  Limit
    ->  Index Scan Backward using boolindex_b_i_key on boolindex
          Index Cond: (b = true)
-         Filter: b
-(4 rows)
+(3 rows)
 
 explain (costs off)
   select * from boolindex where not b order by i limit 10;
@@ -3201,8 +3199,7 @@ explain (costs off)
  Limit
    ->  Index Scan using boolindex_b_i_key on boolindex
          Index Cond: (b = false)
-         Filter: (NOT b)
-(4 rows)
+(3 rows)
 
 --
 -- Test for multilevel page deletion
-- 
2.7.4

Reply via email to