From 6c54823efedc9aca94d53a6f70b9b3ab0b94be24 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Fri, 4 Nov 2022 13:49:55 -0400
Subject: [PATCH v4 1/4] Existing patch to split tab and idx

---
 src/backend/access/index/indexam.c            |   2 +-
 src/backend/catalog/heap.c                    |   7 +-
 src/backend/catalog/index.c                   |   4 +-
 src/backend/catalog/system_views.sql          |  62 +--
 src/backend/storage/buffer/bufmgr.c           |  12 +-
 src/backend/utils/activity/pgstat.c           |  28 +-
 src/backend/utils/activity/pgstat_relation.c  | 440 +++++++++++++-----
 src/backend/utils/adt/pgstatfuncs.c           | 244 +++++-----
 src/backend/utils/cache/relcache.c            |   8 +-
 src/include/catalog/pg_proc.dat               |  63 ++-
 src/include/pgstat.h                          | 133 ++++--
 src/include/utils/pgstat_internal.h           |  16 +-
 src/include/utils/rel.h                       |   5 +-
 src/test/isolation/expected/stats.out         |  76 +--
 src/test/isolation/specs/stats.spec           |   4 +-
 src/test/recovery/t/029_stats_restart.pl      |  23 +-
 .../recovery/t/030_stats_cleanup_replica.pl   |   2 +-
 src/test/regress/expected/rules.out           |  48 +-
 src/test/regress/expected/stats.out           | 131 +++++-
 src/test/regress/sql/stats.sql                |  91 +++-
 src/tools/pgindent/typedefs.list              |   3 +-
 21 files changed, 987 insertions(+), 415 deletions(-)

diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index fe80b8b0ba..a1defc6838 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -582,7 +582,7 @@ index_fetch_heap(IndexScanDesc scan, TupleTableSlot *slot)
 									&scan->xs_heap_continue, &all_dead);
 
 	if (found)
-		pgstat_count_heap_fetch(scan->indexRelation);
+		pgstat_count_index_fetch(scan->indexRelation);
 
 	/*
 	 * If we scanned a whole HOT chain and found only dead tuples, tell index
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 5b49cc5a09..5fd42fa189 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -404,7 +404,10 @@ heap_create(const char *relname,
 									 reltablespace);
 
 	/* ensure that stats are dropped if transaction aborts */
-	pgstat_create_relation(rel);
+	if (rel->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_create_index(rel);
+	else
+		pgstat_create_table(rel);
 
 	return rel;
 }
@@ -1853,7 +1856,7 @@ heap_drop_with_catalog(Oid relid)
 		RelationDropStorage(rel);
 
 	/* ensure that stats are dropped if transaction commits */
-	pgstat_drop_relation(rel);
+	pgstat_drop_table(rel);
 
 	/*
 	 * Close relcache entry, but *keep* AccessExclusiveLock on the relation
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 61f1d3926a..28b94fef7f 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1752,7 +1752,7 @@ index_concurrently_swap(Oid newIndexId, Oid oldIndexId, const char *oldName)
 	changeDependenciesOn(RelationRelationId, oldIndexId, newIndexId);
 
 	/* copy over statistics from old to new index */
-	pgstat_copy_relation_stats(newClassRel, oldClassRel);
+	pgstat_copy_index_stats(newClassRel, oldClassRel);
 
 	/* Copy data of pg_statistic from the old index to the new one */
 	CopyStatistics(oldIndexId, newIndexId);
@@ -2326,7 +2326,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 		RelationDropStorage(userIndexRelation);
 
 	/* ensure that stats are dropped if transaction commits */
-	pgstat_drop_relation(userIndexRelation);
+	pgstat_drop_index(userIndexRelation);
 
 	/*
 	 * Close and flush the index's relcache entry, to ensure relcache doesn't
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2d8104b090..6f5153bf66 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -655,13 +655,13 @@ CREATE VIEW pg_stat_all_tables AS
             C.oid AS relid,
             N.nspname AS schemaname,
             C.relname AS relname,
-            pg_stat_get_numscans(C.oid) AS seq_scan,
-            pg_stat_get_lastscan(C.oid) AS last_seq_scan,
-            pg_stat_get_tuples_returned(C.oid) AS seq_tup_read,
-            sum(pg_stat_get_numscans(I.indexrelid))::bigint AS idx_scan,
-            max(pg_stat_get_lastscan(I.indexrelid)) AS last_idx_scan,
-            sum(pg_stat_get_tuples_fetched(I.indexrelid))::bigint +
-            pg_stat_get_tuples_fetched(C.oid) AS idx_tup_fetch,
+            pg_stat_get_table_numscans(C.oid) AS seq_scan,
+            pg_stat_get_table_lastscan(C.oid) AS last_seq_scan,
+            pg_stat_get_table_tuples_returned(C.oid) AS seq_tup_read,
+            sum(pg_stat_get_index_numscans(I.indexrelid))::bigint AS idx_scan,
+            max(pg_stat_get_index_lastscan(I.indexrelid)) AS last_idx_scan,
+            sum(pg_stat_get_index_tuples_fetched(I.indexrelid))::bigint +
+            pg_stat_get_table_tuples_fetched(C.oid) AS idx_tup_fetch,
             pg_stat_get_tuples_inserted(C.oid) AS n_tup_ins,
             pg_stat_get_tuples_updated(C.oid) AS n_tup_upd,
             pg_stat_get_tuples_deleted(C.oid) AS n_tup_del,
@@ -689,9 +689,9 @@ CREATE VIEW pg_stat_xact_all_tables AS
             C.oid AS relid,
             N.nspname AS schemaname,
             C.relname AS relname,
-            pg_stat_get_xact_numscans(C.oid) AS seq_scan,
+            pg_stat_get_table_xact_numscans(C.oid) AS seq_scan,
             pg_stat_get_xact_tuples_returned(C.oid) AS seq_tup_read,
-            sum(pg_stat_get_xact_numscans(I.indexrelid))::bigint AS idx_scan,
+            sum(pg_stat_get_index_xact_numscans(I.indexrelid))::bigint AS idx_scan,
             sum(pg_stat_get_xact_tuples_fetched(I.indexrelid))::bigint +
             pg_stat_get_xact_tuples_fetched(C.oid) AS idx_tup_fetch,
             pg_stat_get_xact_tuples_inserted(C.oid) AS n_tup_ins,
@@ -729,31 +729,31 @@ CREATE VIEW pg_statio_all_tables AS
             C.oid AS relid,
             N.nspname AS schemaname,
             C.relname AS relname,
-            pg_stat_get_blocks_fetched(C.oid) -
-                    pg_stat_get_blocks_hit(C.oid) AS heap_blks_read,
-            pg_stat_get_blocks_hit(C.oid) AS heap_blks_hit,
+            pg_stat_get_table_blocks_fetched(C.oid) -
+                    pg_stat_get_table_blocks_hit(C.oid) AS heap_blks_read,
+            pg_stat_get_table_blocks_hit(C.oid) AS heap_blks_hit,
             I.idx_blks_read AS idx_blks_read,
             I.idx_blks_hit AS idx_blks_hit,
-            pg_stat_get_blocks_fetched(T.oid) -
-                    pg_stat_get_blocks_hit(T.oid) AS toast_blks_read,
-            pg_stat_get_blocks_hit(T.oid) AS toast_blks_hit,
+            pg_stat_get_table_blocks_fetched(T.oid) -
+                    pg_stat_get_table_blocks_hit(T.oid) AS toast_blks_read,
+            pg_stat_get_table_blocks_hit(T.oid) AS toast_blks_hit,
             X.idx_blks_read AS tidx_blks_read,
             X.idx_blks_hit AS tidx_blks_hit
     FROM pg_class C LEFT JOIN
             pg_class T ON C.reltoastrelid = T.oid
             LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
             LEFT JOIN LATERAL (
-              SELECT sum(pg_stat_get_blocks_fetched(indexrelid) -
-                         pg_stat_get_blocks_hit(indexrelid))::bigint
+              SELECT sum(pg_stat_get_index_blocks_fetched(indexrelid) -
+                         pg_stat_get_index_blocks_hit(indexrelid))::bigint
                      AS idx_blks_read,
-                     sum(pg_stat_get_blocks_hit(indexrelid))::bigint
+                     sum(pg_stat_get_index_blocks_hit(indexrelid))::bigint
                      AS idx_blks_hit
               FROM pg_index WHERE indrelid = C.oid ) I ON true
             LEFT JOIN LATERAL (
-              SELECT sum(pg_stat_get_blocks_fetched(indexrelid) -
-                         pg_stat_get_blocks_hit(indexrelid))::bigint
+              SELECT sum(pg_stat_get_index_blocks_fetched(indexrelid) -
+                         pg_stat_get_index_blocks_hit(indexrelid))::bigint
                      AS idx_blks_read,
-                     sum(pg_stat_get_blocks_hit(indexrelid))::bigint
+                     sum(pg_stat_get_index_blocks_hit(indexrelid))::bigint
                      AS idx_blks_hit
               FROM pg_index WHERE indrelid = T.oid ) X ON true
     WHERE C.relkind IN ('r', 't', 'm');
@@ -775,10 +775,10 @@ CREATE VIEW pg_stat_all_indexes AS
             N.nspname AS schemaname,
             C.relname AS relname,
             I.relname AS indexrelname,
-            pg_stat_get_numscans(I.oid) AS idx_scan,
-            pg_stat_get_lastscan(I.oid) AS last_idx_scan,
-            pg_stat_get_tuples_returned(I.oid) AS idx_tup_read,
-            pg_stat_get_tuples_fetched(I.oid) AS idx_tup_fetch
+            pg_stat_get_index_numscans(I.oid) AS idx_scan,
+            pg_stat_get_index_lastscan(I.oid) AS last_idx_scan,
+            pg_stat_get_index_tuples_returned(I.oid) AS idx_tup_read,
+            pg_stat_get_index_tuples_fetched(I.oid) AS idx_tup_fetch
     FROM pg_class C JOIN
             pg_index X ON C.oid = X.indrelid JOIN
             pg_class I ON I.oid = X.indexrelid
@@ -802,9 +802,9 @@ CREATE VIEW pg_statio_all_indexes AS
             N.nspname AS schemaname,
             C.relname AS relname,
             I.relname AS indexrelname,
-            pg_stat_get_blocks_fetched(I.oid) -
-                    pg_stat_get_blocks_hit(I.oid) AS idx_blks_read,
-            pg_stat_get_blocks_hit(I.oid) AS idx_blks_hit
+            pg_stat_get_index_blocks_fetched(I.oid) -
+                    pg_stat_get_index_blocks_hit(I.oid) AS idx_blks_read,
+            pg_stat_get_index_blocks_hit(I.oid) AS idx_blks_hit
     FROM pg_class C JOIN
             pg_index X ON C.oid = X.indrelid JOIN
             pg_class I ON I.oid = X.indexrelid
@@ -826,9 +826,9 @@ CREATE VIEW pg_statio_all_sequences AS
             C.oid AS relid,
             N.nspname AS schemaname,
             C.relname AS relname,
-            pg_stat_get_blocks_fetched(C.oid) -
-                    pg_stat_get_blocks_hit(C.oid) AS blks_read,
-            pg_stat_get_blocks_hit(C.oid) AS blks_hit
+            pg_stat_get_index_blocks_fetched(C.oid) -
+                    pg_stat_get_table_blocks_hit(C.oid) AS blks_read,
+            pg_stat_get_table_blocks_hit(C.oid) AS blks_hit
     FROM pg_class C
             LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
     WHERE C.relkind = 'S';
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 73d30bf619..f1a7a8aa8d 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -776,11 +776,19 @@ ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum,
 	 * Read the buffer, and update pgstat counters to reflect a cache hit or
 	 * miss.
 	 */
-	pgstat_count_buffer_read(reln);
+	if (reln->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_count_index_buffer_read(reln);
+	else
+		pgstat_count_table_buffer_read(reln);
 	buf = ReadBuffer_common(RelationGetSmgr(reln), reln->rd_rel->relpersistence,
 							forkNum, blockNum, mode, strategy, &hit);
 	if (hit)
-		pgstat_count_buffer_hit(reln);
+	{
+		if (reln->rd_rel->relkind == RELKIND_INDEX)
+			pgstat_count_index_buffer_hit(reln);
+		else
+			pgstat_count_table_buffer_hit(reln);
+	}
 	return buf;
 }
 
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index 1ebe3bbf29..9349d7f00c 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -269,18 +269,32 @@ static const PgStat_KindInfo pgstat_kind_infos[PGSTAT_NUM_KINDS] = {
 		.reset_timestamp_cb = pgstat_database_reset_timestamp_cb,
 	},
 
-	[PGSTAT_KIND_RELATION] = {
-		.name = "relation",
+	[PGSTAT_KIND_TABLE] = {
+		.name = "table",
 
 		.fixed_amount = false,
 
-		.shared_size = sizeof(PgStatShared_Relation),
-		.shared_data_off = offsetof(PgStatShared_Relation, stats),
-		.shared_data_len = sizeof(((PgStatShared_Relation *) 0)->stats),
+		.shared_size = sizeof(PgStatShared_Table),
+		.shared_data_off = offsetof(PgStatShared_Table, stats),
+		.shared_data_len = sizeof(((PgStatShared_Table *) 0)->stats),
 		.pending_size = sizeof(PgStat_TableStatus),
 
-		.flush_pending_cb = pgstat_relation_flush_cb,
-		.delete_pending_cb = pgstat_relation_delete_pending_cb,
+		.flush_pending_cb = pgstat_table_flush_cb,
+		.delete_pending_cb = pgstat_table_delete_pending_cb,
+	},
+
+	[PGSTAT_KIND_INDEX] = {
+		.name = "index",
+
+		.fixed_amount = false,
+
+		.shared_size = sizeof(PgStatShared_Index),
+		.shared_data_off = offsetof(PgStatShared_Index, stats),
+		.shared_data_len = sizeof(((PgStatShared_Index *) 0)->stats),
+		.pending_size = sizeof(PgStat_IndexStatus),
+
+		.flush_pending_cb = pgstat_index_flush_cb,
+		.delete_pending_cb = pgstat_index_delete_pending_cb,
 	},
 
 	[PGSTAT_KIND_FUNCTION] = {
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c
index 55a355f583..13cdd7f849 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_relation.c
@@ -43,35 +43,35 @@ typedef struct TwoPhasePgStatRecord
 } TwoPhasePgStatRecord;
 
 
-static PgStat_TableStatus *pgstat_prep_relation_pending(Oid rel_id, bool isshared);
-static void add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level);
-static void ensure_tabstat_xact_level(PgStat_TableStatus *pgstat_info);
+static PgStat_TableStatus *pgstat_prep_table_pending(Oid rel_id, bool isshared);
+static PgStat_IndexStatus *pgstat_prep_index_pending(Oid rel_id, bool isshared);
+static void add_tabstat_xact_level(PgStat_TableStatus *pgstattab_info, int nest_level);
+static void ensure_tabstat_xact_level(PgStat_TableStatus *pgstattab_info);
 static void save_truncdrop_counters(PgStat_TableXactStatus *trans, bool is_drop);
 static void restore_truncdrop_counters(PgStat_TableXactStatus *trans);
 
-
 /*
- * Copy stats between relations. This is used for things like REINDEX
+ * Copy stats between indexes. This is used for things like REINDEX
  * CONCURRENTLY.
  */
 void
-pgstat_copy_relation_stats(Relation dst, Relation src)
+pgstat_copy_index_stats(Relation dst, Relation src)
 {
-	PgStat_StatTabEntry *srcstats;
-	PgStatShared_Relation *dstshstats;
+	PgStat_StatIndEntry *srcstats;
+	PgStatShared_Index *dstshstats;
 	PgStat_EntryRef *dst_ref;
 
-	srcstats = pgstat_fetch_stat_tabentry_ext(src->rd_rel->relisshared,
+	srcstats = pgstat_fetch_stat_indentry_ext(src->rd_rel->relisshared,
 											  RelationGetRelid(src));
 	if (!srcstats)
 		return;
 
-	dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
+	dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_INDEX,
 										  dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
 										  RelationGetRelid(dst),
 										  false);
 
-	dstshstats = (PgStatShared_Relation *) dst_ref->shared_stats;
+	dstshstats = (PgStatShared_Index *) dst_ref->shared_stats;
 	dstshstats->stats = *srcstats;
 
 	pgstat_unlock_entry(dst_ref);
@@ -81,8 +81,9 @@ pgstat_copy_relation_stats(Relation dst, Relation src)
  * Initialize a relcache entry to count access statistics.  Called whenever a
  * relation is opened.
  *
- * We assume that a relcache entry's pgstat_info field is zeroed by relcache.c
- * when the relcache entry is made; thereafter it is long-lived data.
+ * We assume that a relcache entry's pgstattab_info and pgstatind_info fields
+ * are zeroed by relcache.c when the relcache entry is made; thereafter it is
+ * long-lived data.
  *
  * This does not create a reference to a stats entry in shared memory, nor
  * allocate memory for the pending stats. That happens in
@@ -99,18 +100,20 @@ pgstat_init_relation(Relation rel)
 	if (!RELKIND_HAS_STORAGE(relkind) && relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		rel->pgstat_enabled = false;
-		rel->pgstat_info = NULL;
+		rel->pgstattab_info = NULL;
+		rel->pgstatind_info = NULL;
 		return;
 	}
 
 	if (!pgstat_track_counts)
 	{
-		if (rel->pgstat_info)
+		if (rel->pgstattab_info != NULL || rel->pgstatind_info != NULL)
 			pgstat_unlink_relation(rel);
 
 		/* We're not counting at all */
 		rel->pgstat_enabled = false;
-		rel->pgstat_info = NULL;
+		rel->pgstattab_info = NULL;
+		rel->pgstatind_info = NULL;
 		return;
 	}
 
@@ -118,10 +121,10 @@ pgstat_init_relation(Relation rel)
 }
 
 /*
- * Prepare for statistics for this relation to be collected.
+ * Prepare for statistics for this table to be collected.
  *
  * This ensures we have a reference to the stats entry before stats can be
- * generated. That is important because a relation drop in another connection
+ * generated. That is important because a table drop in another connection
  * could otherwise lead to the stats entry being dropped, which then later
  * would get recreated when flushing stats.
  *
@@ -129,20 +132,48 @@ pgstat_init_relation(Relation rel)
  * relcache entries to be opened without ever getting stats reported.
  */
 void
-pgstat_assoc_relation(Relation rel)
+pgstat_assoc_table(Relation rel)
 {
 	Assert(rel->pgstat_enabled);
-	Assert(rel->pgstat_info == NULL);
+	Assert(rel->pgstattab_info == NULL);
 
 	/* Else find or make the PgStat_TableStatus entry, and update link */
-	rel->pgstat_info = pgstat_prep_relation_pending(RelationGetRelid(rel),
+	rel->pgstattab_info = pgstat_prep_table_pending(RelationGetRelid(rel),
+													rel->rd_rel->relisshared);
+
+	/* don't allow link a stats to multiple relcache entries */
+	Assert(rel->pgstattab_info->relation == NULL);
+
+	/* mark this relation as the owner */
+	rel->pgstattab_info->relation = rel;
+}
+
+/*
+ * Prepare for statistics for this index to be collected.
+ *
+ * This ensures we have a reference to the stats entry before stats can be
+ * generated. That is important because an index drop in another
+ * connection could otherwise lead to the stats entry being dropped, which then
+ * later would get recreated when flushing stats.
+ *
+ * This is separate from pgstat_init_relation() as it is not uncommon for
+ * relcache entries to be opened without ever getting stats reported.
+ */
+void
+pgstat_assoc_index(Relation rel)
+{
+	Assert(rel->pgstat_enabled);
+	Assert(rel->pgstatind_info == NULL);
+
+	/* Else find or make the PgStat_IndexStatus entry, and update link */
+	rel->pgstatind_info = pgstat_prep_index_pending(RelationGetRelid(rel),
 													rel->rd_rel->relisshared);
 
 	/* don't allow link a stats to multiple relcache entries */
-	Assert(rel->pgstat_info->relation == NULL);
+	Assert(rel->pgstatind_info->relation == NULL);
 
 	/* mark this relation as the owner */
-	rel->pgstat_info->relation = rel;
+	rel->pgstatind_info->relation = rel;
 }
 
 /*
@@ -152,55 +183,88 @@ pgstat_assoc_relation(Relation rel)
 void
 pgstat_unlink_relation(Relation rel)
 {
-	/* remove the link to stats info if any */
-	if (rel->pgstat_info == NULL)
+
+	if (rel->pgstatind_info == NULL && rel->pgstattab_info == NULL)
 		return;
 
-	/* link sanity check */
-	Assert(rel->pgstat_info->relation == rel);
-	rel->pgstat_info->relation = NULL;
-	rel->pgstat_info = NULL;
+	/* link sanity check for the table stats */
+	if (rel->pgstattab_info)
+	{
+		Assert(rel->pgstattab_info->relation == rel);
+		rel->pgstattab_info->relation = NULL;
+		rel->pgstattab_info = NULL;
+	}
+
+	/* link sanity check for the index stats */
+	if (rel->pgstatind_info)
+	{
+		Assert(rel->pgstatind_info->relation == rel);
+		rel->pgstatind_info->relation = NULL;
+		rel->pgstatind_info = NULL;
+	}
+}
+
+/*
+ * Ensure that table stats are dropped if transaction aborts.
+ */
+void
+pgstat_create_table(Relation rel)
+{
+	pgstat_create_transactional(PGSTAT_KIND_TABLE,
+								rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+								RelationGetRelid(rel));
 }
 
 /*
- * Ensure that stats are dropped if transaction aborts.
+ * Ensure that index stats are dropped if transaction aborts.
  */
 void
-pgstat_create_relation(Relation rel)
+pgstat_create_index(Relation rel)
 {
-	pgstat_create_transactional(PGSTAT_KIND_RELATION,
+	pgstat_create_transactional(PGSTAT_KIND_INDEX,
 								rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
 								RelationGetRelid(rel));
 }
 
 /*
- * Ensure that stats are dropped if transaction commits.
+ * Ensure that index stats are dropped if transaction commits.
  */
 void
-pgstat_drop_relation(Relation rel)
+pgstat_drop_index(Relation rel)
+{
+	pgstat_drop_transactional(PGSTAT_KIND_INDEX,
+							  rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+							  RelationGetRelid(rel));
+}
+
+/*
+ * Ensure that table stats are dropped if transaction commits.
+ */
+void
+pgstat_drop_table(Relation rel)
 {
 	int			nest_level = GetCurrentTransactionNestLevel();
-	PgStat_TableStatus *pgstat_info;
+	PgStat_TableStatus *pgstattab_info;
 
-	pgstat_drop_transactional(PGSTAT_KIND_RELATION,
+	pgstat_drop_transactional(PGSTAT_KIND_TABLE,
 							  rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
 							  RelationGetRelid(rel));
 
-	if (!pgstat_should_count_relation(rel))
+	if (!pgstat_should_count_table(rel))
 		return;
 
 	/*
 	 * Transactionally set counters to 0. That ensures that accesses to
 	 * pg_stat_xact_all_tables inside the transaction show 0.
 	 */
-	pgstat_info = rel->pgstat_info;
-	if (pgstat_info->trans &&
-		pgstat_info->trans->nest_level == nest_level)
+	pgstattab_info = rel->pgstattab_info;
+	if (pgstattab_info->trans &&
+		pgstattab_info->trans->nest_level == nest_level)
 	{
-		save_truncdrop_counters(pgstat_info->trans, true);
-		pgstat_info->trans->tuples_inserted = 0;
-		pgstat_info->trans->tuples_updated = 0;
-		pgstat_info->trans->tuples_deleted = 0;
+		save_truncdrop_counters(pgstattab_info->trans, true);
+		pgstattab_info->trans->tuples_inserted = 0;
+		pgstattab_info->trans->tuples_updated = 0;
+		pgstattab_info->trans->tuples_deleted = 0;
 	}
 }
 
@@ -212,7 +276,7 @@ pgstat_report_vacuum(Oid tableoid, bool shared,
 					 PgStat_Counter livetuples, PgStat_Counter deadtuples)
 {
 	PgStat_EntryRef *entry_ref;
-	PgStatShared_Relation *shtabentry;
+	PgStatShared_Table *shtabentry;
 	PgStat_StatTabEntry *tabentry;
 	Oid			dboid = (shared ? InvalidOid : MyDatabaseId);
 	TimestampTz ts;
@@ -224,10 +288,10 @@ pgstat_report_vacuum(Oid tableoid, bool shared,
 	ts = GetCurrentTimestamp();
 
 	/* block acquiring lock for the same reason as pgstat_report_autovac() */
-	entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
+	entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_TABLE,
 											dboid, tableoid, false);
 
-	shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
+	shtabentry = (PgStatShared_Table *) entry_ref->shared_stats;
 	tabentry = &shtabentry->stats;
 
 	tabentry->n_live_tuples = livetuples;
@@ -271,7 +335,7 @@ pgstat_report_analyze(Relation rel,
 					  bool resetcounter)
 {
 	PgStat_EntryRef *entry_ref;
-	PgStatShared_Relation *shtabentry;
+	PgStatShared_Table *shtabentry;
 	PgStat_StatTabEntry *tabentry;
 	Oid			dboid = (rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId);
 
@@ -290,31 +354,31 @@ pgstat_report_analyze(Relation rel,
 	 *
 	 * Waste no time on partitioned tables, though.
 	 */
-	if (pgstat_should_count_relation(rel) &&
+	if (pgstat_should_count_table(rel) &&
 		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		PgStat_TableXactStatus *trans;
 
-		for (trans = rel->pgstat_info->trans; trans; trans = trans->upper)
+		for (trans = rel->pgstattab_info->trans; trans; trans = trans->upper)
 		{
 			livetuples -= trans->tuples_inserted - trans->tuples_deleted;
 			deadtuples -= trans->tuples_updated + trans->tuples_deleted;
 		}
 		/* count stuff inserted by already-aborted subxacts, too */
-		deadtuples -= rel->pgstat_info->t_counts.t_delta_dead_tuples;
+		deadtuples -= rel->pgstattab_info->t_counts.t_delta_dead_tuples;
 		/* Since ANALYZE's counts are estimates, we could have underflowed */
 		livetuples = Max(livetuples, 0);
 		deadtuples = Max(deadtuples, 0);
 	}
 
 	/* block acquiring lock for the same reason as pgstat_report_autovac() */
-	entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, dboid,
+	entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_TABLE, dboid,
 											RelationGetRelid(rel),
 											false);
 	/* can't get dropped while accessed */
 	Assert(entry_ref != NULL && entry_ref->shared_stats != NULL);
 
-	shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
+	shtabentry = (PgStatShared_Table *) entry_ref->shared_stats;
 	tabentry = &shtabentry->stats;
 
 	tabentry->n_live_tuples = livetuples;
@@ -348,12 +412,12 @@ pgstat_report_analyze(Relation rel,
 void
 pgstat_count_heap_insert(Relation rel, PgStat_Counter n)
 {
-	if (pgstat_should_count_relation(rel))
+	if (pgstat_should_count_table(rel))
 	{
-		PgStat_TableStatus *pgstat_info = rel->pgstat_info;
+		PgStat_TableStatus *pgstattab_info = rel->pgstattab_info;
 
-		ensure_tabstat_xact_level(pgstat_info);
-		pgstat_info->trans->tuples_inserted += n;
+		ensure_tabstat_xact_level(pgstattab_info);
+		pgstattab_info->trans->tuples_inserted += n;
 	}
 }
 
@@ -363,16 +427,16 @@ pgstat_count_heap_insert(Relation rel, PgStat_Counter n)
 void
 pgstat_count_heap_update(Relation rel, bool hot)
 {
-	if (pgstat_should_count_relation(rel))
+	if (pgstat_should_count_table(rel))
 	{
-		PgStat_TableStatus *pgstat_info = rel->pgstat_info;
+		PgStat_TableStatus *pgstattab_info = rel->pgstattab_info;
 
-		ensure_tabstat_xact_level(pgstat_info);
-		pgstat_info->trans->tuples_updated++;
+		ensure_tabstat_xact_level(pgstattab_info);
+		pgstattab_info->trans->tuples_updated++;
 
 		/* t_tuples_hot_updated is nontransactional, so just advance it */
 		if (hot)
-			pgstat_info->t_counts.t_tuples_hot_updated++;
+			pgstattab_info->t_counts.t_tuples_hot_updated++;
 	}
 }
 
@@ -382,12 +446,12 @@ pgstat_count_heap_update(Relation rel, bool hot)
 void
 pgstat_count_heap_delete(Relation rel)
 {
-	if (pgstat_should_count_relation(rel))
+	if (pgstat_should_count_table(rel))
 	{
-		PgStat_TableStatus *pgstat_info = rel->pgstat_info;
+		PgStat_TableStatus *pgstattab_info = rel->pgstattab_info;
 
-		ensure_tabstat_xact_level(pgstat_info);
-		pgstat_info->trans->tuples_deleted++;
+		ensure_tabstat_xact_level(pgstattab_info);
+		pgstattab_info->trans->tuples_deleted++;
 	}
 }
 
@@ -397,15 +461,15 @@ pgstat_count_heap_delete(Relation rel)
 void
 pgstat_count_truncate(Relation rel)
 {
-	if (pgstat_should_count_relation(rel))
+	if (pgstat_should_count_table(rel))
 	{
-		PgStat_TableStatus *pgstat_info = rel->pgstat_info;
+		PgStat_TableStatus *pgstattab_info = rel->pgstattab_info;
 
-		ensure_tabstat_xact_level(pgstat_info);
-		save_truncdrop_counters(pgstat_info->trans, false);
-		pgstat_info->trans->tuples_inserted = 0;
-		pgstat_info->trans->tuples_updated = 0;
-		pgstat_info->trans->tuples_deleted = 0;
+		ensure_tabstat_xact_level(pgstattab_info);
+		save_truncdrop_counters(pgstattab_info->trans, false);
+		pgstattab_info->trans->tuples_inserted = 0;
+		pgstattab_info->trans->tuples_updated = 0;
+		pgstattab_info->trans->tuples_deleted = 0;
 	}
 }
 
@@ -420,11 +484,11 @@ pgstat_count_truncate(Relation rel)
 void
 pgstat_update_heap_dead_tuples(Relation rel, int delta)
 {
-	if (pgstat_should_count_relation(rel))
+	if (pgstat_should_count_table(rel))
 	{
-		PgStat_TableStatus *pgstat_info = rel->pgstat_info;
+		PgStat_TableStatus *pgstattab_info = rel->pgstattab_info;
 
-		pgstat_info->t_counts.t_delta_dead_tuples -= delta;
+		pgstattab_info->t_counts.t_delta_dead_tuples -= delta;
 	}
 }
 
@@ -460,7 +524,42 @@ pgstat_fetch_stat_tabentry_ext(bool shared, Oid reloid)
 	Oid			dboid = (shared ? InvalidOid : MyDatabaseId);
 
 	return (PgStat_StatTabEntry *)
-		pgstat_fetch_entry(PGSTAT_KIND_RELATION, dboid, reloid);
+		pgstat_fetch_entry(PGSTAT_KIND_TABLE, dboid, reloid);
+}
+
+/*
+ * Support function for the SQL-callable pgstat* functions. Returns
+ * the collected statistics for one index or NULL. NULL doesn't mean
+ * that the index doesn't exist, just that there are no statistics, so the
+ * caller is better off to report ZERO instead.
+ */
+PgStat_StatIndEntry *
+pgstat_fetch_stat_indentry(Oid relid)
+{
+	PgStat_StatIndEntry *indentry;
+
+	indentry = pgstat_fetch_stat_indentry_ext(false, relid);
+	if (indentry != NULL)
+		return indentry;
+
+	/*
+	 * If we didn't find it, maybe it's a shared index.
+	 */
+	indentry = pgstat_fetch_stat_indentry_ext(true, relid);
+	return indentry;
+}
+
+/*
+ * More efficient version of pgstat_fetch_stat_indentry(), allowing to specify
+ * whether the to-be-accessed index is shared or not.
+ */
+PgStat_StatIndEntry *
+pgstat_fetch_stat_indentry_ext(bool shared, Oid reloid)
+{
+	Oid			dboid = (shared ? InvalidOid : MyDatabaseId);
+
+	return (PgStat_StatIndEntry *)
+		pgstat_fetch_entry(PGSTAT_KIND_INDEX, dboid, reloid);
 }
 
 /*
@@ -476,9 +575,31 @@ find_tabstat_entry(Oid rel_id)
 {
 	PgStat_EntryRef *entry_ref;
 
-	entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_RELATION, MyDatabaseId, rel_id);
+	entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_TABLE, MyDatabaseId, rel_id);
 	if (!entry_ref)
-		entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_RELATION, InvalidOid, rel_id);
+		entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_TABLE, InvalidOid, rel_id);
+
+	if (entry_ref)
+		return entry_ref->pending;
+	return NULL;
+}
+
+/*
+ * find any existing PgStat_IndexStatus entry for rel
+ *
+ * Find any existing PgStat_IndexStatus entry for rel_id in the current
+ * database. If not found, try finding from shared indexes.
+ *
+ * If no entry found, return NULL, don't create a new one
+ */
+PgStat_IndexStatus *
+find_indstat_entry(Oid rel_id)
+{
+	PgStat_EntryRef *entry_ref;
+
+	entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_INDEX, MyDatabaseId, rel_id);
+	if (!entry_ref)
+		entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_INDEX, InvalidOid, rel_id);
 
 	if (entry_ref)
 		return entry_ref->pending;
@@ -694,27 +815,27 @@ pgstat_twophase_postcommit(TransactionId xid, uint16 info,
 						   void *recdata, uint32 len)
 {
 	TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *) recdata;
-	PgStat_TableStatus *pgstat_info;
+	PgStat_TableStatus *pgstattab_info;
 
 	/* Find or create a tabstat entry for the rel */
-	pgstat_info = pgstat_prep_relation_pending(rec->t_id, rec->t_shared);
+	pgstattab_info = pgstat_prep_table_pending(rec->t_id, rec->t_shared);
 
 	/* Same math as in AtEOXact_PgStat, commit case */
-	pgstat_info->t_counts.t_tuples_inserted += rec->tuples_inserted;
-	pgstat_info->t_counts.t_tuples_updated += rec->tuples_updated;
-	pgstat_info->t_counts.t_tuples_deleted += rec->tuples_deleted;
-	pgstat_info->t_counts.t_truncdropped = rec->t_truncdropped;
+	pgstattab_info->t_counts.t_tuples_inserted += rec->tuples_inserted;
+	pgstattab_info->t_counts.t_tuples_updated += rec->tuples_updated;
+	pgstattab_info->t_counts.t_tuples_deleted += rec->tuples_deleted;
+	pgstattab_info->t_counts.t_truncdropped = rec->t_truncdropped;
 	if (rec->t_truncdropped)
 	{
 		/* forget live/dead stats seen by backend thus far */
-		pgstat_info->t_counts.t_delta_live_tuples = 0;
-		pgstat_info->t_counts.t_delta_dead_tuples = 0;
+		pgstattab_info->t_counts.t_delta_live_tuples = 0;
+		pgstattab_info->t_counts.t_delta_dead_tuples = 0;
 	}
-	pgstat_info->t_counts.t_delta_live_tuples +=
+	pgstattab_info->t_counts.t_delta_live_tuples +=
 		rec->tuples_inserted - rec->tuples_deleted;
-	pgstat_info->t_counts.t_delta_dead_tuples +=
+	pgstattab_info->t_counts.t_delta_dead_tuples +=
 		rec->tuples_updated + rec->tuples_deleted;
-	pgstat_info->t_counts.t_changed_tuples +=
+	pgstattab_info->t_counts.t_changed_tuples +=
 		rec->tuples_inserted + rec->tuples_updated +
 		rec->tuples_deleted;
 }
@@ -730,10 +851,10 @@ pgstat_twophase_postabort(TransactionId xid, uint16 info,
 						  void *recdata, uint32 len)
 {
 	TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *) recdata;
-	PgStat_TableStatus *pgstat_info;
+	PgStat_TableStatus *pgstattab_info;
 
 	/* Find or create a tabstat entry for the rel */
-	pgstat_info = pgstat_prep_relation_pending(rec->t_id, rec->t_shared);
+	pgstattab_info = pgstat_prep_table_pending(rec->t_id, rec->t_shared);
 
 	/* Same math as in AtEOXact_PgStat, abort case */
 	if (rec->t_truncdropped)
@@ -742,10 +863,10 @@ pgstat_twophase_postabort(TransactionId xid, uint16 info,
 		rec->tuples_updated = rec->updated_pre_truncdrop;
 		rec->tuples_deleted = rec->deleted_pre_truncdrop;
 	}
-	pgstat_info->t_counts.t_tuples_inserted += rec->tuples_inserted;
-	pgstat_info->t_counts.t_tuples_updated += rec->tuples_updated;
-	pgstat_info->t_counts.t_tuples_deleted += rec->tuples_deleted;
-	pgstat_info->t_counts.t_delta_dead_tuples +=
+	pgstattab_info->t_counts.t_tuples_inserted += rec->tuples_inserted;
+	pgstattab_info->t_counts.t_tuples_updated += rec->tuples_updated;
+	pgstattab_info->t_counts.t_tuples_deleted += rec->tuples_deleted;
+	pgstattab_info->t_counts.t_delta_dead_tuples +=
 		rec->tuples_inserted + rec->tuples_updated;
 }
 
@@ -759,22 +880,21 @@ pgstat_twophase_postabort(TransactionId xid, uint16 info,
  * entry when successfully flushing.
  */
 bool
-pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
+pgstat_table_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
 {
 	static const PgStat_TableCounts all_zeroes;
 	Oid			dboid;
 	PgStat_TableStatus *lstats; /* pending stats entry  */
-	PgStatShared_Relation *shtabstats;
+	PgStatShared_Table *shtabstats;
 	PgStat_StatTabEntry *tabentry;	/* table entry of shared stats */
 	PgStat_StatDBEntry *dbentry;	/* pending database entry */
 
 	dboid = entry_ref->shared_entry->key.dboid;
 	lstats = (PgStat_TableStatus *) entry_ref->pending;
-	shtabstats = (PgStatShared_Relation *) entry_ref->shared_stats;
+	shtabstats = (PgStatShared_Table *) entry_ref->shared_stats;
 
 	/*
-	 * Ignore entries that didn't accumulate any actual counts, such as
-	 * indexes that were opened by the planner but not used.
+	 * Ignore entries that didn't accumulate any actual counts.
 	 */
 	if (memcmp(&lstats->t_counts, &all_zeroes,
 			   sizeof(PgStat_TableCounts)) == 0)
@@ -792,6 +912,7 @@ pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
 	if (lstats->t_counts.t_numscans)
 	{
 		TimestampTz t = GetCurrentTransactionStopTimestamp();
+
 		if (t > tabentry->lastscan)
 			tabentry->lastscan = t;
 	}
@@ -839,8 +960,74 @@ pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
 	return true;
 }
 
+/*
+ * Flush out pending stats for the entry
+ *
+ * If nowait is true, this function returns false if lock could not
+ * immediately acquired, otherwise true is returned.
+ *
+ * Some of the stats are copied to the corresponding pending database stats
+ * entry when successfully flushing.
+ */
+bool
+pgstat_index_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
+{
+	static const PgStat_IndexCounts all_zeroes;
+	Oid			dboid;
+
+	PgStat_IndexStatus *lstats; /* pending stats entry  */
+	PgStatShared_Index *shrelcomstats;
+	PgStat_StatIndEntry *indentry;	/* index entry of shared stats */
+	PgStat_StatDBEntry *dbentry;	/* pending database entry */
+
+	dboid = entry_ref->shared_entry->key.dboid;
+	lstats = (PgStat_IndexStatus *) entry_ref->pending;
+	shrelcomstats = (PgStatShared_Index *) entry_ref->shared_stats;
+
+	/*
+	 * Ignore entries that didn't accumulate any actual counts, such as
+	 * indexes that were opened by the planner but not used.
+	 */
+	if (memcmp(&lstats->i_counts, &all_zeroes,
+			   sizeof(PgStat_IndexCounts)) == 0)
+	{
+		return true;
+	}
+
+	if (!pgstat_lock_entry(entry_ref, nowait))
+		return false;
+
+	/* add the values to the shared entry. */
+	indentry = &shrelcomstats->stats;
+
+	indentry->numscans += lstats->i_counts.i_numscans;
+
+	if (lstats->i_counts.i_numscans)
+	{
+		TimestampTz t = GetCurrentTransactionStopTimestamp();
+
+		if (t > indentry->lastscan)
+			indentry->lastscan = t;
+	}
+	indentry->tuples_returned += lstats->i_counts.i_tuples_returned;
+	indentry->tuples_fetched += lstats->i_counts.i_tuples_fetched;
+	indentry->blocks_fetched += lstats->i_counts.i_blocks_fetched;
+	indentry->blocks_hit += lstats->i_counts.i_blocks_hit;
+
+	pgstat_unlock_entry(entry_ref);
+
+	/* The entry was successfully flushed, add the same to database stats */
+	dbentry = pgstat_prep_database_pending(dboid);
+	dbentry->n_tuples_returned += lstats->i_counts.i_tuples_returned;
+	dbentry->n_tuples_fetched += lstats->i_counts.i_tuples_fetched;
+	dbentry->n_blocks_fetched += lstats->i_counts.i_blocks_fetched;
+	dbentry->n_blocks_hit += lstats->i_counts.i_blocks_hit;
+
+	return true;
+}
+
 void
-pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref)
+pgstat_table_delete_pending_cb(PgStat_EntryRef *entry_ref)
 {
 	PgStat_TableStatus *pending = (PgStat_TableStatus *) entry_ref->pending;
 
@@ -848,17 +1035,26 @@ pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref)
 		pgstat_unlink_relation(pending->relation);
 }
 
+void
+pgstat_index_delete_pending_cb(PgStat_EntryRef *entry_ref)
+{
+	PgStat_IndexStatus *pending = (PgStat_IndexStatus *) entry_ref->pending;
+
+	if (pending->relation)
+		pgstat_unlink_relation(pending->relation);
+}
+
 /*
  * Find or create a PgStat_TableStatus entry for rel. New entry is created and
  * initialized if not exists.
  */
 static PgStat_TableStatus *
-pgstat_prep_relation_pending(Oid rel_id, bool isshared)
+pgstat_prep_table_pending(Oid rel_id, bool isshared)
 {
 	PgStat_EntryRef *entry_ref;
 	PgStat_TableStatus *pending;
 
-	entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_RELATION,
+	entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_TABLE,
 										  isshared ? InvalidOid : MyDatabaseId,
 										  rel_id, NULL);
 	pending = entry_ref->pending;
@@ -868,11 +1064,31 @@ pgstat_prep_relation_pending(Oid rel_id, bool isshared)
 	return pending;
 }
 
+/*
+ * Find or create a PgStat_IndexStatus entry for rel. New entry is created and
+ * initialized if not exists.
+ */
+static PgStat_IndexStatus *
+pgstat_prep_index_pending(Oid rel_id, bool isshared)
+{
+	PgStat_EntryRef *entry_ref;
+	PgStat_IndexStatus *pending;
+
+	entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_INDEX,
+										  isshared ? InvalidOid : MyDatabaseId,
+										  rel_id, NULL);
+	pending = entry_ref->pending;
+	pending->r_id = rel_id;
+	pending->r_shared = isshared;
+
+	return pending;
+}
+
 /*
  * add a new (sub)transaction state record
  */
 static void
-add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level)
+add_tabstat_xact_level(PgStat_TableStatus *pgstattab_info, int nest_level)
 {
 	PgStat_SubXactStatus *xact_state;
 	PgStat_TableXactStatus *trans;
@@ -888,24 +1104,24 @@ add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level)
 		MemoryContextAllocZero(TopTransactionContext,
 							   sizeof(PgStat_TableXactStatus));
 	trans->nest_level = nest_level;
-	trans->upper = pgstat_info->trans;
-	trans->parent = pgstat_info;
+	trans->upper = pgstattab_info->trans;
+	trans->parent = pgstattab_info;
 	trans->next = xact_state->first;
 	xact_state->first = trans;
-	pgstat_info->trans = trans;
+	pgstattab_info->trans = trans;
 }
 
 /*
  * Add a new (sub)transaction record if needed.
  */
 static void
-ensure_tabstat_xact_level(PgStat_TableStatus *pgstat_info)
+ensure_tabstat_xact_level(PgStat_TableStatus *pgstattab_info)
 {
 	int			nest_level = GetCurrentTransactionNestLevel();
 
-	if (pgstat_info->trans == NULL ||
-		pgstat_info->trans->nest_level != nest_level)
-		add_tabstat_xact_level(pgstat_info, nest_level);
+	if (pgstattab_info->trans == NULL ||
+		pgstattab_info->trans->nest_level != nest_level)
+		add_tabstat_xact_level(pgstattab_info, nest_level);
 }
 
 /*
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 96bffc0f2a..0dd6148ed1 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -36,24 +36,34 @@
 
 #define HAS_PGSTAT_PERMISSIONS(role)	 (has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS) || has_privs_of_role(GetUserId(), role))
 
+#define PGSTAT_FETCH_STAT_ENTRY(entry, stat_name) ((entry == NULL) ? 0 : (int64) (entry->stat_name));
+
 Datum
-pg_stat_get_numscans(PG_FUNCTION_ARGS)
+pg_stat_get_index_numscans(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
-	PgStat_StatTabEntry *tabentry;
+	PgStat_StatIndEntry *indentry = pgstat_fetch_stat_indentry(relid);
 
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
-		result = 0;
-	else
-		result = (int64) (tabentry->numscans);
+	result = PGSTAT_FETCH_STAT_ENTRY(indentry, numscans);
 
 	PG_RETURN_INT64(result);
 }
 
+Datum
+pg_stat_get_table_numscans(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int64		result;
+	PgStat_StatTabEntry *tabentry = pgstat_fetch_stat_tabentry(relid);
+
+	result = PGSTAT_FETCH_STAT_ENTRY(tabentry, numscans);
+
+	PG_RETURN_INT64(result);
+}
 
 Datum
-pg_stat_get_lastscan(PG_FUNCTION_ARGS)
+pg_stat_get_table_lastscan(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	PgStat_StatTabEntry *tabentry;
@@ -64,34 +74,61 @@ pg_stat_get_lastscan(PG_FUNCTION_ARGS)
 		PG_RETURN_TIMESTAMPTZ(tabentry->lastscan);
 }
 
+Datum
+pg_stat_get_index_lastscan(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	PgStat_StatIndEntry *indentry;
+
+	if ((indentry = pgstat_fetch_stat_indentry(relid)) == NULL)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_TIMESTAMPTZ(indentry->lastscan);
+}
 
 Datum
-pg_stat_get_tuples_returned(PG_FUNCTION_ARGS)
+pg_stat_get_table_tuples_returned(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
-	PgStat_StatTabEntry *tabentry;
+	PgStat_StatTabEntry *tabentry = pgstat_fetch_stat_tabentry(relid);
 
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
-		result = 0;
-	else
-		result = (int64) (tabentry->tuples_returned);
+	result = PGSTAT_FETCH_STAT_ENTRY(tabentry, tuples_returned);
 
 	PG_RETURN_INT64(result);
 }
+Datum
+pg_stat_get_index_tuples_returned(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int64		result;
+	PgStat_StatIndEntry *indentry = pgstat_fetch_stat_indentry(relid);
 
+	result = PGSTAT_FETCH_STAT_ENTRY(indentry, tuples_returned);
+
+	PG_RETURN_INT64(result);
+}
 
 Datum
-pg_stat_get_tuples_fetched(PG_FUNCTION_ARGS)
+pg_stat_get_table_tuples_fetched(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
-	PgStat_StatTabEntry *tabentry;
+	PgStat_StatTabEntry *tabentry = pgstat_fetch_stat_tabentry(relid);
 
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
-		result = 0;
-	else
-		result = (int64) (tabentry->tuples_fetched);
+	result = PGSTAT_FETCH_STAT_ENTRY(tabentry, tuples_fetched);
+
+	PG_RETURN_INT64(result);
+}
+
+Datum
+pg_stat_get_index_tuples_fetched(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int64		result;
+	PgStat_StatIndEntry *indentry = pgstat_fetch_stat_indentry(relid);
+
+	result = PGSTAT_FETCH_STAT_ENTRY(indentry, tuples_fetched);
 
 	PG_RETURN_INT64(result);
 }
@@ -102,12 +139,9 @@ pg_stat_get_tuples_inserted(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
-	PgStat_StatTabEntry *tabentry;
+	PgStat_StatTabEntry *tabentry = pgstat_fetch_stat_tabentry(relid);
 
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
-		result = 0;
-	else
-		result = (int64) (tabentry->tuples_inserted);
+	result = PGSTAT_FETCH_STAT_ENTRY(tabentry, tuples_inserted);
 
 	PG_RETURN_INT64(result);
 }
@@ -118,12 +152,9 @@ pg_stat_get_tuples_updated(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
-	PgStat_StatTabEntry *tabentry;
+	PgStat_StatTabEntry *tabentry = pgstat_fetch_stat_tabentry(relid);
 
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
-		result = 0;
-	else
-		result = (int64) (tabentry->tuples_updated);
+	result = PGSTAT_FETCH_STAT_ENTRY(tabentry, tuples_updated);
 
 	PG_RETURN_INT64(result);
 }
@@ -134,12 +165,9 @@ pg_stat_get_tuples_deleted(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
-	PgStat_StatTabEntry *tabentry;
+	PgStat_StatTabEntry *tabentry = pgstat_fetch_stat_tabentry(relid);
 
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
-		result = 0;
-	else
-		result = (int64) (tabentry->tuples_deleted);
+	result = PGSTAT_FETCH_STAT_ENTRY(tabentry, tuples_deleted);
 
 	PG_RETURN_INT64(result);
 }
@@ -150,12 +178,9 @@ pg_stat_get_tuples_hot_updated(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
-	PgStat_StatTabEntry *tabentry;
+	PgStat_StatTabEntry *tabentry = pgstat_fetch_stat_tabentry(relid);
 
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
-		result = 0;
-	else
-		result = (int64) (tabentry->tuples_hot_updated);
+	result = PGSTAT_FETCH_STAT_ENTRY(tabentry, tuples_hot_updated);
 
 	PG_RETURN_INT64(result);
 }
@@ -166,12 +191,9 @@ pg_stat_get_live_tuples(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
-	PgStat_StatTabEntry *tabentry;
+	PgStat_StatTabEntry *tabentry = pgstat_fetch_stat_tabentry(relid);
 
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
-		result = 0;
-	else
-		result = (int64) (tabentry->n_live_tuples);
+	result = PGSTAT_FETCH_STAT_ENTRY(tabentry, n_live_tuples);
 
 	PG_RETURN_INT64(result);
 }
@@ -182,12 +204,9 @@ pg_stat_get_dead_tuples(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
-	PgStat_StatTabEntry *tabentry;
+	PgStat_StatTabEntry *tabentry = pgstat_fetch_stat_tabentry(relid);
 
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
-		result = 0;
-	else
-		result = (int64) (tabentry->n_dead_tuples);
+	result = PGSTAT_FETCH_STAT_ENTRY(tabentry, n_dead_tuples);
 
 	PG_RETURN_INT64(result);
 }
@@ -198,12 +217,9 @@ pg_stat_get_mod_since_analyze(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
-	PgStat_StatTabEntry *tabentry;
+	PgStat_StatTabEntry *tabentry = pgstat_fetch_stat_tabentry(relid);
 
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
-		result = 0;
-	else
-		result = (int64) (tabentry->changes_since_analyze);
+	result = PGSTAT_FETCH_STAT_ENTRY(tabentry, changes_since_analyze);
 
 	PG_RETURN_INT64(result);
 }
@@ -214,44 +230,58 @@ pg_stat_get_ins_since_vacuum(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
-	PgStat_StatTabEntry *tabentry;
+	PgStat_StatTabEntry *tabentry = pgstat_fetch_stat_tabentry(relid);
 
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
-		result = 0;
-	else
-		result = (int64) (tabentry->inserts_since_vacuum);
+	result = PGSTAT_FETCH_STAT_ENTRY(tabentry, inserts_since_vacuum);
 
 	PG_RETURN_INT64(result);
 }
 
 
 Datum
-pg_stat_get_blocks_fetched(PG_FUNCTION_ARGS)
+pg_stat_get_table_blocks_fetched(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
-	PgStat_StatTabEntry *tabentry;
+	PgStat_StatTabEntry *tabentry = pgstat_fetch_stat_tabentry(relid);
 
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
-		result = 0;
-	else
-		result = (int64) (tabentry->blocks_fetched);
+	result = PGSTAT_FETCH_STAT_ENTRY(tabentry, blocks_fetched);
+
+	PG_RETURN_INT64(result);
+}
+
+Datum
+pg_stat_get_index_blocks_fetched(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int64		result;
+	PgStat_StatIndEntry *indentry = pgstat_fetch_stat_indentry(relid);
+
+	result = PGSTAT_FETCH_STAT_ENTRY(indentry, blocks_fetched);
 
 	PG_RETURN_INT64(result);
 }
 
+Datum
+pg_stat_get_table_blocks_hit(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int64		result;
+	PgStat_StatTabEntry *tabentry = pgstat_fetch_stat_tabentry(relid);
+
+	result = PGSTAT_FETCH_STAT_ENTRY(tabentry, blocks_hit);
+
+	PG_RETURN_INT64(result);
+}
 
 Datum
-pg_stat_get_blocks_hit(PG_FUNCTION_ARGS)
+pg_stat_get_index_blocks_hit(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
-	PgStat_StatTabEntry *tabentry;
+	PgStat_StatIndEntry *indentry = pgstat_fetch_stat_indentry(relid);
 
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
-		result = 0;
-	else
-		result = (int64) (tabentry->blocks_hit);
+	result = PGSTAT_FETCH_STAT_ENTRY(indentry, blocks_hit);
 
 	PG_RETURN_INT64(result);
 }
@@ -333,12 +363,9 @@ pg_stat_get_vacuum_count(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
-	PgStat_StatTabEntry *tabentry;
+	PgStat_StatTabEntry *tabentry = pgstat_fetch_stat_tabentry(relid);
 
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
-		result = 0;
-	else
-		result = (int64) (tabentry->vacuum_count);
+	result = PGSTAT_FETCH_STAT_ENTRY(tabentry, vacuum_count);
 
 	PG_RETURN_INT64(result);
 }
@@ -348,12 +375,9 @@ pg_stat_get_autovacuum_count(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
-	PgStat_StatTabEntry *tabentry;
+	PgStat_StatTabEntry *tabentry = pgstat_fetch_stat_tabentry(relid);
 
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
-		result = 0;
-	else
-		result = (int64) (tabentry->autovac_vacuum_count);
+	result = PGSTAT_FETCH_STAT_ENTRY(tabentry, autovac_vacuum_count);
 
 	PG_RETURN_INT64(result);
 }
@@ -363,12 +387,9 @@ pg_stat_get_analyze_count(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
-	PgStat_StatTabEntry *tabentry;
+	PgStat_StatTabEntry *tabentry = pgstat_fetch_stat_tabentry(relid);
 
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
-		result = 0;
-	else
-		result = (int64) (tabentry->analyze_count);
+	result = PGSTAT_FETCH_STAT_ENTRY(tabentry, analyze_count);
 
 	PG_RETURN_INT64(result);
 }
@@ -378,12 +399,9 @@ pg_stat_get_autoanalyze_count(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
-	PgStat_StatTabEntry *tabentry;
+	PgStat_StatTabEntry *tabentry = pgstat_fetch_stat_tabentry(relid);
 
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
-		result = 0;
-	else
-		result = (int64) (tabentry->autovac_analyze_count);
+	result = PGSTAT_FETCH_STAT_ENTRY(tabentry, autovac_analyze_count);
 
 	PG_RETURN_INT64(result);
 }
@@ -1837,7 +1855,7 @@ pg_stat_get_slru(PG_FUNCTION_ARGS)
 }
 
 Datum
-pg_stat_get_xact_numscans(PG_FUNCTION_ARGS)
+pg_stat_get_table_xact_numscans(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
@@ -1851,17 +1869,32 @@ pg_stat_get_xact_numscans(PG_FUNCTION_ARGS)
 	PG_RETURN_INT64(result);
 }
 
+Datum
+pg_stat_get_index_xact_numscans(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int64		result;
+	PgStat_IndexStatus *indentry;
+
+	if ((indentry = find_indstat_entry(relid)) == NULL)
+		result = 0;
+	else
+		result = (int64) (indentry->i_counts.i_numscans);
+
+	PG_RETURN_INT64(result);
+}
+
 Datum
 pg_stat_get_xact_tuples_returned(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
-	PgStat_TableStatus *tabentry;
+	PgStat_IndexStatus *indentry;
 
-	if ((tabentry = find_tabstat_entry(relid)) == NULL)
+	if ((indentry = find_indstat_entry(relid)) == NULL)
 		result = 0;
 	else
-		result = (int64) (tabentry->t_counts.t_tuples_returned);
+		result = (int64) (indentry->i_counts.i_tuples_returned);
 
 	PG_RETURN_INT64(result);
 }
@@ -1871,12 +1904,12 @@ pg_stat_get_xact_tuples_fetched(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
-	PgStat_TableStatus *tabentry;
+	PgStat_IndexStatus *indentry;
 
-	if ((tabentry = find_tabstat_entry(relid)) == NULL)
+	if ((indentry = find_indstat_entry(relid)) == NULL)
 		result = 0;
 	else
-		result = (int64) (tabentry->t_counts.t_tuples_fetched);
+		result = (int64) (indentry->i_counts.i_tuples_fetched);
 
 	PG_RETURN_INT64(result);
 }
@@ -1964,12 +1997,12 @@ pg_stat_get_xact_blocks_fetched(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
-	PgStat_TableStatus *tabentry;
+	PgStat_IndexStatus *indentry;
 
-	if ((tabentry = find_tabstat_entry(relid)) == NULL)
+	if ((indentry = find_indstat_entry(relid)) == NULL)
 		result = 0;
 	else
-		result = (int64) (tabentry->t_counts.t_blocks_fetched);
+		result = (int64) (indentry->i_counts.i_blocks_fetched);
 
 	PG_RETURN_INT64(result);
 }
@@ -1979,12 +2012,12 @@ pg_stat_get_xact_blocks_hit(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
-	PgStat_TableStatus *tabentry;
+	PgStat_IndexStatus *indentry;
 
-	if ((tabentry = find_tabstat_entry(relid)) == NULL)
+	if ((indentry = find_indstat_entry(relid)) == NULL)
 		result = 0;
 	else
-		result = (int64) (tabentry->t_counts.t_blocks_hit);
+		result = (int64) (indentry->i_counts.i_blocks_hit);
 
 	PG_RETURN_INT64(result);
 }
@@ -2103,7 +2136,8 @@ pg_stat_reset_single_table_counters(PG_FUNCTION_ARGS)
 {
 	Oid			taboid = PG_GETARG_OID(0);
 
-	pgstat_reset(PGSTAT_KIND_RELATION, MyDatabaseId, taboid);
+	pgstat_reset(PGSTAT_KIND_TABLE, MyDatabaseId, taboid);
+	pgstat_reset(PGSTAT_KIND_INDEX, MyDatabaseId, taboid);
 
 	PG_RETURN_VOID();
 }
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index bd6cd4e47b..2d24abc3df 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -2720,8 +2720,9 @@ RelationClearRelation(Relation relation, bool rebuild)
 			SWAPFIELD(RowSecurityDesc *, rd_rsdesc);
 		/* toast OID override must be preserved */
 		SWAPFIELD(Oid, rd_toastoid);
-		/* pgstat_info / enabled must be preserved */
-		SWAPFIELD(struct PgStat_TableStatus *, pgstat_info);
+		/* pgstattab_info / pgstatind_info / enabled must be preserved */
+		SWAPFIELD(struct PgStat_TableStatus *, pgstattab_info);
+		SWAPFIELD(struct PgStat_IndexStatus *, pgstatind_info);
 		SWAPFIELD(bool, pgstat_enabled);
 		/* preserve old partition key if we have one */
 		if (keep_partkey)
@@ -6314,7 +6315,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_firstRelfilelocatorSubid = InvalidSubTransactionId;
 		rel->rd_droppedSubid = InvalidSubTransactionId;
 		rel->rd_amcache = NULL;
-		rel->pgstat_info = NULL;
+		rel->pgstattab_info = NULL;
+		rel->pgstatind_info = NULL;
 
 		/*
 		 * Recompute lock and physical addressing info.  This is needed in
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 20f5aa56ea..80da09d9e4 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5248,22 +5248,38 @@
   proargnames => '{mcv_list,index,values,nulls,frequency,base_frequency}',
   prosrc => 'pg_stats_ext_mcvlist_items' },
 
-{ oid => '1928', descr => 'statistics: number of scans done for table/index',
-  proname => 'pg_stat_get_numscans', provolatile => 's', proparallel => 'r',
+{ oid => '1928', descr => 'statistics: number of scans done for table',
+  proname => 'pg_stat_get_table_numscans', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_numscans' },
-{ oid => '9976', descr => 'statistics: time of the last scan for table/index',
-  proname => 'pg_stat_get_lastscan', provolatile => 's', proparallel => 'r',
+  prosrc => 'pg_stat_get_table_numscans' },
+{ oid => '8296', descr => 'statistics: number of scans done for index',
+  proname => 'pg_stat_get_index_numscans', provolatile => 's', proparallel => 'r',
+  prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_index_numscans' },
+{ oid => '9976', descr => 'statistics: time of the last scan for table',
+  proname => 'pg_stat_get_table_lastscan', provolatile => 's', proparallel => 'r',
+  prorettype => 'timestamptz', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_table_lastscan' },
+{ oid => '8626', descr => 'statistics: time of the last scan for index',
+  proname => 'pg_stat_get_index_lastscan', provolatile => 's', proparallel => 'r',
   prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_lastscan' },
+  prosrc => 'pg_stat_get_index_lastscan' },
 { oid => '1929', descr => 'statistics: number of tuples read by seqscan',
-  proname => 'pg_stat_get_tuples_returned', provolatile => 's',
+  proname => 'pg_stat_get_table_tuples_returned', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_returned' },
+  prosrc => 'pg_stat_get_table_tuples_returned' },
+{ oid => '9603', descr => 'statistics: number of tuples read by seqscan',
+  proname => 'pg_stat_get_index_tuples_returned', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_index_tuples_returned' },
 { oid => '1930', descr => 'statistics: number of tuples fetched by idxscan',
-  proname => 'pg_stat_get_tuples_fetched', provolatile => 's',
+  proname => 'pg_stat_get_table_tuples_fetched', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_table_tuples_fetched' },
+{ oid => '8526', descr => 'statistics: number of tuples fetched by idxscan',
+  proname => 'pg_stat_get_index_tuples_fetched', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_fetched' },
+  prosrc => 'pg_stat_get_index_tuples_fetched' },
 { oid => '1931', descr => 'statistics: number of tuples inserted',
   proname => 'pg_stat_get_tuples_inserted', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
@@ -5299,13 +5315,21 @@
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_ins_since_vacuum' },
 { oid => '1934', descr => 'statistics: number of blocks fetched',
-  proname => 'pg_stat_get_blocks_fetched', provolatile => 's',
+  proname => 'pg_stat_get_table_blocks_fetched', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_blocks_fetched' },
+  prosrc => 'pg_stat_get_table_blocks_fetched' },
+{ oid => '9432', descr => 'statistics: number of blocks fetched',
+  proname => 'pg_stat_get_index_blocks_fetched', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_index_blocks_fetched' },
 { oid => '1935', descr => 'statistics: number of blocks found in cache',
-  proname => 'pg_stat_get_blocks_hit', provolatile => 's', proparallel => 'r',
+  proname => 'pg_stat_get_table_blocks_hit', provolatile => 's', proparallel => 'r',
+  prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_table_blocks_hit' },
+{ oid => '8354', descr => 'statistics: number of blocks found in cache',
+  proname => 'pg_stat_get_index_blocks_hit', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_blocks_hit' },
+  prosrc => 'pg_stat_get_index_blocks_hit' },
 { oid => '2781', descr => 'statistics: last manual vacuum time for a table',
   proname => 'pg_stat_get_last_vacuum_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
@@ -5693,10 +5717,15 @@
   prosrc => 'pg_stat_get_function_self_time' },
 
 { oid => '3037',
-  descr => 'statistics: number of scans done for table/index in current transaction',
-  proname => 'pg_stat_get_xact_numscans', provolatile => 'v',
+  descr => 'statistics: number of scans done for table in current transaction',
+  proname => 'pg_stat_get_table_xact_numscans', provolatile => 'v',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_table_xact_numscans' },
+{ oid => '8892',
+  descr => 'statistics: number of scans done for index in current transaction',
+  proname => 'pg_stat_get_index_xact_numscans', provolatile => 'v',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_xact_numscans' },
+  prosrc => 'pg_stat_get_index_xact_numscans' },
 { oid => '3038',
   descr => 'statistics: number of tuples read by seqscan in current transaction',
   proname => 'pg_stat_get_xact_tuples_returned', provolatile => 'v',
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 9e2ce6f011..8dae39b917 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -39,7 +39,8 @@ typedef enum PgStat_Kind
 
 	/* stats for variable-numbered objects */
 	PGSTAT_KIND_DATABASE,		/* database-wide statistics */
-	PGSTAT_KIND_RELATION,		/* per-table statistics */
+	PGSTAT_KIND_TABLE,			/* per-table statistics */
+	PGSTAT_KIND_INDEX,			/* per-index statistics */
 	PGSTAT_KIND_FUNCTION,		/* per-function statistics */
 	PGSTAT_KIND_REPLSLOT,		/* per-slot statistics */
 	PGSTAT_KIND_SUBSCRIPTION,	/* per-subscription statistics */
@@ -145,6 +146,28 @@ typedef struct PgStat_BackendSubEntry
 	PgStat_Counter sync_error_count;
 } PgStat_BackendSubEntry;
 
+/* ----------
+ * PgStat_IndexCounts			The actual per-index counts kept by a backend
+ *
+ * This struct should contain only actual event counters, because we memcmp
+ * it against zeroes to detect whether there are any stats updates to apply.
+ * It is a component of PgStat_IndexStatus (within-backend state).
+ *
+ * tuples_returned is the number of index entries returned by
+ * the index AM, while tuples_fetched is the number of tuples successfully
+ * fetched by heap_fetch under the control of simple indexscans for this index.
+ * ----------
+ */
+typedef struct PgStat_IndexCounts
+{
+	PgStat_Counter i_numscans;
+
+	PgStat_Counter i_tuples_returned;
+	PgStat_Counter i_tuples_fetched;
+	PgStat_Counter i_blocks_fetched;
+	PgStat_Counter i_blocks_hit;
+} PgStat_IndexCounts;
+
 /* ----------
  * PgStat_TableCounts			The actual per-table counts kept by a backend
  *
@@ -152,12 +175,9 @@ typedef struct PgStat_BackendSubEntry
  * it against zeroes to detect whether there are any stats updates to apply.
  * It is a component of PgStat_TableStatus (within-backend state).
  *
- * Note: for a table, tuples_returned is the number of tuples successfully
+ * Note: tuples_returned is the number of tuples successfully
  * fetched by heap_getnext, while tuples_fetched is the number of tuples
  * successfully fetched by heap_fetch under the control of bitmap indexscans.
- * For an index, tuples_returned is the number of index entries returned by
- * the index AM, while tuples_fetched is the number of tuples successfully
- * fetched by heap_fetch under the control of simple indexscans for this index.
  *
  * tuples_inserted/updated/deleted/hot_updated count attempted actions,
  * regardless of whether the transaction committed.  delta_live_tuples,
@@ -210,6 +230,22 @@ typedef struct PgStat_TableStatus
 	Relation	relation;		/* rel that is using this entry */
 } PgStat_TableStatus;
 
+/* ----------
+ * PgStat_IndexStatus			Per-index status within a backend
+ *
+ * Many of the event counters are nontransactional, ie, we count events
+ * in committed and aborted transactions alike.  For these, we just count
+ * directly in the PgStat_IndexStatus.
+ * ----------
+ */
+typedef struct PgStat_IndexStatus
+{
+	Oid			r_id;			/* relation's OID */
+	bool		r_shared;		/* is it a shared catalog? */
+	PgStat_IndexCounts i_counts;	/* event counts to be sent */
+	Relation	relation;		/* rel that is using this entry */
+} PgStat_IndexStatus;
+
 /* ----------
  * PgStat_TableXactStatus		Per-table, per-subtransaction status
  * ----------
@@ -382,6 +418,17 @@ typedef struct PgStat_StatTabEntry
 	PgStat_Counter autovac_analyze_count;
 } PgStat_StatTabEntry;
 
+typedef struct PgStat_StatIndEntry
+{
+	PgStat_Counter numscans;
+	TimestampTz lastscan;
+
+	PgStat_Counter tuples_returned;
+	PgStat_Counter tuples_fetched;
+	PgStat_Counter blocks_fetched;
+	PgStat_Counter blocks_hit;
+} PgStat_StatIndEntry;
+
 typedef struct PgStat_WalStats
 {
 	PgStat_Counter wal_records;
@@ -497,12 +544,16 @@ extern PgStat_BackendFunctionEntry *find_funcstat_entry(Oid func_id);
  * Functions in pgstat_relation.c
  */
 
-extern void pgstat_create_relation(Relation rel);
-extern void pgstat_drop_relation(Relation rel);
-extern void pgstat_copy_relation_stats(Relation dst, Relation src);
+extern void pgstat_create_table(Relation rel);
+extern void pgstat_create_index(Relation rel);
+extern void pgstat_drop_table(Relation rel);
+extern void pgstat_drop_index(Relation rel);
+extern void pgstat_copy_index_stats(Relation dst, Relation src);
 
 extern void pgstat_init_relation(Relation rel);
 extern void pgstat_assoc_relation(Relation rel);
+extern void pgstat_assoc_table(Relation rel);
+extern void pgstat_assoc_index(Relation rel);
 extern void pgstat_unlink_relation(Relation rel);
 
 extern void pgstat_report_vacuum(Oid tableoid, bool shared,
@@ -513,51 +564,69 @@ extern void pgstat_report_analyze(Relation rel,
 
 /*
  * If stats are enabled, but pending data hasn't been prepared yet, call
- * pgstat_assoc_relation() to do so. See its comment for why this is done
- * separately from pgstat_init_relation().
+ * pgstat_assoc_table() / pgstat_assoc_index() to do so.
+ * See their comment for why this is done separately from pgstat_init_relation().
  */
-#define pgstat_should_count_relation(rel)                           \
-	(likely((rel)->pgstat_info != NULL) ? true :                    \
-	 ((rel)->pgstat_enabled ? pgstat_assoc_relation(rel), true : false))
+#define pgstat_should_count_table(rel)                           \
+	(likely((rel)->pgstattab_info != NULL) ? true :                    \
+	 ((rel)->pgstat_enabled ? pgstat_assoc_table(rel), true : false))
+
+#define pgstat_should_count_index(rel)                           \
+	(likely((rel)->pgstatind_info != NULL) ? true :                    \
+	 ((rel)->pgstat_enabled ? pgstat_assoc_index(rel), true : false))
 
 /* nontransactional event counts are simple enough to inline */
 
 #define pgstat_count_heap_scan(rel)									\
 	do {															\
-		if (pgstat_should_count_relation(rel))						\
-			(rel)->pgstat_info->t_counts.t_numscans++;				\
+		if (pgstat_should_count_table(rel))						\
+			(rel)->pgstattab_info->t_counts.t_numscans++;				\
 	} while (0)
 #define pgstat_count_heap_getnext(rel)								\
 	do {															\
-		if (pgstat_should_count_relation(rel))						\
-			(rel)->pgstat_info->t_counts.t_tuples_returned++;		\
+		if (pgstat_should_count_table(rel))						\
+			(rel)->pgstattab_info->t_counts.t_tuples_returned++;		\
 	} while (0)
 #define pgstat_count_heap_fetch(rel)								\
 	do {															\
-		if (pgstat_should_count_relation(rel))						\
-			(rel)->pgstat_info->t_counts.t_tuples_fetched++;		\
+		if (pgstat_should_count_table(rel))						\
+			(rel)->pgstattab_info->t_counts.t_tuples_fetched++;		\
+	} while (0)
+#define pgstat_count_index_fetch(rel)								\
+	do {															\
+		if (pgstat_should_count_index(rel))						\
+			(rel)->pgstatind_info->i_counts.i_tuples_fetched++;		\
 	} while (0)
 #define pgstat_count_index_scan(rel)								\
 	do {															\
-		if (pgstat_should_count_relation(rel))						\
-			(rel)->pgstat_info->t_counts.t_numscans++;				\
+		if (pgstat_should_count_index(rel))						\
+			(rel)->pgstatind_info->i_counts.i_numscans++;			\
 	} while (0)
 #define pgstat_count_index_tuples(rel, n)							\
 	do {															\
-		if (pgstat_should_count_relation(rel))						\
-			(rel)->pgstat_info->t_counts.t_tuples_returned += (n);	\
+		if (pgstat_should_count_index(rel))						\
+			(rel)->pgstatind_info->i_counts.i_tuples_returned += (n);	\
 	} while (0)
-#define pgstat_count_buffer_read(rel)								\
+#define pgstat_count_table_buffer_read(rel)								\
 	do {															\
-		if (pgstat_should_count_relation(rel))						\
-			(rel)->pgstat_info->t_counts.t_blocks_fetched++;		\
+		if (pgstat_should_count_table(rel))	\
+			(rel)->pgstattab_info->t_counts.t_blocks_fetched++;		\
 	} while (0)
-#define pgstat_count_buffer_hit(rel)								\
+#define pgstat_count_index_buffer_read(rel)								\
 	do {															\
-		if (pgstat_should_count_relation(rel))						\
-			(rel)->pgstat_info->t_counts.t_blocks_hit++;			\
+		if (pgstat_should_count_index(rel))	\
+			(rel)->pgstatind_info->i_counts.i_blocks_fetched++;		\
+	} while (0)
+#define pgstat_count_table_buffer_hit(rel)								\
+	do {															\
+		if (pgstat_should_count_table(rel))	\
+			(rel)->pgstattab_info->t_counts.t_blocks_hit++; \
+	} while (0)
+#define pgstat_count_index_buffer_hit(rel)								\
+	do {															\
+		if (pgstat_should_count_index(rel))	\
+			(rel)->pgstatind_info->i_counts.i_blocks_hit++; \
 	} while (0)
-
 extern void pgstat_count_heap_insert(Relation rel, PgStat_Counter n);
 extern void pgstat_count_heap_update(Relation rel, bool hot);
 extern void pgstat_count_heap_delete(Relation rel);
@@ -573,6 +642,10 @@ extern PgStat_StatTabEntry *pgstat_fetch_stat_tabentry(Oid relid);
 extern PgStat_StatTabEntry *pgstat_fetch_stat_tabentry_ext(bool shared,
 														   Oid reloid);
 extern PgStat_TableStatus *find_tabstat_entry(Oid rel_id);
+extern PgStat_IndexStatus *find_indstat_entry(Oid rel_id);
+extern PgStat_StatIndEntry *pgstat_fetch_stat_indentry(Oid relid);
+extern PgStat_StatIndEntry *pgstat_fetch_stat_indentry_ext(bool shared,
+														   Oid relid);
 
 
 /*
diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h
index e2c7b59324..761747820b 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -359,11 +359,17 @@ typedef struct PgStatShared_Database
 	PgStat_StatDBEntry stats;
 } PgStatShared_Database;
 
-typedef struct PgStatShared_Relation
+typedef struct PgStatShared_Table
 {
 	PgStatShared_Common header;
 	PgStat_StatTabEntry stats;
-} PgStatShared_Relation;
+} PgStatShared_Table;
+
+typedef struct PgStatShared_Index
+{
+	PgStatShared_Common header;
+	PgStat_StatIndEntry stats;
+} PgStatShared_Index;
 
 typedef struct PgStatShared_Function
 {
@@ -558,8 +564,10 @@ extern void AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool
 extern void AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state);
 extern void PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state);
 
-extern bool pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
-extern void pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref);
+extern bool pgstat_table_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+extern bool pgstat_index_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+extern void pgstat_table_delete_pending_cb(PgStat_EntryRef *entry_ref);
+extern void pgstat_index_delete_pending_cb(PgStat_EntryRef *entry_ref);
 
 
 /*
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index f383a2fca9..796423f3e3 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -248,7 +248,10 @@ typedef struct RelationData
 
 	bool		pgstat_enabled; /* should relation stats be counted */
 	/* use "struct" here to avoid needing to include pgstat.h: */
-	struct PgStat_TableStatus *pgstat_info; /* statistics collection area */
+	/* table's statistics collection area */
+	struct PgStat_TableStatus *pgstattab_info;
+	/* Index's statistics collection area */
+	struct PgStat_IndexStatus *pgstatind_info;
 } RelationData;
 
 
diff --git a/src/test/isolation/expected/stats.out b/src/test/isolation/expected/stats.out
index 61b5a710ec..b339759c82 100644
--- a/src/test/isolation/expected/stats.out
+++ b/src/test/isolation/expected/stats.out
@@ -2166,8 +2166,8 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
-        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_table_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_table_tuples_returned(tso.oid) AS seq_tup_read,
         pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
         pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
         pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
@@ -2210,8 +2210,8 @@ step s2_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key =
 step s1_table_drop: DROP TABLE test_stat_tab;
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
-        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_table_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_table_tuples_returned(tso.oid) AS seq_tup_read,
         pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
         pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
         pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
@@ -2236,8 +2236,8 @@ pg_stat_force_next_flush
 step s1_track_counts_off: SET track_counts = off;
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
-        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_table_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_table_tuples_returned(tso.oid) AS seq_tup_read,
         pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
         pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
         pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
@@ -2275,8 +2275,8 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
-        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_table_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_table_tuples_returned(tso.oid) AS seq_tup_read,
         pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
         pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
         pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
@@ -2314,8 +2314,8 @@ pg_stat_force_next_flush
 step s1_track_counts_off: SET track_counts = off;
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
-        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_table_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_table_tuples_returned(tso.oid) AS seq_tup_read,
         pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
         pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
         pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
@@ -2369,8 +2369,8 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
-        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_table_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_table_tuples_returned(tso.oid) AS seq_tup_read,
         pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
         pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
         pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
@@ -2401,8 +2401,8 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
-        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_table_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_table_tuples_returned(tso.oid) AS seq_tup_read,
         pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
         pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
         pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
@@ -2463,8 +2463,8 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
-        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_table_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_table_tuples_returned(tso.oid) AS seq_tup_read,
         pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
         pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
         pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
@@ -2495,8 +2495,8 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
-        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_table_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_table_tuples_returned(tso.oid) AS seq_tup_read,
         pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
         pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
         pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
@@ -2558,8 +2558,8 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
-        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_table_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_table_tuples_returned(tso.oid) AS seq_tup_read,
         pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
         pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
         pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
@@ -2627,8 +2627,8 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
-        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_table_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_table_tuples_returned(tso.oid) AS seq_tup_read,
         pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
         pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
         pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
@@ -2688,8 +2688,8 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
-        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_table_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_table_tuples_returned(tso.oid) AS seq_tup_read,
         pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
         pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
         pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
@@ -2755,8 +2755,8 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
-        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_table_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_table_tuples_returned(tso.oid) AS seq_tup_read,
         pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
         pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
         pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
@@ -2795,8 +2795,8 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
-        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_table_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_table_tuples_returned(tso.oid) AS seq_tup_read,
         pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
         pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
         pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
@@ -2841,8 +2841,8 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
-        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_table_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_table_tuples_returned(tso.oid) AS seq_tup_read,
         pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
         pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
         pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
@@ -2881,8 +2881,8 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
-        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_table_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_table_tuples_returned(tso.oid) AS seq_tup_read,
         pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
         pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
         pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
@@ -2927,8 +2927,8 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
-        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_table_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_table_tuples_returned(tso.oid) AS seq_tup_read,
         pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
         pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
         pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
@@ -2968,8 +2968,8 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
-        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_table_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_table_tuples_returned(tso.oid) AS seq_tup_read,
         pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
         pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
         pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
@@ -3015,8 +3015,8 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
-        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_table_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_table_tuples_returned(tso.oid) AS seq_tup_read,
         pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
         pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
         pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
diff --git a/src/test/isolation/specs/stats.spec b/src/test/isolation/specs/stats.spec
index 5b922d788c..07ce31885b 100644
--- a/src/test/isolation/specs/stats.spec
+++ b/src/test/isolation/specs/stats.spec
@@ -93,8 +93,8 @@ step s1_table_drop { DROP TABLE test_stat_tab; }
 
 step s1_table_stats {
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
-        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_table_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_table_tuples_returned(tso.oid) AS seq_tup_read,
         pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
         pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
         pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
diff --git a/src/test/recovery/t/029_stats_restart.pl b/src/test/recovery/t/029_stats_restart.pl
index 1bf7b568cc..c25634d33f 100644
--- a/src/test/recovery/t/029_stats_restart.pl
+++ b/src/test/recovery/t/029_stats_restart.pl
@@ -43,8 +43,8 @@ my $sect = "initial";
 is(have_stats('database', $dboid, 0), 't', "$sect: db stats do exist");
 is(have_stats('function', $dboid, $funcoid),
 	't', "$sect: function stats do exist");
-is(have_stats('relation', $dboid, $tableoid),
-	't', "$sect: relation stats do exist");
+is(have_stats('table', $dboid, $tableoid),
+	't', "$sect: table stats do exist");
 
 # regular shutdown
 $node->stop();
@@ -67,8 +67,8 @@ $sect = "copy";
 is(have_stats('database', $dboid, 0), 't', "$sect: db stats do exist");
 is(have_stats('function', $dboid, $funcoid),
 	't', "$sect: function stats do exist");
-is(have_stats('relation', $dboid, $tableoid),
-	't', "$sect: relation stats do exist");
+is(have_stats('table', $dboid, $tableoid),
+	't', "$sect: table stats do exist");
 
 $node->stop('immediate');
 
@@ -84,8 +84,8 @@ $sect = "post immediate";
 is(have_stats('database', $dboid, 0), 'f', "$sect: db stats do not exist");
 is(have_stats('function', $dboid, $funcoid),
 	'f', "$sect: function stats do exist");
-is(have_stats('relation', $dboid, $tableoid),
-	'f', "$sect: relation stats do not exist");
+is(have_stats('table', $dboid, $tableoid),
+	'f', "$sect: table stats do not exist");
 
 # get rid of backup statsfile
 unlink $statsfile or die "cannot unlink $statsfile $!";
@@ -98,8 +98,8 @@ $sect = "post immediate, new";
 is(have_stats('database', $dboid, 0), 't', "$sect: db stats do exist");
 is(have_stats('function', $dboid, $funcoid),
 	't', "$sect: function stats do exist");
-is(have_stats('relation', $dboid, $tableoid),
-	't', "$sect: relation stats do exist");
+is(have_stats('table', $dboid, $tableoid),
+	't', "$sect: table stats do exist");
 
 # regular shutdown
 $node->stop();
@@ -117,8 +117,7 @@ $sect = "invalid_overwrite";
 is(have_stats('database', $dboid, 0), 'f', "$sect: db stats do not exist");
 is(have_stats('function', $dboid, $funcoid),
 	'f', "$sect: function stats do not exist");
-is(have_stats('relation', $dboid, $tableoid),
-	'f', "$sect: relation stats do not exist");
+is(have_stats('table', $dboid, $tableoid), 'f', "$sect: table do not exist");
 
 
 ## check invalid stats file starting with valid contents, but followed by
@@ -133,8 +132,8 @@ $sect = "invalid_append";
 is(have_stats('database', $dboid, 0), 'f', "$sect: db stats do not exist");
 is(have_stats('function', $dboid, $funcoid),
 	'f', "$sect: function stats do not exist");
-is(have_stats('relation', $dboid, $tableoid),
-	'f', "$sect: relation stats do not exist");
+is(have_stats('table', $dboid, $tableoid),
+	'f', "$sect: table stats do not exist");
 
 
 ## checks related to stats persistency around restarts and resets
diff --git a/src/test/recovery/t/030_stats_cleanup_replica.pl b/src/test/recovery/t/030_stats_cleanup_replica.pl
index cc92ddbb52..1e78b13cf9 100644
--- a/src/test/recovery/t/030_stats_cleanup_replica.pl
+++ b/src/test/recovery/t/030_stats_cleanup_replica.pl
@@ -185,7 +185,7 @@ sub test_standby_func_tab_stats_status
 	my %stats;
 
 	$stats{rel} = $node_standby->safe_psql($connect_db,
-		"SELECT pg_stat_have_stats('relation', $dboid, $tableoid)");
+		"SELECT pg_stat_have_stats('table', $dboid, $tableoid)");
 	$stats{func} = $node_standby->safe_psql($connect_db,
 		"SELECT pg_stat_have_stats('function', $dboid, $funcoid)");
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 624d0e5aae..3d19d1a833 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1764,10 +1764,10 @@ pg_stat_all_indexes| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
     i.relname AS indexrelname,
-    pg_stat_get_numscans(i.oid) AS idx_scan,
-    pg_stat_get_lastscan(i.oid) AS last_idx_scan,
-    pg_stat_get_tuples_returned(i.oid) AS idx_tup_read,
-    pg_stat_get_tuples_fetched(i.oid) AS idx_tup_fetch
+    pg_stat_get_index_numscans(i.oid) AS idx_scan,
+    pg_stat_get_index_lastscan(i.oid) AS last_idx_scan,
+    pg_stat_get_index_tuples_returned(i.oid) AS idx_tup_read,
+    pg_stat_get_index_tuples_fetched(i.oid) AS idx_tup_fetch
    FROM (((pg_class c
      JOIN pg_index x ON ((c.oid = x.indrelid)))
      JOIN pg_class i ON ((i.oid = x.indexrelid)))
@@ -1776,12 +1776,12 @@ pg_stat_all_indexes| SELECT c.oid AS relid,
 pg_stat_all_tables| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
-    pg_stat_get_numscans(c.oid) AS seq_scan,
-    pg_stat_get_lastscan(c.oid) AS last_seq_scan,
-    pg_stat_get_tuples_returned(c.oid) AS seq_tup_read,
-    (sum(pg_stat_get_numscans(i.indexrelid)))::bigint AS idx_scan,
-    max(pg_stat_get_lastscan(i.indexrelid)) AS last_idx_scan,
-    ((sum(pg_stat_get_tuples_fetched(i.indexrelid)))::bigint + pg_stat_get_tuples_fetched(c.oid)) AS idx_tup_fetch,
+    pg_stat_get_table_numscans(c.oid) AS seq_scan,
+    pg_stat_get_table_lastscan(c.oid) AS last_seq_scan,
+    pg_stat_get_table_tuples_returned(c.oid) AS seq_tup_read,
+    (sum(pg_stat_get_index_numscans(i.indexrelid)))::bigint AS idx_scan,
+    max(pg_stat_get_index_lastscan(i.indexrelid)) AS last_idx_scan,
+    ((sum(pg_stat_get_index_tuples_fetched(i.indexrelid)))::bigint + pg_stat_get_table_tuples_fetched(c.oid)) AS idx_tup_fetch,
     pg_stat_get_tuples_inserted(c.oid) AS n_tup_ins,
     pg_stat_get_tuples_updated(c.oid) AS n_tup_upd,
     pg_stat_get_tuples_deleted(c.oid) AS n_tup_del,
@@ -2221,9 +2221,9 @@ pg_stat_wal_receiver| SELECT s.pid,
 pg_stat_xact_all_tables| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
-    pg_stat_get_xact_numscans(c.oid) AS seq_scan,
+    pg_stat_get_table_xact_numscans(c.oid) AS seq_scan,
     pg_stat_get_xact_tuples_returned(c.oid) AS seq_tup_read,
-    (sum(pg_stat_get_xact_numscans(i.indexrelid)))::bigint AS idx_scan,
+    (sum(pg_stat_get_index_xact_numscans(i.indexrelid)))::bigint AS idx_scan,
     ((sum(pg_stat_get_xact_tuples_fetched(i.indexrelid)))::bigint + pg_stat_get_xact_tuples_fetched(c.oid)) AS idx_tup_fetch,
     pg_stat_get_xact_tuples_inserted(c.oid) AS n_tup_ins,
     pg_stat_get_xact_tuples_updated(c.oid) AS n_tup_upd,
@@ -2274,8 +2274,8 @@ pg_statio_all_indexes| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
     i.relname AS indexrelname,
-    (pg_stat_get_blocks_fetched(i.oid) - pg_stat_get_blocks_hit(i.oid)) AS idx_blks_read,
-    pg_stat_get_blocks_hit(i.oid) AS idx_blks_hit
+    (pg_stat_get_index_blocks_fetched(i.oid) - pg_stat_get_index_blocks_hit(i.oid)) AS idx_blks_read,
+    pg_stat_get_index_blocks_hit(i.oid) AS idx_blks_hit
    FROM (((pg_class c
      JOIN pg_index x ON ((c.oid = x.indrelid)))
      JOIN pg_class i ON ((i.oid = x.indexrelid)))
@@ -2284,31 +2284,31 @@ pg_statio_all_indexes| SELECT c.oid AS relid,
 pg_statio_all_sequences| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
-    (pg_stat_get_blocks_fetched(c.oid) - pg_stat_get_blocks_hit(c.oid)) AS blks_read,
-    pg_stat_get_blocks_hit(c.oid) AS blks_hit
+    (pg_stat_get_index_blocks_fetched(c.oid) - pg_stat_get_table_blocks_hit(c.oid)) AS blks_read,
+    pg_stat_get_table_blocks_hit(c.oid) AS blks_hit
    FROM (pg_class c
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE (c.relkind = 'S'::"char");
 pg_statio_all_tables| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
-    (pg_stat_get_blocks_fetched(c.oid) - pg_stat_get_blocks_hit(c.oid)) AS heap_blks_read,
-    pg_stat_get_blocks_hit(c.oid) AS heap_blks_hit,
+    (pg_stat_get_table_blocks_fetched(c.oid) - pg_stat_get_table_blocks_hit(c.oid)) AS heap_blks_read,
+    pg_stat_get_table_blocks_hit(c.oid) AS heap_blks_hit,
     i.idx_blks_read,
     i.idx_blks_hit,
-    (pg_stat_get_blocks_fetched(t.oid) - pg_stat_get_blocks_hit(t.oid)) AS toast_blks_read,
-    pg_stat_get_blocks_hit(t.oid) AS toast_blks_hit,
+    (pg_stat_get_table_blocks_fetched(t.oid) - pg_stat_get_table_blocks_hit(t.oid)) AS toast_blks_read,
+    pg_stat_get_table_blocks_hit(t.oid) AS toast_blks_hit,
     x.idx_blks_read AS tidx_blks_read,
     x.idx_blks_hit AS tidx_blks_hit
    FROM ((((pg_class c
      LEFT JOIN pg_class t ON ((c.reltoastrelid = t.oid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
-     LEFT JOIN LATERAL ( SELECT (sum((pg_stat_get_blocks_fetched(pg_index.indexrelid) - pg_stat_get_blocks_hit(pg_index.indexrelid))))::bigint AS idx_blks_read,
-            (sum(pg_stat_get_blocks_hit(pg_index.indexrelid)))::bigint AS idx_blks_hit
+     LEFT JOIN LATERAL ( SELECT (sum((pg_stat_get_index_blocks_fetched(pg_index.indexrelid) - pg_stat_get_index_blocks_hit(pg_index.indexrelid))))::bigint AS idx_blks_read,
+            (sum(pg_stat_get_index_blocks_hit(pg_index.indexrelid)))::bigint AS idx_blks_hit
            FROM pg_index
           WHERE (pg_index.indrelid = c.oid)) i ON (true))
-     LEFT JOIN LATERAL ( SELECT (sum((pg_stat_get_blocks_fetched(pg_index.indexrelid) - pg_stat_get_blocks_hit(pg_index.indexrelid))))::bigint AS idx_blks_read,
-            (sum(pg_stat_get_blocks_hit(pg_index.indexrelid)))::bigint AS idx_blks_hit
+     LEFT JOIN LATERAL ( SELECT (sum((pg_stat_get_index_blocks_fetched(pg_index.indexrelid) - pg_stat_get_index_blocks_hit(pg_index.indexrelid))))::bigint AS idx_blks_read,
+            (sum(pg_stat_get_index_blocks_hit(pg_index.indexrelid)))::bigint AS idx_blks_hit
            FROM pg_index
           WHERE (pg_index.indrelid = t.oid)) x ON (true))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]));
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index 257a6a9da9..449d70f4c7 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -151,6 +151,117 @@ FROM prevstats AS pr;
 
 COMMIT;
 ----
+-- Basic tests for toast
+---
+CREATE TABLE stats_toast(stuff text);
+ALTER TABLE stats_toast ALTER COLUMN stuff SET STORAGE EXTERNAL;
+INSERT INTO stats_toast VALUES (repeat('a',1000000));
+SELECT count(*) FROM stats_toast WHERE stuff like '%a%';
+ count 
+-------
+     1
+(1 row)
+
+-- ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+select toast_blks_read > 0 as toast_blks_read, toast_blks_hit > 0 as toast_blks_hit,
+       tidx_blks_read > 0 as tidx_blks_read, tidx_blks_hit > 0 as tidx_blks_hit,
+       toast_blks_hit >= toast_blks_read,
+       tidx_blks_hit >= tidx_blks_read
+   from pg_statio_all_tables
+ where relname = 'stats_toast';
+ toast_blks_read | toast_blks_hit | tidx_blks_read | tidx_blks_hit | ?column? | ?column? 
+-----------------+----------------+----------------+---------------+----------+----------
+ t               | t              | t              | t             | t        | t
+(1 row)
+
+----
+-- Basic tests for partition
+---
+CREATE TABLE stats_partition (
+    id1         int not null,
+    id2         int not null
+) PARTITION BY RANGE (id2);
+CREATE TABLE stats_partition_5 PARTITION OF stats_partition
+    FOR VALUES FROM (0) TO (5);
+CREATE TABLE stats_partition_20000 PARTITION OF stats_partition
+    FOR VALUES FROM (5) TO (20001);
+CREATE INDEX ON stats_partition (id2);
+insert into stats_partition select a,a from generate_series(0,20000) a;
+SET enable_seqscan TO off;
+SET enable_bitmapscan TO off;
+select * from stats_partition where id2 = 2000;
+ id1  | id2  
+------+------
+ 2000 | 2000
+(1 row)
+
+select * from stats_partition where id2 = 2;
+ id1 | id2 
+-----+-----
+   2 |   2
+(1 row)
+
+-- ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+select relname, indexrelname, idx_scan > 0 as idx_scan, idx_tup_read > 0 as idx_tup_read,
+       idx_tup_fetch > 0 as idx_tup_fetch
+   from pg_stat_all_indexes
+ where relname like '%stats_partition%'
+ order by relname COLLATE "C";
+        relname        |         indexrelname          | idx_scan | idx_tup_read | idx_tup_fetch 
+-----------------------+-------------------------------+----------+--------------+---------------
+ stats_partition_20000 | stats_partition_20000_id2_idx | t        | t            | t
+ stats_partition_5     | stats_partition_5_id2_idx     | t        | t            | t
+(2 rows)
+
+select relname, indexrelname, idx_blks_read > 0 as idx_blks_read,
+       idx_blks_hit > 0 as idx_blks_hit
+   from pg_statio_all_indexes
+ where relname like '%stats_partition%'
+ order by relname COLLATE "C";
+        relname        |         indexrelname          | idx_blks_read | idx_blks_hit 
+-----------------------+-------------------------------+---------------+--------------
+ stats_partition_20000 | stats_partition_20000_id2_idx | t             | t
+ stats_partition_5     | stats_partition_5_id2_idx     | t             | t
+(2 rows)
+
+select relname,seq_scan > 0 as seq_scan, seq_tup_read > 0 as seq_tup_read,
+       idx_scan > 0 as idx_scan, idx_tup_fetch > 0 as idx_tup_fetch
+   from pg_stat_all_tables
+  where relname like '%stats_partition%'
+  order by relname COLLATE "C";
+        relname        | seq_scan | seq_tup_read | idx_scan | idx_tup_fetch 
+-----------------------+----------+--------------+----------+---------------
+ stats_partition       | f        | f            | f        | f
+ stats_partition_20000 | t        | f            | t        | t
+ stats_partition_5     | t        | f            | t        | t
+(3 rows)
+
+select relname, heap_blks_read > 0 as heap_blks_read, heap_blks_hit > 0 as heap_blks_hit,
+       idx_blks_read > 0 as idx_blks_read, idx_blks_hit > 0 as idx_blks_hit
+   from pg_statio_all_tables
+ where relname like '%stats_partition%'
+ order by relname COLLATE "C";
+        relname        | heap_blks_read | heap_blks_hit | idx_blks_read | idx_blks_hit 
+-----------------------+----------------+---------------+---------------+--------------
+ stats_partition_20000 | t              | t             | t             | t
+ stats_partition_5     | t              | t             | t             | t
+(2 rows)
+
+SET enable_seqscan TO on;
+SET enable_bitmapscan TO on;
+----
 -- Basic tests for track_functions
 ---
 CREATE FUNCTION stats_test_func1() RETURNS VOID LANGUAGE plpgsql AS $$BEGIN END;$$;
@@ -1015,21 +1126,21 @@ select a from stats_test_tab1 where a = 3;
  3
 (1 row)
 
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('index', :dboid, :stats_test_idx1_oid);
  pg_stat_have_stats 
 --------------------
  t
 (1 row)
 
 -- pg_stat_have_stats returns false for dropped index with stats
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('index', :dboid, :stats_test_idx1_oid);
  pg_stat_have_stats 
 --------------------
  t
 (1 row)
 
 DROP index stats_test_idx1;
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('index', :dboid, :stats_test_idx1_oid);
  pg_stat_have_stats 
 --------------------
  f
@@ -1045,14 +1156,14 @@ select a from stats_test_tab1 where a = 3;
  3
 (1 row)
 
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('index', :dboid, :stats_test_idx1_oid);
  pg_stat_have_stats 
 --------------------
  t
 (1 row)
 
 ROLLBACK;
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('index', :dboid, :stats_test_idx1_oid);
  pg_stat_have_stats 
 --------------------
  f
@@ -1067,7 +1178,7 @@ select a from stats_test_tab1 where a = 3;
  3
 (1 row)
 
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('index', :dboid, :stats_test_idx1_oid);
  pg_stat_have_stats 
 --------------------
  t
@@ -1075,7 +1186,7 @@ SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
 
 REINDEX index CONCURRENTLY stats_test_idx1;
 -- false for previous oid
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('index', :dboid, :stats_test_idx1_oid);
  pg_stat_have_stats 
 --------------------
  f
@@ -1083,7 +1194,7 @@ SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
 
 -- true for new oid
 SELECT 'stats_test_idx1'::regclass::oid AS stats_test_idx1_oid \gset
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('index', :dboid, :stats_test_idx1_oid);
  pg_stat_have_stats 
 --------------------
  t
@@ -1091,7 +1202,7 @@ SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
 
 -- pg_stat_have_stats returns true for a rolled back drop index with stats
 BEGIN;
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('index', :dboid, :stats_test_idx1_oid);
  pg_stat_have_stats 
 --------------------
  t
@@ -1099,7 +1210,7 @@ SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
 
 DROP index stats_test_idx1;
 ROLLBACK;
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('index', :dboid, :stats_test_idx1_oid);
  pg_stat_have_stats 
 --------------------
  t
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index f6270f7bad..c7fee71d6f 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -126,6 +126,77 @@ FROM prevstats AS pr;
 
 COMMIT;
 
+----
+-- Basic tests for toast
+---
+CREATE TABLE stats_toast(stuff text);
+ALTER TABLE stats_toast ALTER COLUMN stuff SET STORAGE EXTERNAL;
+INSERT INTO stats_toast VALUES (repeat('a',1000000));
+SELECT count(*) FROM stats_toast WHERE stuff like '%a%';
+
+-- ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+
+select toast_blks_read > 0 as toast_blks_read, toast_blks_hit > 0 as toast_blks_hit,
+       tidx_blks_read > 0 as tidx_blks_read, tidx_blks_hit > 0 as tidx_blks_hit,
+       toast_blks_hit >= toast_blks_read,
+       tidx_blks_hit >= tidx_blks_read
+   from pg_statio_all_tables
+ where relname = 'stats_toast';
+
+----
+-- Basic tests for partition
+---
+CREATE TABLE stats_partition (
+    id1         int not null,
+    id2         int not null
+) PARTITION BY RANGE (id2);
+
+CREATE TABLE stats_partition_5 PARTITION OF stats_partition
+    FOR VALUES FROM (0) TO (5);
+
+CREATE TABLE stats_partition_20000 PARTITION OF stats_partition
+    FOR VALUES FROM (5) TO (20001);
+
+CREATE INDEX ON stats_partition (id2);
+
+insert into stats_partition select a,a from generate_series(0,20000) a;
+
+SET enable_seqscan TO off;
+SET enable_bitmapscan TO off;
+
+select * from stats_partition where id2 = 2000;
+select * from stats_partition where id2 = 2;
+-- ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+
+select relname, indexrelname, idx_scan > 0 as idx_scan, idx_tup_read > 0 as idx_tup_read,
+       idx_tup_fetch > 0 as idx_tup_fetch
+   from pg_stat_all_indexes
+ where relname like '%stats_partition%'
+ order by relname COLLATE "C";
+
+select relname, indexrelname, idx_blks_read > 0 as idx_blks_read,
+       idx_blks_hit > 0 as idx_blks_hit
+   from pg_statio_all_indexes
+ where relname like '%stats_partition%'
+ order by relname COLLATE "C";
+
+select relname,seq_scan > 0 as seq_scan, seq_tup_read > 0 as seq_tup_read,
+       idx_scan > 0 as idx_scan, idx_tup_fetch > 0 as idx_tup_fetch
+   from pg_stat_all_tables
+  where relname like '%stats_partition%'
+  order by relname COLLATE "C";
+
+select relname, heap_blks_read > 0 as heap_blks_read, heap_blks_hit > 0 as heap_blks_hit,
+       idx_blks_read > 0 as idx_blks_read, idx_blks_hit > 0 as idx_blks_hit
+   from pg_statio_all_tables
+ where relname like '%stats_partition%'
+ order by relname COLLATE "C";
+
+SET enable_seqscan TO on;
+SET enable_bitmapscan TO on;
+
 ----
 -- Basic tests for track_functions
 ---
@@ -492,40 +563,40 @@ CREATE index stats_test_idx1 on stats_test_tab1(a);
 SELECT 'stats_test_idx1'::regclass::oid AS stats_test_idx1_oid \gset
 SET enable_seqscan TO off;
 select a from stats_test_tab1 where a = 3;
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('index', :dboid, :stats_test_idx1_oid);
 
 -- pg_stat_have_stats returns false for dropped index with stats
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('index', :dboid, :stats_test_idx1_oid);
 DROP index stats_test_idx1;
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('index', :dboid, :stats_test_idx1_oid);
 
 -- pg_stat_have_stats returns false for rolled back index creation
 BEGIN;
 CREATE index stats_test_idx1 on stats_test_tab1(a);
 SELECT 'stats_test_idx1'::regclass::oid AS stats_test_idx1_oid \gset
 select a from stats_test_tab1 where a = 3;
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('index', :dboid, :stats_test_idx1_oid);
 ROLLBACK;
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('index', :dboid, :stats_test_idx1_oid);
 
 -- pg_stat_have_stats returns true for reindex CONCURRENTLY
 CREATE index stats_test_idx1 on stats_test_tab1(a);
 SELECT 'stats_test_idx1'::regclass::oid AS stats_test_idx1_oid \gset
 select a from stats_test_tab1 where a = 3;
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('index', :dboid, :stats_test_idx1_oid);
 REINDEX index CONCURRENTLY stats_test_idx1;
 -- false for previous oid
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('index', :dboid, :stats_test_idx1_oid);
 -- true for new oid
 SELECT 'stats_test_idx1'::regclass::oid AS stats_test_idx1_oid \gset
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('index', :dboid, :stats_test_idx1_oid);
 
 -- pg_stat_have_stats returns true for a rolled back drop index with stats
 BEGIN;
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('index', :dboid, :stats_test_idx1_oid);
 DROP index stats_test_idx1;
 ROLLBACK;
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('index', :dboid, :stats_test_idx1_oid);
 
 -- put enable_seqscan back to on
 SET enable_seqscan TO on;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 9683b0a88e..0899cb471e 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2009,10 +2009,11 @@ PgStatShared_Common
 PgStatShared_Database
 PgStatShared_Function
 PgStatShared_HashEntry
-PgStatShared_Relation
+PgStatShared_Index
 PgStatShared_ReplSlot
 PgStatShared_SLRU
 PgStatShared_Subscription
+PgStatShared_Table
 PgStatShared_Wal
 PgStat_ArchiverStats
 PgStat_BackendFunctionEntry
-- 
2.34.1

