On 7/15/23 16:20, Tomas Vondra wrote:
>
> ...
> 
> 4) problems with opcintype != opckeytype (name_ops)
> 
> While running the tests, I ran into an issue with name_ops, causing
> failures for \dT and other catalog queries. The root cause is that
> name_ops has opcintype = name, but opckeytype = cstring. The index-only
> clauses are copied from the table, with Vars mutated to reference the
> INDEX_VAR. But the type is not, so when we get to evaluating the
> expressions, CheckVarSlotCompatibility() fails because the Var has name,
> but the iss_IndexSlot (created with index tuple descriptor) has cstring.
> 
> The rebased patch fixes this by explicitly adjusting types of the
> descriptor in ExecInitIndexScan().
> 
> However, maybe this indicates the very idea of evaluating expressions
> using slot with index tuple descriptor is misguided. This made me look
> at regular index-only scan (nodeIndexonlyscan.c), and that uses a slot
> with the "table" structure, and instead of evaluating the expression on
> the index index tuple it expands the index tuple into the table slot.
> Which is what StoreIndexTuple() does.
> 
> So maybe this should do what IOS does - expand the index tuple into
> "table slot" and evaluate the expression on that. That'd also make the
> INDEX_VAR tweak in createplan.c unnecessary - in fact, that seemed a bit
> strange anyway, so ditching fix_indexfilter_mutator would be good.
> 

This kept bothering me, so I looked at it today, and reworked it to use
the IOS approach. It's a bit more complicated because for IOS both slots
have the same overall structure, except for the data types. But for
regular index scans that's not the case - the code has to "expand" the
index tuple into the larger "table slot". This works, and in general I
think the result is much cleaner - in particular, it means we don't need
to switch the Var nodes to reference the INDEX_VAR.

While experimenting with this I realized again that we're not matching
expressions to IOS. So if you have an expression index on (a+b), that
can't be used even if the query only uses this particular expression.
The same limitation applies to index-only filters, of course. It's not
the fault of this patch, but perhaps it'd be an interesting improvement.


regards

-- 
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
From 685e4014533d9d5f29564a3a21b972abba70ee9f Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tomas.von...@postgresql.org>
Date: Sun, 9 Apr 2023 02:08:45 +0200
Subject: [PATCH] evaluate filters on the index tuple (when possible)

Discussion: https://www.postgresql.org/message-id/flat/N1xaIrU29uk5YxLyW55MGk5fz9s6V2FNtj54JRaVlFbPixD5z8sJ07Ite5CvbWwik8ZvDG07oSTN-usENLVMq2UAcizVTEd5b-o16ZGDIIU%3D%40yamlcoder.me
---
 src/backend/commands/explain.c                |   2 +
 src/backend/executor/nodeIndexscan.c          | 258 +++++++++++++++++-
 src/backend/optimizer/path/costsize.c         |  23 ++
 src/backend/optimizer/path/indxpath.c         | 190 +++++++++++--
 src/backend/optimizer/plan/createplan.c       |  80 ++++++
 src/backend/optimizer/plan/planner.c          |   2 +-
 src/backend/optimizer/plan/setrefs.c          |   6 +
 src/backend/optimizer/util/pathnode.c         |   2 +
 src/backend/utils/misc/guc_tables.c           |  10 +
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/include/nodes/execnodes.h                 |   7 +
 src/include/nodes/pathnodes.h                 |   1 +
 src/include/nodes/plannodes.h                 |   2 +
 src/include/optimizer/cost.h                  |   1 +
 src/include/optimizer/pathnode.h              |   1 +
 src/test/regress/expected/create_index.out    |  19 +-
 src/test/regress/expected/sysviews.out        |   3 +-
 src/test/regress/expected/updatable_views.out |  12 +-
 18 files changed, 580 insertions(+), 40 deletions(-)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 8570b14f621..556388bc499 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1778,6 +1778,8 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_IndexScan:
 			show_scan_qual(((IndexScan *) plan)->indexqualorig,
 						   "Index Cond", planstate, ancestors, es);
+			show_scan_qual(((IndexScan *) plan)->indexfiltersorig,
+						   "Index Filter", planstate, ancestors, es);
 			if (((IndexScan *) plan)->indexqualorig)
 				show_instrumentation_count("Rows Removed by Index Recheck", 2,
 										   planstate, es);
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 4540c7781d2..5ee1fd86d7f 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -32,6 +32,8 @@
 #include "access/nbtree.h"
 #include "access/relscan.h"
 #include "access/tableam.h"
+#include "access/visibilitymap.h"
+#include "catalog/index.h"
 #include "catalog/pg_am.h"
 #include "executor/execdebug.h"
 #include "executor/nodeIndexscan.h"
@@ -69,6 +71,10 @@ static void reorderqueue_push(IndexScanState *node, TupleTableSlot *slot,
 							  Datum *orderbyvals, bool *orderbynulls);
 static HeapTuple reorderqueue_pop(IndexScanState *node);
 
+static void StoreIndexTuple(TupleTableSlot *slot, IndexTuple itup,
+							TupleDesc itupdesc, IndexInfo *iinfo);
+static void StoreHeapTuple(TupleTableSlot *slot, HeapTuple tup,
+						   TupleDesc tupdesc, IndexInfo *iinfo);
 
 /* ----------------------------------------------------------------
  *		IndexNext
@@ -115,6 +121,13 @@ IndexNext(IndexScanState *node)
 
 		node->iss_ScanDesc = scandesc;
 
+		/* Set it up for index-only filters, if there are any. */
+		if (node->indexfilters != NULL)
+		{
+			node->iss_ScanDesc->xs_want_itup = true;
+			node->iss_VMBuffer = InvalidBuffer;
+		}
+
 		/*
 		 * If no run-time keys to calculate or they are ready, go ahead and
 		 * pass the scankeys to the index AM.
@@ -127,11 +140,150 @@ IndexNext(IndexScanState *node)
 
 	/*
 	 * ok, now that we have what we need, fetch the next tuple.
+	 *
+	 * XXX maybe we should invent something like index_getnext_tid/index_getnext_slot
+	 * that would allow doing this in a more readable / coherent way.
 	 */
-	while (index_getnext_slot(scandesc, direction, slot))
+	while (true)
 	{
+		bool	has_index_tuple = false;
+		bool	filter_checked;
+
 		CHECK_FOR_INTERRUPTS();
 
+		/*
+		 * XXX code from index_getnext_slot(), but we need to inject stuff between
+		 * the index_getnext_tid() and index_fetch_heap(), so we do it here
+		 */
+		for (;;)
+		{
+			filter_checked = false;
+
+			if (!scandesc->xs_heap_continue)
+			{
+				ItemPointer tid;
+
+				/* Time to fetch the next TID from the index */
+				tid = index_getnext_tid(scandesc, direction);
+
+				/* If we're out of index entries, we're done */
+				if (tid == NULL)
+					break;
+
+				Assert(ItemPointerEquals(tid, &scandesc->xs_heaptid));
+			}
+
+			/* Make sure we have a valid item pointer. */
+			Assert(ItemPointerIsValid(&scandesc->xs_heaptid));
+
+			/*
+			 * If there are index clauses, try to evaluate the filter on the index
+			 * tuple first, and only when it fails try the index tuple.
+			 *
+			 * https://www.postgresql.org/message-id/N1xaIrU29uk5YxLyW55MGk5fz9s6V2FNtj54JRaVlFbPixD5z8sJ07Ite5CvbWwik8ZvDG07oSTN-usENLVMq2UAcizVTEd5b-o16ZGDIIU%3D%40yamlcoder.me
+			 */
+			if (node->indexfilters != NULL)
+			{
+				ItemPointer tid = &scandesc->xs_heaptid;
+
+				/*
+				 * XXX see nodeIndexonlyscan.c, but inverse - we only do this when
+				 * we can check some filters on the index tuple.
+				 */
+				if (VM_ALL_VISIBLE(scandesc->heapRelation,
+								   ItemPointerGetBlockNumber(tid),
+								   &node->iss_VMBuffer))
+				{
+					/*
+					 * Only MVCC snapshots are supported here, so there should be no
+					 * need to keep following the HOT chain once a visible entry has
+					 * been found.  If we did want to allow that, we'd need to keep
+					 * more state to remember not to call index_getnext_tid next time.
+					 *
+					 * XXX Is there a place where we can decide whether to consider
+					 * this optimization, based on which type of snapshot we're going
+					 * to use? And disable it for non-MVCC ones? That'd mean this
+					 * error can't really happen here. Or how can we even get this
+					 * error now?
+					 */
+					if (scandesc->xs_heap_continue)
+						elog(ERROR, "non-MVCC snapshots are not supported with index filters");
+
+					/*
+					 * Fill the scan tuple slot with data from the index.  This might be
+					 * provided in either HeapTuple or IndexTuple format.  Conceivably an
+					 * index AM might fill both fields, in which case we prefer the heap
+					 * format, since it's probably a bit cheaper to fill a slot from.
+					 */
+					if (scandesc->xs_hitup)
+					{
+						StoreHeapTuple(node->iss_TableSlot, scandesc->xs_hitup,
+									   scandesc->xs_hitupdesc, node->iss_IndexInfo);
+					}
+					else if (scandesc->xs_itup)
+						StoreIndexTuple(node->iss_TableSlot, scandesc->xs_itup,
+										scandesc->xs_itupdesc, node->iss_IndexInfo);
+					else
+						elog(ERROR, "no data returned for index-only scan");
+
+					/* run the expressions
+					 *
+					 * XXX This does not work, because for some indexes the index key type
+					 * may differ from the table attribute type. This happens when the
+					 * (pg_opclass.opcintype != opckeytype). There's about 10 such built-in
+					 * opclasses, e.g. name_ops creates cstring on name columns. A good
+					 * example is pg_namespace.nspname, with the index on nspname causing
+					 * trouble for \dT.
+					 *
+					 * One option would be to tweak the type for the Var, but I'm not sure
+					 * that's actually correct. Surely some of the types may not be binary
+					 * compatible (e.g. jsonb_ops has opcintype=jsonb and opckeytype=text).
+					 * So maybe we should just disable index-only filters for such cases.
+					 */
+					econtext->ecxt_scantuple = node->iss_TableSlot;
+
+					/* check the filters pushed to the index-tuple level */
+					if (!ExecQual(node->indexfilters, econtext))
+					{
+						InstrCountFiltered2(node, 1);
+						continue;
+					}
+
+					/* remember we already checked the filter */
+					filter_checked = true;
+				}
+			}
+
+			/*
+			 * Fetch the next (or only) visible heap tuple for this index entry.
+			 * If we don't find anything, loop around and grab the next TID from
+			 * the index.
+			 */
+			if (index_fetch_heap(scandesc, slot))
+			{
+				has_index_tuple = true;
+				break;
+			}
+		}
+
+		if (!has_index_tuple)
+			break;
+
+		/*
+		 * If we didn't manage to check the filter on the index tuple (because
+		 * of the page not being all-visible, etc.), do that now on the heap
+		 * tuple.
+		 */
+		if (!filter_checked)
+		{
+			econtext->ecxt_scantuple = slot;
+			if (!ExecQual(node->indexfiltersorig, econtext))
+			{
+				InstrCountFiltered2(node, 1);
+				continue;
+			}
+		}
+
 		/*
 		 * If the index was lossy, we have to recheck the index quals using
 		 * the fetched tuple.
@@ -794,6 +946,13 @@ ExecEndIndexScan(IndexScanState *node)
 	indexRelationDesc = node->iss_RelationDesc;
 	indexScanDesc = node->iss_ScanDesc;
 
+	/* Release VM buffer pin, if any. */
+	if (node->iss_VMBuffer != InvalidBuffer)
+	{
+		ReleaseBuffer(node->iss_VMBuffer);
+		node->iss_VMBuffer = InvalidBuffer;
+	}
+
 	/*
 	 * Free the exprcontext(s) ... now dead code, see ExecFreeExprContext
 	 */
@@ -956,6 +1115,10 @@ ExecInitIndexScan(IndexScan *node, EState *estate, int eflags)
 		ExecInitQual(node->scan.plan.qual, (PlanState *) indexstate);
 	indexstate->indexqualorig =
 		ExecInitQual(node->indexqualorig, (PlanState *) indexstate);
+	indexstate->indexfilters =
+		ExecInitQual(node->indexfilters, (PlanState *) indexstate);
+	indexstate->indexfiltersorig =
+		ExecInitQual(node->indexfiltersorig, (PlanState *) indexstate);
 	indexstate->indexorderbyorig =
 		ExecInitExprList(node->indexorderbyorig, (PlanState *) indexstate);
 
@@ -971,6 +1134,19 @@ ExecInitIndexScan(IndexScan *node, EState *estate, int eflags)
 	lockmode = exec_rt_fetch(node->scan.scanrelid, estate)->rellockmode;
 	indexstate->iss_RelationDesc = index_open(node->indexid, lockmode);
 
+	/*
+	 * We need another slot, in a format that's suitable for the table AM, for
+	 * when we need to evaluate index-only filter on data from index tuple.
+	 *
+	 * XXX Maybe this could use the scan tuple slot?
+	 */
+	indexstate->iss_TableSlot =
+		ExecAllocTableSlot(&estate->es_tupleTable,
+						   RelationGetDescr(currentRelation),
+						   table_slot_callbacks(currentRelation));
+
+	indexstate->iss_IndexInfo = BuildIndexInfo(indexstate->iss_RelationDesc);
+
 	/*
 	 * Initialize index-specific scan state
 	 */
@@ -1744,3 +1920,83 @@ ExecIndexScanInitializeWorker(IndexScanState *node,
 					 node->iss_ScanKeys, node->iss_NumScanKeys,
 					 node->iss_OrderByKeys, node->iss_NumOrderByKeys);
 }
+
+/*
+ * StoreIndexTuple
+ *		Fill the slot with data from the index tuple.
+ *
+ * At some point this might be generally-useful functionality, but
+ * right now we don't need it elsewhere.
+ */
+static void
+StoreIndexTuple(TupleTableSlot *slot, IndexTuple itup, TupleDesc itupdesc, IndexInfo *iinfo)
+{
+	bool   *isnull;
+	Datum  *values;
+
+	isnull = palloc0(itupdesc->natts * sizeof(bool));
+	values = palloc0(itupdesc->natts * sizeof(Datum));
+
+	/* expand the index tuple */
+	index_deform_tuple(itup, itupdesc, values, isnull);
+
+	/* now fill the values from the index tuple into the table slot */
+	ExecClearTuple(slot);
+
+	for (int i = 0; i < iinfo->ii_NumIndexAttrs; i++)
+	{
+		AttrNumber	attnum = iinfo->ii_IndexAttrNumbers[i];
+
+		/* skip expressions */
+		if (attnum > 0)
+		{
+			slot->tts_isnull[attnum - 1] = isnull[i];
+			slot->tts_values[attnum - 1] = values[i];
+		}
+	}
+
+	pfree(values);
+	pfree(isnull);
+
+	ExecStoreVirtualTuple(slot);
+}
+
+/*
+ * StoreHeapTuple
+ *		Fill the slot with data from the heap tuple (from the index).
+ *
+ * At some point this might be generally-useful functionality, but
+ * right now we don't need it elsewhere.
+ */
+static void
+StoreHeapTuple(TupleTableSlot *slot, HeapTuple tup, TupleDesc tupdesc, IndexInfo *iinfo)
+{
+	bool   *isnull;
+	Datum  *values;
+
+	isnull = palloc0(tupdesc->natts * sizeof(bool));
+	values = palloc0(tupdesc->natts * sizeof(Datum));
+
+	/* expand the index tuple */
+	heap_deform_tuple(tup, tupdesc, values, isnull);
+
+	/* now fill the values from the index tuple into the table slot */
+	ExecClearTuple(slot);
+
+	for (int i = 0; i < iinfo->ii_NumIndexAttrs; i++)
+	{
+		AttrNumber	attnum = iinfo->ii_IndexAttrNumbers[i];
+
+		/* skip expressions */
+		if (attnum > 0)
+		{
+			slot->tts_isnull[attnum - 1] = isnull[i];
+			slot->tts_values[attnum - 1] = values[i];
+		}
+	}
+
+	pfree(values);
+	pfree(isnull);
+
+	ExecStoreVirtualTuple(slot);
+}
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index ef475d95a18..ef4be8d6bb3 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -135,6 +135,7 @@ int			max_parallel_workers_per_gather = 2;
 bool		enable_seqscan = true;
 bool		enable_indexscan = true;
 bool		enable_indexonlyscan = true;
+bool		enable_indexonlyfilter = true;
 bool		enable_bitmapscan = true;
 bool		enable_tidscan = true;
 bool		enable_sort = true;
@@ -607,6 +608,28 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count,
 	/* estimate number of main-table tuples fetched */
 	tuples_fetched = clamp_row_est(indexSelectivity * baserel->tuples);
 
+	/*
+	 * If some filters can be evaluated on the index tuple, account for that.
+	 * We need to scan all tuples from pages that are not all-visible, and
+	 * for the remaining tuples we fetch those not eliminated by the filter.
+	 *
+	 * XXX Does this need to worry about path->path.param_info?
+	 *
+	 * XXX All of this seems overly manual / ad-hoc, surely there's a place
+	 * where we already do this in a more elegant manner?
+	 */
+	if (path->indexfilters != NIL)
+	{
+		Selectivity sel;
+
+		sel = clauselist_selectivity(root, path->indexfilters, baserel->relid,
+									 JOIN_INNER, NULL);
+
+		tuples_fetched *= (1.0 - baserel->allvisfrac) + (baserel->allvisfrac) * sel;
+
+		tuples_fetched = clamp_row_est(tuples_fetched);
+	}
+
 	/* fetch estimated page costs for tablespace containing table */
 	get_tablespace_page_costs(baserel->reltablespace,
 							  &spc_random_page_cost,
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 6a93d767a5c..4dc5b814f00 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -101,9 +101,11 @@ static bool eclass_already_used(EquivalenceClass *parent_ec, Relids oldrelids,
 								List *indexjoinclauses);
 static void get_index_paths(PlannerInfo *root, RelOptInfo *rel,
 							IndexOptInfo *index, IndexClauseSet *clauses,
-							List **bitindexpaths);
+							List *filters, List **bitindexpaths);
 static List *build_index_paths(PlannerInfo *root, RelOptInfo *rel,
-							   IndexOptInfo *index, IndexClauseSet *clauses,
+							   IndexOptInfo *index,
+							   IndexClauseSet *clauses,
+							   List *filters,
 							   bool useful_predicate,
 							   ScanTypeControl scantype,
 							   bool *skip_nonnative_saop,
@@ -124,6 +126,7 @@ static PathClauseUsage *classify_index_clause_usage(Path *path,
 static void find_indexpath_quals(Path *bitmapqual, List **quals, List **preds);
 static int	find_list_position(Node *node, List **nodelist);
 static bool check_index_only(RelOptInfo *rel, IndexOptInfo *index);
+static bool check_index_filter(RelOptInfo *rel, IndexOptInfo *index, Node *clause);
 static double get_loop_count(PlannerInfo *root, Index cur_relid, Relids outer_relids);
 static double adjust_rowcount_for_semijoins(PlannerInfo *root,
 											Index cur_relid,
@@ -132,7 +135,8 @@ static double adjust_rowcount_for_semijoins(PlannerInfo *root,
 static double approximate_joinrel_size(PlannerInfo *root, Relids relids);
 static void match_restriction_clauses_to_index(PlannerInfo *root,
 											   IndexOptInfo *index,
-											   IndexClauseSet *clauseset);
+											   IndexClauseSet *clauseset,
+											   List **filters);
 static void match_join_clauses_to_index(PlannerInfo *root,
 										RelOptInfo *rel, IndexOptInfo *index,
 										IndexClauseSet *clauseset,
@@ -143,15 +147,20 @@ static void match_eclass_clauses_to_index(PlannerInfo *root,
 static void match_clauses_to_index(PlannerInfo *root,
 								   List *clauses,
 								   IndexOptInfo *index,
-								   IndexClauseSet *clauseset);
+								   IndexClauseSet *clauseset,
+								   List **filters);
 static void match_clause_to_index(PlannerInfo *root,
 								  RestrictInfo *rinfo,
 								  IndexOptInfo *index,
-								  IndexClauseSet *clauseset);
+								  IndexClauseSet *clauseset,
+								  List **filters);
 static IndexClause *match_clause_to_indexcol(PlannerInfo *root,
 											 RestrictInfo *rinfo,
 											 int indexcol,
 											 IndexOptInfo *index);
+static RestrictInfo *match_filter_to_index(PlannerInfo *root,
+										  RestrictInfo *rinfo,
+										  IndexOptInfo *index);
 static bool IsBooleanOpfamily(Oid opfamily);
 static IndexClause *match_boolean_index_clause(PlannerInfo *root,
 											   RestrictInfo *rinfo,
@@ -241,6 +250,7 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel)
 	IndexClauseSet rclauseset;
 	IndexClauseSet jclauseset;
 	IndexClauseSet eclauseset;
+	List	   *rfilters;
 	ListCell   *lc;
 
 	/* Skip the whole mess if no indexes */
@@ -270,14 +280,15 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel)
 		 * Identify the restriction clauses that can match the index.
 		 */
 		MemSet(&rclauseset, 0, sizeof(rclauseset));
-		match_restriction_clauses_to_index(root, index, &rclauseset);
+		rfilters = NIL;
+		match_restriction_clauses_to_index(root, index, &rclauseset, &rfilters);
 
 		/*
 		 * Build index paths from the restriction clauses.  These will be
 		 * non-parameterized paths.  Plain paths go directly to add_path(),
 		 * bitmap paths are added to bitindexpaths to be handled below.
 		 */
-		get_index_paths(root, rel, index, &rclauseset,
+		get_index_paths(root, rel, index, &rclauseset, rfilters,
 						&bitindexpaths);
 
 		/*
@@ -301,6 +312,8 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel)
 		/*
 		 * If we found any plain or eclass join clauses, build parameterized
 		 * index paths using them.
+		 *
+		 * XXX Maybe pass the filters too?
 		 */
 		if (jclauseset.nonempty || eclauseset.nonempty)
 			consider_index_join_clauses(root, rel, index,
@@ -662,7 +675,7 @@ get_join_index_paths(PlannerInfo *root, RelOptInfo *rel,
 	Assert(clauseset.nonempty);
 
 	/* Build index path(s) using the collected set of clauses */
-	get_index_paths(root, rel, index, &clauseset, bitindexpaths);
+	get_index_paths(root, rel, index, &clauseset, NULL, bitindexpaths);
 
 	/*
 	 * Remember we considered paths for this set of relids.
@@ -712,7 +725,7 @@ eclass_already_used(EquivalenceClass *parent_ec, Relids oldrelids,
 static void
 get_index_paths(PlannerInfo *root, RelOptInfo *rel,
 				IndexOptInfo *index, IndexClauseSet *clauses,
-				List **bitindexpaths)
+				List *filters, List **bitindexpaths)
 {
 	List	   *indexpaths;
 	bool		skip_nonnative_saop = false;
@@ -726,7 +739,7 @@ get_index_paths(PlannerInfo *root, RelOptInfo *rel,
 	 * paths if possible).
 	 */
 	indexpaths = build_index_paths(root, rel,
-								   index, clauses,
+								   index, clauses, filters,
 								   index->predOK,
 								   ST_ANYSCAN,
 								   &skip_nonnative_saop,
@@ -741,7 +754,7 @@ get_index_paths(PlannerInfo *root, RelOptInfo *rel,
 	{
 		indexpaths = list_concat(indexpaths,
 								 build_index_paths(root, rel,
-												   index, clauses,
+												   index, clauses, filters,
 												   index->predOK,
 												   ST_ANYSCAN,
 												   &skip_nonnative_saop,
@@ -781,7 +794,7 @@ get_index_paths(PlannerInfo *root, RelOptInfo *rel,
 	if (skip_nonnative_saop)
 	{
 		indexpaths = build_index_paths(root, rel,
-									   index, clauses,
+									   index, clauses, filters,
 									   false,
 									   ST_BITMAPSCAN,
 									   NULL,
@@ -833,7 +846,9 @@ get_index_paths(PlannerInfo *root, RelOptInfo *rel,
  */
 static List *
 build_index_paths(PlannerInfo *root, RelOptInfo *rel,
-				  IndexOptInfo *index, IndexClauseSet *clauses,
+				  IndexOptInfo *index,
+				  IndexClauseSet *clauses,
+				  List *filters,
 				  bool useful_predicate,
 				  ScanTypeControl scantype,
 				  bool *skip_nonnative_saop,
@@ -842,6 +857,7 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 	List	   *result = NIL;
 	IndexPath  *ipath;
 	List	   *index_clauses;
+	List	   *index_filters;
 	Relids		outer_relids;
 	double		loop_count;
 	List	   *orderbyclauses;
@@ -947,6 +963,26 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 			return NIL;
 	}
 
+	/*
+	 * If we have index-only filters, combine the clauses into a simple list and
+	 * add the relids to outer relids.
+	 */
+	index_filters = NIL;
+	if (filters)
+	{
+		ListCell   *lc;
+
+		foreach(lc, filters)
+		{
+			RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+			/* OK to include this clause (as a filter) */
+			index_filters = lappend(index_filters, rinfo);
+			outer_relids = bms_add_members(outer_relids,
+										   rinfo->clause_relids);
+		}
+	}
+
 	/* We do not want the index's rel itself listed in outer_relids */
 	outer_relids = bms_del_member(outer_relids, rel->relid);
 
@@ -1015,6 +1051,7 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 	{
 		ipath = create_index_path(root, index,
 								  index_clauses,
+								  index_filters,
 								  orderbyclauses,
 								  orderbyclausecols,
 								  useful_pathkeys,
@@ -1035,6 +1072,7 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 		{
 			ipath = create_index_path(root, index,
 									  index_clauses,
+									  index_filters,
 									  orderbyclauses,
 									  orderbyclausecols,
 									  useful_pathkeys,
@@ -1068,6 +1106,7 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 		{
 			ipath = create_index_path(root, index,
 									  index_clauses,
+									  index_filters,
 									  NIL,
 									  NIL,
 									  useful_pathkeys,
@@ -1085,6 +1124,7 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 			{
 				ipath = create_index_path(root, index,
 										  index_clauses,
+										  index_filters,
 										  NIL,
 										  NIL,
 										  useful_pathkeys,
@@ -1147,6 +1187,7 @@ build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
 	{
 		IndexOptInfo *index = (IndexOptInfo *) lfirst(lc);
 		IndexClauseSet clauseset;
+		List	   *filters;
 		List	   *indexpaths;
 		bool		useful_predicate;
 
@@ -1191,11 +1232,14 @@ build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
 		 * Identify the restriction clauses that can match the index.
 		 */
 		MemSet(&clauseset, 0, sizeof(clauseset));
-		match_clauses_to_index(root, clauses, index, &clauseset);
+		filters = NIL;
+		match_clauses_to_index(root, clauses, index, &clauseset, &filters);
 
 		/*
 		 * If no matches so far, and the index predicate isn't useful, we
 		 * don't want it.
+		 *
+		 * XXX Maybe this should check the filterset too?
 		 */
 		if (!clauseset.nonempty && !useful_predicate)
 			continue;
@@ -1203,13 +1247,13 @@ build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
 		/*
 		 * Add "other" restriction clauses to the clauseset.
 		 */
-		match_clauses_to_index(root, other_clauses, index, &clauseset);
+		match_clauses_to_index(root, other_clauses, index, &clauseset, &filters);
 
 		/*
 		 * Construct paths if possible.
 		 */
 		indexpaths = build_index_paths(root, rel,
-									   index, &clauseset,
+									   index, &clauseset, filters,
 									   useful_predicate,
 									   ST_BITMAPSCAN,
 									   NULL,
@@ -1853,6 +1897,62 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index)
 	return result;
 }
 
+/*
+ * check_index_filter
+ *		Determine whether a clause can be executed directly on the index tuple.
+ */
+static bool
+check_index_filter(RelOptInfo *rel, IndexOptInfo *index, Node *clause)
+{
+	bool		result;
+	Bitmapset  *attrs_used = NULL;
+	Bitmapset  *index_canreturn_attrs = NULL;
+	int			i;
+
+	/* Index-only scans must be enabled */
+	if (!enable_indexonlyfilter)
+		return false;
+
+	/*
+	 * Check that all needed attributes of the relation are available from the
+	 * index.
+	 */
+
+	/*
+	 * First, identify all the attributes needed by the clause.
+	 */
+	pull_varattnos(clause, rel->relid, &attrs_used);
+
+	/*
+	 * Construct a bitmapset of columns that the index can return back in an
+	 * index-only scan.
+	 */
+	for (i = 0; i < index->ncolumns; i++)
+	{
+		int			attno = index->indexkeys[i];
+
+		/*
+		 * For the moment, we just ignore index expressions.  It might be nice
+		 * to do something with them, later.
+		 */
+		if (attno == 0)
+			continue;
+
+		if (index->canreturn[i])
+			index_canreturn_attrs =
+				bms_add_member(index_canreturn_attrs,
+							   attno - FirstLowInvalidHeapAttributeNumber);
+	}
+
+	/* Do we have all the necessary attributes? */
+	result = bms_is_subset(attrs_used, index_canreturn_attrs);
+
+	bms_free(attrs_used);
+	bms_free(index_canreturn_attrs);
+
+	return result;
+}
+
 /*
  * get_loop_count
  *		Choose the loop count estimate to use for costing a parameterized path
@@ -2021,10 +2121,11 @@ approximate_joinrel_size(PlannerInfo *root, Relids relids)
 static void
 match_restriction_clauses_to_index(PlannerInfo *root,
 								   IndexOptInfo *index,
-								   IndexClauseSet *clauseset)
+								   IndexClauseSet *clauseset,
+								   List **filters)
 {
 	/* We can ignore clauses that are implied by the index predicate */
-	match_clauses_to_index(root, index->indrestrictinfo, index, clauseset);
+	match_clauses_to_index(root, index->indrestrictinfo, index, clauseset, filters);
 }
 
 /*
@@ -2032,6 +2133,8 @@ match_restriction_clauses_to_index(PlannerInfo *root,
  *	  Identify join clauses for the rel that match the index.
  *	  Matching clauses are added to *clauseset.
  *	  Also, add any potentially usable join OR clauses to *joinorclauses.
+ *
+ * FIXME Maybe this should fill the filterset too?
  */
 static void
 match_join_clauses_to_index(PlannerInfo *root,
@@ -2054,7 +2157,7 @@ match_join_clauses_to_index(PlannerInfo *root,
 		if (restriction_is_or_clause(rinfo))
 			*joinorclauses = lappend(*joinorclauses, rinfo);
 		else
-			match_clause_to_index(root, rinfo, index, clauseset);
+			match_clause_to_index(root, rinfo, index, clauseset, NULL);
 	}
 }
 
@@ -2062,6 +2165,8 @@ match_join_clauses_to_index(PlannerInfo *root,
  * match_eclass_clauses_to_index
  *	  Identify EquivalenceClass join clauses for the rel that match the index.
  *	  Matching clauses are added to *clauseset.
+ *
+ * XXX Maybe this should fill the filterset too?
  */
 static void
 match_eclass_clauses_to_index(PlannerInfo *root, IndexOptInfo *index,
@@ -2092,7 +2197,7 @@ match_eclass_clauses_to_index(PlannerInfo *root, IndexOptInfo *index,
 		 * since for non-btree indexes the EC's equality operators might not
 		 * be in the index opclass (cf ec_member_matches_indexcol).
 		 */
-		match_clauses_to_index(root, clauses, index, clauseset);
+		match_clauses_to_index(root, clauses, index, clauseset, NULL);
 	}
 }
 
@@ -2105,7 +2210,8 @@ static void
 match_clauses_to_index(PlannerInfo *root,
 					   List *clauses,
 					   IndexOptInfo *index,
-					   IndexClauseSet *clauseset)
+					   IndexClauseSet *clauseset,
+					   List **filters)
 {
 	ListCell   *lc;
 
@@ -2113,7 +2219,7 @@ match_clauses_to_index(PlannerInfo *root,
 	{
 		RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
 
-		match_clause_to_index(root, rinfo, index, clauseset);
+		match_clause_to_index(root, rinfo, index, clauseset, filters);
 	}
 }
 
@@ -2138,7 +2244,8 @@ static void
 match_clause_to_index(PlannerInfo *root,
 					  RestrictInfo *rinfo,
 					  IndexOptInfo *index,
-					  IndexClauseSet *clauseset)
+					  IndexClauseSet *clauseset,
+					  List **filters)
 {
 	int			indexcol;
 
@@ -2187,6 +2294,20 @@ match_clause_to_index(PlannerInfo *root,
 			return;
 		}
 	}
+
+	/* if filterset is NULL, we're done */
+	if (!filters)
+		return;
+
+	/*
+	 * We didn't record the clause as a regular index clause, so see if
+	 * we can evaluate it as an index filter.
+	 */
+	if ((rinfo = match_filter_to_index(root, rinfo, index)) != NULL)
+	{
+		/* FIXME maybe check/prevent duplicates, like above? */
+		*filters = lappend(*filters, rinfo);
+	}
 }
 
 /*
@@ -2322,6 +2443,29 @@ match_clause_to_indexcol(PlannerInfo *root,
 	return NULL;
 }
 
+static RestrictInfo *
+match_filter_to_index(PlannerInfo *root,
+					  RestrictInfo *rinfo,
+					  IndexOptInfo *index)
+{
+	Expr	   *clause = rinfo->clause;
+
+	/*
+	 * Historically this code has coped with NULL clauses.  That's probably
+	 * not possible anymore, but we might as well continue to cope.
+	 */
+	if (clause == NULL)
+		return NULL;
+
+	/*
+	 * Can the clause be evaluated only using the index tuple?
+	 */
+	if (!check_index_filter(index->rel, index, (Node *) rinfo->clause))
+		return NULL;
+
+	return rinfo;
+}
+
 /*
  * IsBooleanOpfamily
  *	  Detect whether an opfamily supports boolean equality as an operator.
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index af48109058d..9d436730961 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -166,10 +166,16 @@ static Node *replace_nestloop_params_mutator(Node *node, PlannerInfo *root);
 static void fix_indexqual_references(PlannerInfo *root, IndexPath *index_path,
 									 List **stripped_indexquals_p,
 									 List **fixed_indexquals_p);
+static void fix_indexfilter_references(PlannerInfo *root, IndexPath *index_path,
+									 List **stripped_indexfilters_p,
+									 List **fixed_indexfilters_p);
 static List *fix_indexorderby_references(PlannerInfo *root, IndexPath *index_path);
 static Node *fix_indexqual_clause(PlannerInfo *root,
 								  IndexOptInfo *index, int indexcol,
 								  Node *clause, List *indexcolnos);
+static Node *fix_indexfilter_clause(PlannerInfo *root,
+									IndexOptInfo *index,
+									Node *clause);
 static Node *fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol);
 static List *get_switched_clauses(List *clauses, Relids outerrelids);
 static List *order_qual_clauses(PlannerInfo *root, List *clauses);
@@ -182,6 +188,7 @@ static SampleScan *make_samplescan(List *qptlist, List *qpqual, Index scanrelid,
 								   TableSampleClause *tsc);
 static IndexScan *make_indexscan(List *qptlist, List *qpqual, Index scanrelid,
 								 Oid indexid, List *indexqual, List *indexqualorig,
+								 List *indexfilters, List *indexfiltersorig,
 								 List *indexorderby, List *indexorderbyorig,
 								 List *indexorderbyops,
 								 ScanDirection indexscandir);
@@ -2994,7 +3001,9 @@ create_indexscan_plan(PlannerInfo *root,
 	Oid			indexoid = indexinfo->indexoid;
 	List	   *qpqual;
 	List	   *stripped_indexquals;
+	List	   *stripped_indexfilters;
 	List	   *fixed_indexquals;
+	List	   *fixed_indexfilters;
 	List	   *fixed_indexorderbys;
 	List	   *indexorderbyops = NIL;
 	ListCell   *l;
@@ -3016,6 +3025,16 @@ create_indexscan_plan(PlannerInfo *root,
 							 &stripped_indexquals,
 							 &fixed_indexquals);
 
+	/*
+	 * Extract the index qual expressions (stripped of RestrictInfos) from the
+	 * IndexClauses list, and prepare a copy with index Vars substituted for
+	 * table Vars.  (This step also does replace_nestloop_params on the
+	 * fixed_indexquals.)
+	 */
+	fix_indexfilter_references(root, best_path,
+							   &stripped_indexfilters,
+							   &fixed_indexfilters);
+
 	/*
 	 * Likewise fix up index attr references in the ORDER BY expressions.
 	 */
@@ -3084,6 +3103,8 @@ create_indexscan_plan(PlannerInfo *root,
 	{
 		stripped_indexquals = (List *)
 			replace_nestloop_params(root, (Node *) stripped_indexquals);
+		stripped_indexfilters = (List *)
+			replace_nestloop_params(root, (Node *) stripped_indexfilters);
 		qpqual = (List *)
 			replace_nestloop_params(root, (Node *) qpqual);
 		indexorderbys = (List *)
@@ -3160,6 +3181,8 @@ create_indexscan_plan(PlannerInfo *root,
 											indexoid,
 											fixed_indexquals,
 											stripped_indexquals,
+											fixed_indexfilters,
+											stripped_indexfilters,
 											fixed_indexorderbys,
 											indexorderbys,
 											indexorderbyops,
@@ -5004,6 +5027,40 @@ fix_indexqual_references(PlannerInfo *root, IndexPath *index_path,
 	*fixed_indexquals_p = fixed_indexquals;
 }
 
+/*
+ * fix_indexfilter_references
+ *	  Adjust indexfilter clauses to the form the executor's indexfilter
+ *	  machinery needs.
+ *
+ * XXX This does similar stuff to fix_indexqual_references does, except that it
+ * doesn't switch the Vars to point to the index attnum (we'll expand the index
+ * tuple into the heap tuple and run the expression on that).
+ */
+static void
+fix_indexfilter_references(PlannerInfo *root, IndexPath *index_path,
+						 List **stripped_indexfilters_p, List **fixed_indexfilters_p)
+{
+	IndexOptInfo *index = index_path->indexinfo;
+	List	   *stripped_indexfilters;
+	List	   *fixed_indexfilters;
+	ListCell   *lc;
+
+	stripped_indexfilters = fixed_indexfilters = NIL;
+
+	foreach(lc, index_path->indexfilters)
+	{
+		RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
+		Node	   *clause = (Node *) rinfo->clause;
+
+		stripped_indexfilters = lappend(stripped_indexfilters, clause);
+		clause = fix_indexfilter_clause(root, index, clause);
+		fixed_indexfilters = lappend(fixed_indexfilters, clause);
+	}
+
+	*stripped_indexfilters_p = stripped_indexfilters;
+	*fixed_indexfilters_p = fixed_indexfilters;
+}
+
 /*
  * fix_indexorderby_references
  *	  Adjust indexorderby clauses to the form the executor's index
@@ -5102,6 +5159,25 @@ fix_indexqual_clause(PlannerInfo *root, IndexOptInfo *index, int indexcol,
 	return clause;
 }
 
+/*
+ * fix_indexfilter_clause
+ *	  Convert a single indexqual clause to the form needed by the executor.
+ *
+ * We only replace nestloop params here. The Vars are left pointing to the
+ * table varno.
+ */
+static Node *
+fix_indexfilter_clause(PlannerInfo *root, IndexOptInfo *index, Node *clause)
+{
+	/*
+	 * Replace any outer-relation variables with nestloop params.
+	 *
+	 * This also makes a copy of the clause, so it's safe to modify it
+	 * in-place below (not done, actually).
+	 */
+	return replace_nestloop_params(root, clause);
+}
+
 /*
  * fix_indexqual_operand
  *	  Convert an indexqual expression to a Var referencing the index column.
@@ -5500,6 +5576,8 @@ make_indexscan(List *qptlist,
 			   Oid indexid,
 			   List *indexqual,
 			   List *indexqualorig,
+			   List *indexfilters,
+			   List *indexfiltersorig,
 			   List *indexorderby,
 			   List *indexorderbyorig,
 			   List *indexorderbyops,
@@ -5516,6 +5594,8 @@ make_indexscan(List *qptlist,
 	node->indexid = indexid;
 	node->indexqual = indexqual;
 	node->indexqualorig = indexqualorig;
+	node->indexfilters = indexfilters;
+	node->indexfiltersorig = indexfiltersorig;
 	node->indexorderby = indexorderby;
 	node->indexorderbyorig = indexorderbyorig;
 	node->indexorderbyops = indexorderbyops;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 44efb1f4ebc..5c2840201da 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6661,7 +6661,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 
 	/* Estimate the cost of index scan */
 	indexScanPath = create_index_path(root, indexInfo,
-									  NIL, NIL, NIL, NIL,
+									  NIL, NIL, NIL, NIL, NIL,
 									  ForwardScanDirection, false,
 									  NULL, 1.0, false);
 
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 97fa561e4ea..3e2d0c60c53 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -667,6 +667,12 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 				splan->indexqualorig =
 					fix_scan_list(root, splan->indexqualorig,
 								  rtoffset, NUM_EXEC_QUAL(plan));
+				splan->indexfilters =
+					fix_scan_list(root, splan->indexfilters,
+								  rtoffset, 1);
+				splan->indexfiltersorig =
+					fix_scan_list(root, splan->indexfiltersorig,
+								  rtoffset, NUM_EXEC_QUAL(plan));
 				splan->indexorderby =
 					fix_scan_list(root, splan->indexorderby,
 								  rtoffset, 1);
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index f123fcb41e3..3ae8c84bd29 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -995,6 +995,7 @@ IndexPath *
 create_index_path(PlannerInfo *root,
 				  IndexOptInfo *index,
 				  List *indexclauses,
+				  List *indexfilters,
 				  List *indexorderbys,
 				  List *indexorderbycols,
 				  List *pathkeys,
@@ -1019,6 +1020,7 @@ create_index_path(PlannerInfo *root,
 
 	pathnode->indexinfo = index;
 	pathnode->indexclauses = indexclauses;
+	pathnode->indexfilters = indexfilters;
 	pathnode->indexorderbys = indexorderbys;
 	pathnode->indexorderbycols = indexorderbycols;
 	pathnode->indexscandir = indexscandir;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 93dc2e76809..a2ee529dc71 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -838,6 +838,16 @@ struct config_bool ConfigureNamesBool[] =
 		true,
 		NULL, NULL, NULL
 	},
+	{
+		{"enable_indexonlyfilter", PGC_USERSET, QUERY_TUNING_METHOD,
+			gettext_noop("Enables the planner's use of index to evaluate filters."),
+			NULL,
+			GUC_EXPLAIN
+		},
+		&enable_indexonlyfilter,
+		true,
+		NULL, NULL, NULL
+	},
 	{
 		{"enable_bitmapscan", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of bitmap-scan plans."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index e4c0269fa3d..c874ec9c3c2 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -381,6 +381,7 @@
 #enable_incremental_sort = on
 #enable_indexscan = on
 #enable_indexonlyscan = on
+#enable_indexonlyfilter = on
 #enable_material = on
 #enable_memoize = on
 #enable_mergejoin = on
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a196..98ff183636c 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1572,6 +1572,13 @@ typedef struct IndexScanState
 	Relation	iss_RelationDesc;
 	struct IndexScanDescData *iss_ScanDesc;
 
+	/* index-only filters */
+	ExprState	   *indexfilters;
+	ExprState	   *indexfiltersorig;
+	TupleTableSlot *iss_TableSlot;
+	Buffer			iss_VMBuffer;
+	IndexInfo	   *iss_IndexInfo;
+
 	/* These are needed for re-checking ORDER BY expr ordering */
 	pairingheap *iss_ReorderQueue;
 	bool		iss_ReachedEnd;
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index c17b53f7adb..5c856b41efb 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1677,6 +1677,7 @@ typedef struct IndexPath
 	Path		path;
 	IndexOptInfo *indexinfo;
 	List	   *indexclauses;
+	List	   *indexfilters;
 	List	   *indexorderbys;
 	List	   *indexorderbycols;
 	ScanDirection indexscandir;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 1b787fe0318..cf02b939b23 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -450,6 +450,8 @@ typedef struct IndexScan
 	Oid			indexid;		/* OID of index to scan */
 	List	   *indexqual;		/* list of index quals (usually OpExprs) */
 	List	   *indexqualorig;	/* the same in original form */
+	List	   *indexfilters;	/* quals for included columns */
+	List	   *indexfiltersorig;	/* the same in original form */
 	List	   *indexorderby;	/* list of index ORDER BY exprs */
 	List	   *indexorderbyorig;	/* the same in original form */
 	List	   *indexorderbyops;	/* OIDs of sort ops for ORDER BY exprs */
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index 6cf49705d3a..02ed1212715 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -52,6 +52,7 @@ extern PGDLLIMPORT int max_parallel_workers_per_gather;
 extern PGDLLIMPORT bool enable_seqscan;
 extern PGDLLIMPORT bool enable_indexscan;
 extern PGDLLIMPORT bool enable_indexonlyscan;
+extern PGDLLIMPORT bool enable_indexonlyfilter;
 extern PGDLLIMPORT bool enable_bitmapscan;
 extern PGDLLIMPORT bool enable_tidscan;
 extern PGDLLIMPORT bool enable_sort;
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 001e75b5b76..d5e380c8275 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -41,6 +41,7 @@ extern Path *create_samplescan_path(PlannerInfo *root, RelOptInfo *rel,
 extern IndexPath *create_index_path(PlannerInfo *root,
 									IndexOptInfo *index,
 									List *indexclauses,
+									List *indexfilters,
 									List *indexorderbys,
 									List *indexorderbycols,
 									List *pathkeys,
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index acfd9d1f4f7..448f26004e4 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -1838,18 +1838,13 @@ DROP TABLE onek_with_null;
 EXPLAIN (COSTS OFF)
 SELECT * FROM tenk1
   WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42);
-                                                               QUERY PLAN                                                                
------------------------------------------------------------------------------------------------------------------------------------------
- Bitmap Heap Scan on tenk1
-   Recheck Cond: (((thousand = 42) AND (tenthous = 1)) OR ((thousand = 42) AND (tenthous = 3)) OR ((thousand = 42) AND (tenthous = 42)))
-   ->  BitmapOr
-         ->  Bitmap Index Scan on tenk1_thous_tenthous
-               Index Cond: ((thousand = 42) AND (tenthous = 1))
-         ->  Bitmap Index Scan on tenk1_thous_tenthous
-               Index Cond: ((thousand = 42) AND (tenthous = 3))
-         ->  Bitmap Index Scan on tenk1_thous_tenthous
-               Index Cond: ((thousand = 42) AND (tenthous = 42))
-(9 rows)
+                              QUERY PLAN                               
+-----------------------------------------------------------------------
+ Index Scan using tenk1_thous_tenthous on tenk1
+   Index Cond: (thousand = 42)
+   Index Filter: ((tenthous = 1) OR (tenthous = 3) OR (tenthous = 42))
+   Filter: ((tenthous = 1) OR (tenthous = 3) OR (tenthous = 42))
+(4 rows)
 
 SELECT * FROM tenk1
   WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42);
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 001c6e7eb9d..b641fa7fd8d 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -117,6 +117,7 @@ select name, setting from pg_settings where name like 'enable%';
  enable_hashagg                 | on
  enable_hashjoin                | on
  enable_incremental_sort        | on
+ enable_indexonlyfilter         | on
  enable_indexonlyscan           | on
  enable_indexscan               | on
  enable_material                | on
@@ -132,7 +133,7 @@ select name, setting from pg_settings where name like 'enable%';
  enable_seqscan                 | on
  enable_sort                    | on
  enable_tidscan                 | on
-(21 rows)
+(22 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 1950e6f281f..37c28b29791 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -2599,6 +2599,7 @@ UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
                ->  Index Scan using t1_a_idx on public.t1 t1_1
                      Output: t1_1.tableoid, t1_1.ctid
                      Index Cond: ((t1_1.a > 5) AND (t1_1.a < 7))
+                     Index Filter: ((SubPlan 1) AND leakproof(t1_1.a) AND (t1_1.a <> 6))
                      Filter: ((t1_1.a <> 6) AND (SubPlan 1) AND snoop(t1_1.a) AND leakproof(t1_1.a))
                      SubPlan 1
                        ->  Append
@@ -2609,16 +2610,19 @@ UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
                ->  Index Scan using t11_a_idx on public.t11 t1_2
                      Output: t1_2.tableoid, t1_2.ctid
                      Index Cond: ((t1_2.a > 5) AND (t1_2.a < 7))
+                     Index Filter: ((SubPlan 1) AND leakproof(t1_2.a) AND (t1_2.a <> 6))
                      Filter: ((t1_2.a <> 6) AND (SubPlan 1) AND snoop(t1_2.a) AND leakproof(t1_2.a))
                ->  Index Scan using t12_a_idx on public.t12 t1_3
                      Output: t1_3.tableoid, t1_3.ctid
                      Index Cond: ((t1_3.a > 5) AND (t1_3.a < 7))
+                     Index Filter: ((SubPlan 1) AND leakproof(t1_3.a) AND (t1_3.a <> 6))
                      Filter: ((t1_3.a <> 6) AND (SubPlan 1) AND snoop(t1_3.a) AND leakproof(t1_3.a))
                ->  Index Scan using t111_a_idx on public.t111 t1_4
                      Output: t1_4.tableoid, t1_4.ctid
                      Index Cond: ((t1_4.a > 5) AND (t1_4.a < 7))
+                     Index Filter: ((SubPlan 1) AND leakproof(t1_4.a) AND (t1_4.a <> 6))
                      Filter: ((t1_4.a <> 6) AND (SubPlan 1) AND snoop(t1_4.a) AND leakproof(t1_4.a))
-(30 rows)
+(34 rows)
 
 UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
 SELECT * FROM v1 WHERE a=100; -- Nothing should have been changed to 100
@@ -2646,6 +2650,7 @@ UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
                ->  Index Scan using t1_a_idx on public.t1 t1_1
                      Output: t1_1.a, t1_1.tableoid, t1_1.ctid
                      Index Cond: ((t1_1.a > 5) AND (t1_1.a = 8))
+                     Index Filter: ((SubPlan 1) AND leakproof(t1_1.a))
                      Filter: ((SubPlan 1) AND snoop(t1_1.a) AND leakproof(t1_1.a))
                      SubPlan 1
                        ->  Append
@@ -2656,16 +2661,19 @@ UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
                ->  Index Scan using t11_a_idx on public.t11 t1_2
                      Output: t1_2.a, t1_2.tableoid, t1_2.ctid
                      Index Cond: ((t1_2.a > 5) AND (t1_2.a = 8))
+                     Index Filter: ((SubPlan 1) AND leakproof(t1_2.a))
                      Filter: ((SubPlan 1) AND snoop(t1_2.a) AND leakproof(t1_2.a))
                ->  Index Scan using t12_a_idx on public.t12 t1_3
                      Output: t1_3.a, t1_3.tableoid, t1_3.ctid
                      Index Cond: ((t1_3.a > 5) AND (t1_3.a = 8))
+                     Index Filter: ((SubPlan 1) AND leakproof(t1_3.a))
                      Filter: ((SubPlan 1) AND snoop(t1_3.a) AND leakproof(t1_3.a))
                ->  Index Scan using t111_a_idx on public.t111 t1_4
                      Output: t1_4.a, t1_4.tableoid, t1_4.ctid
                      Index Cond: ((t1_4.a > 5) AND (t1_4.a = 8))
+                     Index Filter: ((SubPlan 1) AND leakproof(t1_4.a))
                      Filter: ((SubPlan 1) AND snoop(t1_4.a) AND leakproof(t1_4.a))
-(30 rows)
+(34 rows)
 
 UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
 NOTICE:  snooped value: 8
-- 
2.41.0

Reply via email to