diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index a43daa7..ef125b4 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -4785,6 +4785,179 @@ get_parallel_divisor(Path *path)
 }
 
 /*
+ * compute_index_pages
+ *
+ * compute number of pages fetched from index in index scan.
+ */
+double
+compute_index_pages(PlannerInfo *root, IndexOptInfo *index,
+					List *indexQuals, int loop_count, double numIndexTuples,
+					double *random_page_cost, double *sa_scans,
+					double *indexTuples, double *indexPages,
+					Selectivity *indexSel, Cost *cost)
+{
+	List	   *selectivityQuals;
+	double		pages_fetched;
+	double		num_sa_scans;
+	double		num_outer_scans;
+	double		num_scans;
+	double		spc_random_page_cost;
+	double		numIndexPages;
+	Selectivity indexSelectivity;
+	Cost		indexTotalCost;
+	ListCell   *l;
+
+	/*
+	 * If the index is partial, AND the index predicate with the explicitly
+	 * given indexquals to produce a more accurate idea of the index
+	 * selectivity.
+	 */
+	selectivityQuals = add_predicate_to_quals(index, indexQuals);
+
+	/*
+	 * Check for ScalarArrayOpExpr index quals, and estimate the number of
+	 * index scans that will be performed.
+	 */
+	num_sa_scans = 1;
+	foreach(l, indexQuals)
+	{
+		RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
+
+		if (IsA(rinfo->clause, ScalarArrayOpExpr))
+		{
+			ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) rinfo->clause;
+			int			alength = estimate_array_length(lsecond(saop->args));
+
+			if (alength > 1)
+				num_sa_scans *= alength;
+		}
+	}
+
+	/* Estimate the fraction of main-table tuples that will be visited */
+	indexSelectivity = clauselist_selectivity(root, selectivityQuals,
+											  index->rel->relid,
+											  JOIN_INNER,
+											  NULL);
+
+	/*
+	 * If caller didn't give us an estimate, estimate the number of index
+	 * tuples that will be visited.  We do it in this rather peculiar-looking
+	 * way in order to get the right answer for partial indexes.
+	 */
+	if (numIndexTuples <= 0.0)
+	{
+		numIndexTuples = indexSelectivity * index->rel->tuples;
+
+		/*
+		 * The above calculation counts all the tuples visited across all
+		 * scans induced by ScalarArrayOpExpr nodes.  We want to consider the
+		 * average per-indexscan number, so adjust.  This is a handy place to
+		 * round to integer, too.  (If caller supplied tuple estimate, it's
+		 * responsible for handling these considerations.)
+		 */
+		numIndexTuples = rint(numIndexTuples / num_sa_scans);
+	}
+
+	/*
+	 * We can bound the number of tuples by the index size in any case. Also,
+	 * always estimate at least one tuple is touched, even when
+	 * indexSelectivity estimate is tiny.
+	 */
+	if (numIndexTuples > index->tuples)
+		numIndexTuples = index->tuples;
+	if (numIndexTuples < 1.0)
+		numIndexTuples = 1.0;
+
+	/*
+	 * Estimate the number of index pages that will be retrieved.
+	 *
+	 * We use the simplistic method of taking a pro-rata fraction of the total
+	 * number of index pages.  In effect, this counts only leaf pages and not
+	 * any overhead such as index metapage or upper tree levels.
+	 *
+	 * In practice access to upper index levels is often nearly free because
+	 * those tend to stay in cache under load; moreover, the cost involved is
+	 * highly dependent on index type.  We therefore ignore such costs here
+	 * and leave it to the caller to add a suitable charge if needed.
+	 */
+	if (index->pages > 1 && index->tuples > 1)
+		numIndexPages = ceil(numIndexTuples * index->pages / index->tuples);
+	else
+		numIndexPages = 1.0;
+
+	/* fetch estimated page cost for tablespace containing index */
+	get_tablespace_page_costs(index->reltablespace,
+							  &spc_random_page_cost,
+							  NULL);
+
+	/*
+	 * Now compute the disk access costs.
+	 *
+	 * The above calculations are all per-index-scan.  However, if we are in a
+	 * nestloop inner scan, we can expect the scan to be repeated (with
+	 * different search keys) for each row of the outer relation.  Likewise,
+	 * ScalarArrayOpExpr quals result in multiple index scans.  This creates
+	 * the potential for cache effects to reduce the number of disk page
+	 * fetches needed.  We want to estimate the average per-scan I/O cost in
+	 * the presence of caching.
+	 *
+	 * We use the Mackert-Lohman formula (see costsize.c for details) to
+	 * estimate the total number of page fetches that occur.  While this
+	 * wasn't what it was designed for, it seems a reasonable model anyway.
+	 * Note that we are counting pages not tuples anymore, so we take N = T =
+	 * index size, as if there were one "tuple" per page.
+	 */
+	num_outer_scans = loop_count;
+	num_scans = num_sa_scans * num_outer_scans;
+
+	if (num_scans > 1)
+	{
+		/* total page fetches ignoring cache effects */
+		pages_fetched = numIndexPages * num_scans;
+
+		/* use Mackert and Lohman formula to adjust for cache effects */
+		pages_fetched = index_pages_fetched(pages_fetched,
+											index->pages,
+											(double) index->pages,
+											root);
+
+		/*
+		 * Now compute the total disk access cost, and then report a pro-rated
+		 * share for each outer scan.  (Don't pro-rate for ScalarArrayOpExpr,
+		 * since that's internal to the indexscan.)
+		 */
+		indexTotalCost = (pages_fetched * spc_random_page_cost)
+			/ num_outer_scans;
+	}
+	else
+	{
+		pages_fetched = numIndexPages;
+
+		/*
+		 * For a single index scan, we just charge spc_random_page_cost per
+		 * page touched.
+		 */
+		indexTotalCost = numIndexPages * spc_random_page_cost;
+	}
+
+	/* fill the output parameters */
+	if (random_page_cost)
+		*random_page_cost = spc_random_page_cost;
+	if (sa_scans)
+		*sa_scans = num_sa_scans;
+	if (indexTuples)
+		*indexTuples = numIndexTuples;
+	if (indexSel)
+		*indexSel = indexSelectivity;
+	if (indexPages)
+		*indexPages = numIndexPages;
+	if (cost)
+		*cost = indexTotalCost;
+
+	return pages_fetched;
+}
+
+/*
  * compute_bitmap_pages
  *
  * compute number of pages fetched from heap in bitmap heap scan.
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 5283468..5d22496 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -863,6 +863,8 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 	IndexPath  *ipath;
 	List	   *index_clauses;
 	List	   *clause_columns;
+	List	   *indexquals;
+	List	   *indexqualcols;
 	Relids		outer_relids;
 	double		loop_count;
 	List	   *orderbyclauses;
@@ -1014,6 +1016,11 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 		orderbyclausecols = NIL;
 	}
 
+	/* Convert clauses to indexquals the executor can handle */
+	expand_indexqual_conditions(index, index_clauses, clause_columns,
+								&indexquals, &indexqualcols);
+
+
 	/*
 	 * 3. Check if an index-only scan is possible.  If we're not building
 	 * plain indexscans, this isn't relevant since bitmap scans don't support
@@ -1034,6 +1041,8 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 		ipath = create_index_path(root, index,
 								  index_clauses,
 								  clause_columns,
+								  indexquals,
+								  indexqualcols,
 								  orderbyclauses,
 								  orderbyclausecols,
 								  useful_pathkeys,
@@ -1060,6 +1069,8 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 			ipath = create_index_path(root, index,
 									  index_clauses,
 									  clause_columns,
+									  indexquals,
+									  indexqualcols,
 									  NIL,
 									  NIL,
 									  useful_pathkeys,
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 881742f..f5366d8 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -5377,7 +5377,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, NIL, NIL, NIL,
 									  ForwardScanDirection, false,
 									  NULL, 1.0);
 
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index f440875..8cd80db 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1013,6 +1013,8 @@ create_index_path(PlannerInfo *root,
 				  IndexOptInfo *index,
 				  List *indexclauses,
 				  List *indexclausecols,
+				  List *indexquals,
+				  List *indexqualcols,
 				  List *indexorderbys,
 				  List *indexorderbycols,
 				  List *pathkeys,
@@ -1023,8 +1025,6 @@ create_index_path(PlannerInfo *root,
 {
 	IndexPath  *pathnode = makeNode(IndexPath);
 	RelOptInfo *rel = index->rel;
-	List	   *indexquals,
-			   *indexqualcols;
 
 	pathnode->path.pathtype = indexonly ? T_IndexOnlyScan : T_IndexScan;
 	pathnode->path.parent = rel;
@@ -1036,10 +1036,6 @@ create_index_path(PlannerInfo *root,
 	pathnode->path.parallel_workers = 0;
 	pathnode->path.pathkeys = pathkeys;
 
-	/* Convert clauses to indexquals the executor can handle */
-	expand_indexqual_conditions(index, indexclauses, indexclausecols,
-								&indexquals, &indexqualcols);
-
 	/* Fill in the pathnode */
 	pathnode->indexinfo = index;
 	pathnode->indexclauses = indexclauses;
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index fa32e9e..6762bf9 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -205,7 +205,6 @@ static Selectivity regex_selectivity(const char *patt, int pattlen,
 static Datum string_to_datum(const char *str, Oid datatype);
 static Const *string_to_const(const char *str, Oid datatype);
 static Const *string_to_bytea_const(const char *str, size_t str_len);
-static List *add_predicate_to_quals(IndexOptInfo *index, List *indexQuals);
 
 
 /*
@@ -6245,146 +6244,13 @@ genericcostestimate(PlannerInfo *root,
 	double		numIndexTuples;
 	double		spc_random_page_cost;
 	double		num_sa_scans;
-	double		num_outer_scans;
-	double		num_scans;
 	double		qual_op_cost;
 	double		qual_arg_cost;
-	List	   *selectivityQuals;
-	ListCell   *l;
-
-	/*
-	 * If the index is partial, AND the index predicate with the explicitly
-	 * given indexquals to produce a more accurate idea of the index
-	 * selectivity.
-	 */
-	selectivityQuals = add_predicate_to_quals(index, indexQuals);
-
-	/*
-	 * Check for ScalarArrayOpExpr index quals, and estimate the number of
-	 * index scans that will be performed.
-	 */
-	num_sa_scans = 1;
-	foreach(l, indexQuals)
-	{
-		RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
-
-		if (IsA(rinfo->clause, ScalarArrayOpExpr))
-		{
-			ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) rinfo->clause;
-			int			alength = estimate_array_length(lsecond(saop->args));
-
-			if (alength > 1)
-				num_sa_scans *= alength;
-		}
-	}
-
-	/* Estimate the fraction of main-table tuples that will be visited */
-	indexSelectivity = clauselist_selectivity(root, selectivityQuals,
-											  index->rel->relid,
-											  JOIN_INNER,
-											  NULL);
-
-	/*
-	 * If caller didn't give us an estimate, estimate the number of index
-	 * tuples that will be visited.  We do it in this rather peculiar-looking
-	 * way in order to get the right answer for partial indexes.
-	 */
-	numIndexTuples = costs->numIndexTuples;
-	if (numIndexTuples <= 0.0)
-	{
-		numIndexTuples = indexSelectivity * index->rel->tuples;
-
-		/*
-		 * The above calculation counts all the tuples visited across all
-		 * scans induced by ScalarArrayOpExpr nodes.  We want to consider the
-		 * average per-indexscan number, so adjust.  This is a handy place to
-		 * round to integer, too.  (If caller supplied tuple estimate, it's
-		 * responsible for handling these considerations.)
-		 */
-		numIndexTuples = rint(numIndexTuples / num_sa_scans);
-	}
-
-	/*
-	 * We can bound the number of tuples by the index size in any case. Also,
-	 * always estimate at least one tuple is touched, even when
-	 * indexSelectivity estimate is tiny.
-	 */
-	if (numIndexTuples > index->tuples)
-		numIndexTuples = index->tuples;
-	if (numIndexTuples < 1.0)
-		numIndexTuples = 1.0;
-
-	/*
-	 * Estimate the number of index pages that will be retrieved.
-	 *
-	 * We use the simplistic method of taking a pro-rata fraction of the total
-	 * number of index pages.  In effect, this counts only leaf pages and not
-	 * any overhead such as index metapage or upper tree levels.
-	 *
-	 * In practice access to upper index levels is often nearly free because
-	 * those tend to stay in cache under load; moreover, the cost involved is
-	 * highly dependent on index type.  We therefore ignore such costs here
-	 * and leave it to the caller to add a suitable charge if needed.
-	 */
-	if (index->pages > 1 && index->tuples > 1)
-		numIndexPages = ceil(numIndexTuples * index->pages / index->tuples);
-	else
-		numIndexPages = 1.0;
 
-	/* fetch estimated page cost for tablespace containing index */
-	get_tablespace_page_costs(index->reltablespace,
-							  &spc_random_page_cost,
-							  NULL);
-
-	/*
-	 * Now compute the disk access costs.
-	 *
-	 * The above calculations are all per-index-scan.  However, if we are in a
-	 * nestloop inner scan, we can expect the scan to be repeated (with
-	 * different search keys) for each row of the outer relation.  Likewise,
-	 * ScalarArrayOpExpr quals result in multiple index scans.  This creates
-	 * the potential for cache effects to reduce the number of disk page
-	 * fetches needed.  We want to estimate the average per-scan I/O cost in
-	 * the presence of caching.
-	 *
-	 * We use the Mackert-Lohman formula (see costsize.c for details) to
-	 * estimate the total number of page fetches that occur.  While this
-	 * wasn't what it was designed for, it seems a reasonable model anyway.
-	 * Note that we are counting pages not tuples anymore, so we take N = T =
-	 * index size, as if there were one "tuple" per page.
-	 */
-	num_outer_scans = loop_count;
-	num_scans = num_sa_scans * num_outer_scans;
-
-	if (num_scans > 1)
-	{
-		double		pages_fetched;
-
-		/* total page fetches ignoring cache effects */
-		pages_fetched = numIndexPages * num_scans;
-
-		/* use Mackert and Lohman formula to adjust for cache effects */
-		pages_fetched = index_pages_fetched(pages_fetched,
-											index->pages,
-											(double) index->pages,
-											root);
-
-		/*
-		 * Now compute the total disk access cost, and then report a pro-rated
-		 * share for each outer scan.  (Don't pro-rate for ScalarArrayOpExpr,
-		 * since that's internal to the indexscan.)
-		 */
-		indexTotalCost = (pages_fetched * spc_random_page_cost)
-			/ num_outer_scans;
-	}
-	else
-	{
-		/*
-		 * For a single index scan, we just charge spc_random_page_cost per
-		 * page touched.
-		 */
-		indexTotalCost = numIndexPages * spc_random_page_cost;
-	}
+	(void) compute_index_pages(root, index, indexQuals, loop_count,
+							   costs->numIndexTuples, &spc_random_page_cost,
+							   &num_sa_scans, &numIndexTuples, &numIndexPages,
+							   &indexSelectivity, &indexTotalCost);
 
 	/*
 	 * CPU cost: any complex expressions in the indexquals will need to be
@@ -6446,7 +6312,7 @@ genericcostestimate(PlannerInfo *root,
  * predicate_implied_by() and clauselist_selectivity(), but might be
  * problematic if the result were passed to other things.
  */
-static List *
+List *
 add_predicate_to_quals(IndexOptInfo *index, List *indexQuals)
 {
 	List	   *predExtraQuals = NIL;
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index 0e68264..8525917 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -183,6 +183,10 @@ extern void set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel,
 					   double cte_rows);
 extern void set_foreign_size_estimates(PlannerInfo *root, RelOptInfo *rel);
 extern PathTarget *set_pathtarget_cost_width(PlannerInfo *root, PathTarget *target);
+extern double compute_index_pages(PlannerInfo *root, IndexOptInfo *index,
+					List *indexclauses, int loop_count, double numIndexTuples,
+			 double *random_page_cost, double *sa_scans, double *indexTuples,
+					double *indexPages, Selectivity *indexSel, Cost *cost);
 extern double compute_bitmap_pages(PlannerInfo *root, RelOptInfo *baserel,
 				  Path *bitmapqual, int loop_count, Cost *cost, double *tuple);
 
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 7b41317..44e143c 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -41,6 +41,8 @@ extern IndexPath *create_index_path(PlannerInfo *root,
 				  IndexOptInfo *index,
 				  List *indexclauses,
 				  List *indexclausecols,
+				  List *indexquals,
+				  List *indexqualcols,
 				  List *indexorderbys,
 				  List *indexorderbycols,
 				  List *pathkeys,
diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h
index 9f9d2dc..b1ffca3 100644
--- a/src/include/utils/selfuncs.h
+++ b/src/include/utils/selfuncs.h
@@ -212,6 +212,7 @@ extern void genericcostestimate(PlannerInfo *root, IndexPath *path,
 					double loop_count,
 					List *qinfos,
 					GenericCosts *costs);
+extern List *add_predicate_to_quals(IndexOptInfo *index, List *indexQuals);
 
 /* Functions in array_selfuncs.c */
 
