Split index and table statistics into different types of stats

Started by Drouvot, Bertrandabout 3 years ago36 messages
#1Drouvot, Bertrand
bertranddrouvot.pg@gmail.com
1 attachment(s)

Hi hackers,

Please find attached a patch proposal to split index and table
statistics into different types of stats.

This idea has been proposed by Andres in a couple of threads, see [1]/messages/by-id/20221019181930.bx73kul4nbiftr65@awork3.anarazel.de
and [2]/messages/by-id/20220818195124.c7ipzf6c5v7vxymc@awork3.anarazel.de.

To sum up:

We currently track index and table types of statistics in the same
format (so that a number of the "columns" in index stats are currently
unused) and we rename column in views etc to make them somewhat sensible.

So that the immediate benefits to $SUBJECT are:

- have reasonable names for the fields
- shrink the current memory usage

The attached patch proposal:

- renames PGSTAT_KIND_RELATION to PGSTAT_KIND_TABLE
- creates a new PGSTAT_KIND_INDEX
- creates new macros: pgstat_count_index_fetch(),
pgstat_count_index_scan(), pgstat_count_index_tuples(),
pgstat_count_index_buffer_read() and pgstat_count_index_buffer_hit() to
increment the indexes related stats
- creates new SQL callable functions dedicated to the indexes that are
used in system_views.sql

It also adds basic tests in src/test/regress/sql/stats.sql for toast and
partitions (we may want to create a dedicated patch for those additional
tests though).

The fields renaming has not been done to ease the reading of this patch
(I think it would be better to create a dedicated patch for the renaming
once the split is done).

I'm adding a new CF entry for this patch.

Looking forward to your feedback,

Regards,

--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

[1]: /messages/by-id/20221019181930.bx73kul4nbiftr65@awork3.anarazel.de
/messages/by-id/20221019181930.bx73kul4nbiftr65@awork3.anarazel.de

[2]: /messages/by-id/20220818195124.c7ipzf6c5v7vxymc@awork3.anarazel.de
/messages/by-id/20220818195124.c7ipzf6c5v7vxymc@awork3.anarazel.de

Attachments:

v1-0001-split_tables_indexes_stats.patchtext/plain; charset=UTF-8; name=v1-0001-split_tables_indexes_stats.patchDownload
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..8a715db82e 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1853,7 +1853,7 @@ heap_drop_with_catalog(Oid relid)
 		RelationDropStorage(rel);
 
 	/* ensure that stats are dropped if transaction commits */
-	pgstat_drop_relation(rel);
+	pgstat_drop_heap(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..e92e50edf7 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_heap_numscans(C.oid) AS seq_scan,
+            pg_stat_get_heap_lastscan(C.oid) AS last_seq_scan,
+            pg_stat_get_heap_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_heap_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_heap_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_heap_blocks_fetched(C.oid) -
+                    pg_stat_get_heap_blocks_hit(C.oid) AS heap_blks_read,
+            pg_stat_get_heap_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_heap_blocks_fetched(T.oid) -
+                    pg_stat_get_heap_blocks_hit(T.oid) AS toast_blks_read,
+            pg_stat_get_heap_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_heap_blocks_hit(C.oid) AS blks_read,
+            pg_stat_get_heap_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..2e3b982aae 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_heap_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_heap_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..5d30b4c242 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,14 +183,25 @@ 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;
+	}
 }
 
 /*
@@ -168,39 +210,55 @@ pgstat_unlink_relation(Relation rel)
 void
 pgstat_create_relation(Relation rel)
 {
-	pgstat_create_transactional(PGSTAT_KIND_RELATION,
-								rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
-								RelationGetRelid(rel));
+	if (rel->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_create_transactional(PGSTAT_KIND_INDEX,
+									rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+									RelationGetRelid(rel));
+	else
+		pgstat_create_transactional(PGSTAT_KIND_TABLE,
+									rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+									RelationGetRelid(rel));
+}
+
+/*
+ * Ensure that index stats are dropped if transaction commits.
+ */
+void
+pgstat_drop_index(Relation rel)
+{
+	pgstat_drop_transactional(PGSTAT_KIND_INDEX,
+							  rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+							  RelationGetRelid(rel));
 }
 
 /*
- * Ensure that stats are dropped if transaction commits.
+ * Ensure that table stats are dropped if transaction commits.
  */
 void
-pgstat_drop_relation(Relation rel)
+pgstat_drop_heap(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 +270,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 +282,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 +329,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 +348,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 +406,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 +421,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 +440,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 +455,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 +478,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 +518,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 +569,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_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_RELATION, InvalidOid, rel_id);
+		entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_INDEX, InvalidOid, rel_id);
 
 	if (entry_ref)
 		return entry_ref->pending;
@@ -694,27 +809,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 +845,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 +857,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 +874,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 +906,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 +954,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 +1029,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 +1058,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 +1098,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..e1a5347e13 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -37,23 +37,37 @@
 #define HAS_PGSTAT_PERMISSIONS(role)	 (has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS) || has_privs_of_role(GetUserId(), role))
 
 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;
 
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
+	if ((indentry = pgstat_fetch_stat_indentry(relid)) == NULL)
 		result = 0;
 	else
-		result = (int64) (tabentry->numscans);
+		result = (int64) (indentry->numscans);
 
 	PG_RETURN_INT64(result);
 }
 
+Datum
+pg_stat_get_heap_numscans(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int64		result;
+	PgStat_StatTabEntry *relentry;
+
+	if ((relentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
+		result = 0;
+	else
+		result = (int64) (relentry->numscans);
+
+	PG_RETURN_INT64(result);
+}
 
 Datum
-pg_stat_get_lastscan(PG_FUNCTION_ARGS)
+pg_stat_get_heap_lastscan(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	PgStat_StatTabEntry *tabentry;
@@ -61,12 +75,23 @@ pg_stat_get_lastscan(PG_FUNCTION_ARGS)
 	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
 		PG_RETURN_NULL();
 	else
-		PG_RETURN_TIMESTAMPTZ(tabentry->lastscan);
+		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_heap_tuples_returned(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
@@ -79,10 +104,23 @@ pg_stat_get_tuples_returned(PG_FUNCTION_ARGS)
 
 	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;
+
+	if ((indentry = pgstat_fetch_stat_indentry(relid)) == NULL)
+		result = 0;
+	else
+		result = (int64) (indentry->tuples_returned);
 
+	PG_RETURN_INT64(result);
+}
 
 Datum
-pg_stat_get_tuples_fetched(PG_FUNCTION_ARGS)
+pg_stat_get_heap_tuples_fetched(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
@@ -96,6 +134,21 @@ pg_stat_get_tuples_fetched(PG_FUNCTION_ARGS)
 	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;
+
+	if ((indentry = pgstat_fetch_stat_indentry(relid)) == NULL)
+		result = 0;
+	else
+		result = (int64) (indentry->tuples_fetched);
+
+	PG_RETURN_INT64(result);
+}
+
 
 Datum
 pg_stat_get_tuples_inserted(PG_FUNCTION_ARGS)
@@ -226,7 +279,7 @@ pg_stat_get_ins_since_vacuum(PG_FUNCTION_ARGS)
 
 
 Datum
-pg_stat_get_blocks_fetched(PG_FUNCTION_ARGS)
+pg_stat_get_heap_blocks_fetched(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
@@ -240,9 +293,23 @@ pg_stat_get_blocks_fetched(PG_FUNCTION_ARGS)
 	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;
+
+	if ((indentry = pgstat_fetch_stat_indentry(relid)) == NULL)
+		result = 0;
+	else
+		result = (int64) (indentry->blocks_fetched);
+
+	PG_RETURN_INT64(result);
+}
 
 Datum
-pg_stat_get_blocks_hit(PG_FUNCTION_ARGS)
+pg_stat_get_heap_blocks_hit(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
@@ -256,6 +323,21 @@ pg_stat_get_blocks_hit(PG_FUNCTION_ARGS)
 	PG_RETURN_INT64(result);
 }
 
+Datum
+pg_stat_get_index_blocks_hit(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int64		result;
+	PgStat_StatIndEntry *indentry;
+
+	if ((indentry = pgstat_fetch_stat_indentry(relid)) == NULL)
+		result = 0;
+	else
+		result = (int64) (indentry->blocks_hit);
+
+	PG_RETURN_INT64(result);
+}
+
 Datum
 pg_stat_get_last_vacuum_time(PG_FUNCTION_ARGS)
 {
@@ -1837,7 +1919,7 @@ pg_stat_get_slru(PG_FUNCTION_ARGS)
 }
 
 Datum
-pg_stat_get_xact_numscans(PG_FUNCTION_ARGS)
+pg_stat_get_heap_xact_numscans(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
@@ -1851,17 +1933,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 +1968,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 +2061,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 +2076,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 +2200,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..7733ebfcb9 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_heap_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_heap_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_heap_lastscan', provolatile => 's', proparallel => 'r',
+  prorettype => 'timestamptz', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_heap_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_heap_tuples_returned', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_returned' },
+  prosrc => 'pg_stat_get_heap_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_heap_tuples_fetched', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_heap_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_heap_blocks_fetched', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_blocks_fetched' },
+  prosrc => 'pg_stat_get_heap_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_heap_blocks_hit', provolatile => 's', proparallel => 'r',
+  prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_heap_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_heap_xact_numscans', provolatile => 'v',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_heap_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..65b1afe971 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;
@@ -498,11 +545,15 @@ extern PgStat_BackendFunctionEntry *find_funcstat_entry(Oid func_id);
  */
 
 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_drop_heap(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_heap_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_heap_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 c869533b28..b7464b5a5c 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -360,11 +360,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
 {
@@ -559,8 +565,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..6ff2cfbd97 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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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..0ca25393e4 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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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..531164e030 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_heap_numscans(c.oid) AS seq_scan,
+    pg_stat_get_heap_lastscan(c.oid) AS last_seq_scan,
+    pg_stat_get_heap_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_heap_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_heap_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_heap_blocks_hit(c.oid)) AS blks_read,
+    pg_stat_get_heap_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_heap_blocks_fetched(c.oid) - pg_stat_get_heap_blocks_hit(c.oid)) AS heap_blks_read,
+    pg_stat_get_heap_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_heap_blocks_fetched(t.oid) - pg_stat_get_heap_blocks_hit(t.oid)) AS toast_blks_read,
+    pg_stat_get_heap_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 2f02cc8f42..38806d5dee 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
#2Drouvot, Bertrand
bertranddrouvot.pg@gmail.com
In reply to: Drouvot, Bertrand (#1)
1 attachment(s)
Re: Split index and table statistics into different types of stats

Hi,

On 10/31/22 2:31 PM, Justin Pryzby wrote:

I didn't looks closely, but there's a couple places where you wrote
";;", which looks unintentional.

-               PG_RETURN_TIMESTAMPTZ(tabentry->lastscan);
+               PG_RETURN_TIMESTAMPTZ(tabentry->lastscan);;

Thanks for looking at it!
oops, thanks for the keen eyes ;-) Fixed in v2 attached.

Regards,

--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

Attachments:

v2-0001-split_tables_indexes_stats.patchtext/plain; charset=UTF-8; name=v2-0001-split_tables_indexes_stats.patchDownload
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..8a715db82e 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1853,7 +1853,7 @@ heap_drop_with_catalog(Oid relid)
 		RelationDropStorage(rel);
 
 	/* ensure that stats are dropped if transaction commits */
-	pgstat_drop_relation(rel);
+	pgstat_drop_heap(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..e92e50edf7 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_heap_numscans(C.oid) AS seq_scan,
+            pg_stat_get_heap_lastscan(C.oid) AS last_seq_scan,
+            pg_stat_get_heap_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_heap_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_heap_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_heap_blocks_fetched(C.oid) -
+                    pg_stat_get_heap_blocks_hit(C.oid) AS heap_blks_read,
+            pg_stat_get_heap_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_heap_blocks_fetched(T.oid) -
+                    pg_stat_get_heap_blocks_hit(T.oid) AS toast_blks_read,
+            pg_stat_get_heap_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_heap_blocks_hit(C.oid) AS blks_read,
+            pg_stat_get_heap_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..2e3b982aae 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_heap_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_heap_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..5d30b4c242 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,14 +183,25 @@ 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;
+	}
 }
 
 /*
@@ -168,39 +210,55 @@ pgstat_unlink_relation(Relation rel)
 void
 pgstat_create_relation(Relation rel)
 {
-	pgstat_create_transactional(PGSTAT_KIND_RELATION,
-								rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
-								RelationGetRelid(rel));
+	if (rel->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_create_transactional(PGSTAT_KIND_INDEX,
+									rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+									RelationGetRelid(rel));
+	else
+		pgstat_create_transactional(PGSTAT_KIND_TABLE,
+									rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+									RelationGetRelid(rel));
+}
+
+/*
+ * Ensure that index stats are dropped if transaction commits.
+ */
+void
+pgstat_drop_index(Relation rel)
+{
+	pgstat_drop_transactional(PGSTAT_KIND_INDEX,
+							  rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+							  RelationGetRelid(rel));
 }
 
 /*
- * Ensure that stats are dropped if transaction commits.
+ * Ensure that table stats are dropped if transaction commits.
  */
 void
-pgstat_drop_relation(Relation rel)
+pgstat_drop_heap(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 +270,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 +282,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 +329,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 +348,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 +406,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 +421,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 +440,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 +455,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 +478,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 +518,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 +569,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_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_RELATION, InvalidOid, rel_id);
+		entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_INDEX, InvalidOid, rel_id);
 
 	if (entry_ref)
 		return entry_ref->pending;
@@ -694,27 +809,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 +845,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 +857,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 +874,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 +906,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 +954,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 +1029,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 +1058,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 +1098,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..958761460b 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -37,23 +37,37 @@
 #define HAS_PGSTAT_PERMISSIONS(role)	 (has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS) || has_privs_of_role(GetUserId(), role))
 
 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;
 
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
+	if ((indentry = pgstat_fetch_stat_indentry(relid)) == NULL)
 		result = 0;
 	else
-		result = (int64) (tabentry->numscans);
+		result = (int64) (indentry->numscans);
 
 	PG_RETURN_INT64(result);
 }
 
+Datum
+pg_stat_get_heap_numscans(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int64		result;
+	PgStat_StatTabEntry *relentry;
+
+	if ((relentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
+		result = 0;
+	else
+		result = (int64) (relentry->numscans);
+
+	PG_RETURN_INT64(result);
+}
 
 Datum
-pg_stat_get_lastscan(PG_FUNCTION_ARGS)
+pg_stat_get_heap_lastscan(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	PgStat_StatTabEntry *tabentry;
@@ -64,9 +78,20 @@ 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_heap_tuples_returned(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
@@ -79,10 +104,23 @@ pg_stat_get_tuples_returned(PG_FUNCTION_ARGS)
 
 	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;
+
+	if ((indentry = pgstat_fetch_stat_indentry(relid)) == NULL)
+		result = 0;
+	else
+		result = (int64) (indentry->tuples_returned);
 
+	PG_RETURN_INT64(result);
+}
 
 Datum
-pg_stat_get_tuples_fetched(PG_FUNCTION_ARGS)
+pg_stat_get_heap_tuples_fetched(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
@@ -96,6 +134,21 @@ pg_stat_get_tuples_fetched(PG_FUNCTION_ARGS)
 	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;
+
+	if ((indentry = pgstat_fetch_stat_indentry(relid)) == NULL)
+		result = 0;
+	else
+		result = (int64) (indentry->tuples_fetched);
+
+	PG_RETURN_INT64(result);
+}
+
 
 Datum
 pg_stat_get_tuples_inserted(PG_FUNCTION_ARGS)
@@ -226,7 +279,7 @@ pg_stat_get_ins_since_vacuum(PG_FUNCTION_ARGS)
 
 
 Datum
-pg_stat_get_blocks_fetched(PG_FUNCTION_ARGS)
+pg_stat_get_heap_blocks_fetched(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
@@ -240,9 +293,23 @@ pg_stat_get_blocks_fetched(PG_FUNCTION_ARGS)
 	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;
+
+	if ((indentry = pgstat_fetch_stat_indentry(relid)) == NULL)
+		result = 0;
+	else
+		result = (int64) (indentry->blocks_fetched);
+
+	PG_RETURN_INT64(result);
+}
 
 Datum
-pg_stat_get_blocks_hit(PG_FUNCTION_ARGS)
+pg_stat_get_heap_blocks_hit(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
@@ -256,6 +323,21 @@ pg_stat_get_blocks_hit(PG_FUNCTION_ARGS)
 	PG_RETURN_INT64(result);
 }
 
+Datum
+pg_stat_get_index_blocks_hit(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int64		result;
+	PgStat_StatIndEntry *indentry;
+
+	if ((indentry = pgstat_fetch_stat_indentry(relid)) == NULL)
+		result = 0;
+	else
+		result = (int64) (indentry->blocks_hit);
+
+	PG_RETURN_INT64(result);
+}
+
 Datum
 pg_stat_get_last_vacuum_time(PG_FUNCTION_ARGS)
 {
@@ -1837,7 +1919,7 @@ pg_stat_get_slru(PG_FUNCTION_ARGS)
 }
 
 Datum
-pg_stat_get_xact_numscans(PG_FUNCTION_ARGS)
+pg_stat_get_heap_xact_numscans(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
@@ -1851,17 +1933,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 +1968,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 +2061,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 +2076,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 +2200,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..7733ebfcb9 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_heap_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_heap_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_heap_lastscan', provolatile => 's', proparallel => 'r',
+  prorettype => 'timestamptz', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_heap_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_heap_tuples_returned', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_returned' },
+  prosrc => 'pg_stat_get_heap_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_heap_tuples_fetched', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_heap_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_heap_blocks_fetched', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_blocks_fetched' },
+  prosrc => 'pg_stat_get_heap_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_heap_blocks_hit', provolatile => 's', proparallel => 'r',
+  prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_heap_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_heap_xact_numscans', provolatile => 'v',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_heap_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..65b1afe971 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;
@@ -498,11 +545,15 @@ extern PgStat_BackendFunctionEntry *find_funcstat_entry(Oid func_id);
  */
 
 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_drop_heap(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_heap_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_heap_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 c869533b28..b7464b5a5c 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -360,11 +360,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
 {
@@ -559,8 +565,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..6ff2cfbd97 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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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..0ca25393e4 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_heap_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_heap_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..531164e030 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_heap_numscans(c.oid) AS seq_scan,
+    pg_stat_get_heap_lastscan(c.oid) AS last_seq_scan,
+    pg_stat_get_heap_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_heap_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_heap_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_heap_blocks_hit(c.oid)) AS blks_read,
+    pg_stat_get_heap_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_heap_blocks_fetched(c.oid) - pg_stat_get_heap_blocks_hit(c.oid)) AS heap_blks_read,
+    pg_stat_get_heap_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_heap_blocks_fetched(t.oid) - pg_stat_get_heap_blocks_hit(t.oid)) AS toast_blks_read,
+    pg_stat_get_heap_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 2f02cc8f42..38806d5dee 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
#3Andres Freund
andres@anarazel.de
In reply to: Drouvot, Bertrand (#1)
Re: Split index and table statistics into different types of stats

Hi,

On 2022-10-31 14:14:15 +0100, Drouvot, Bertrand wrote:

Please find attached a patch proposal to split index and table statistics
into different types of stats.

This idea has been proposed by Andres in a couple of threads, see [1] and
[2].

Thanks for working on this!

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 5b49cc5a09..8a715db82e 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1853,7 +1853,7 @@ heap_drop_with_catalog(Oid relid)
RelationDropStorage(rel);
/* ensure that stats are dropped if transaction commits */
-	pgstat_drop_relation(rel);
+	pgstat_drop_heap(rel);

I don't think "heap" is a good name for these, even if there's some historical
reasons for it. Particularly because you used "table" in some bits and pieces
too.

/*
@@ -168,39 +210,55 @@ pgstat_unlink_relation(Relation rel)
void
pgstat_create_relation(Relation rel)
{
-	pgstat_create_transactional(PGSTAT_KIND_RELATION,
-								rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
-								RelationGetRelid(rel));
+	if (rel->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_create_transactional(PGSTAT_KIND_INDEX,
+									rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+									RelationGetRelid(rel));
+	else
+		pgstat_create_transactional(PGSTAT_KIND_TABLE,
+									rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+									RelationGetRelid(rel));
+}

Hm - why is this best handled on this level, rather than at the caller?

+/*
+ * 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);
}

Do we need this split anywhere for now? I suspect not, the table case is
mainly for the autovacuum launcher, which won't look at indexes "in isolation".

@@ -240,9 +293,23 @@ pg_stat_get_blocks_fetched(PG_FUNCTION_ARGS)
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;
+
+	if ((indentry = pgstat_fetch_stat_indentry(relid)) == NULL)
+		result = 0;
+	else
+		result = (int64) (indentry->blocks_fetched);
+
+	PG_RETURN_INT64(result);
+}

We have so many copies of this by now - perhaps we first should deduplicate
them somehow? Even if it's just a macro or such.

Greetings,

Andres Freund

#4Drouvot, Bertrand
bertranddrouvot.pg@gmail.com
In reply to: Andres Freund (#3)
1 attachment(s)
Re: Split index and table statistics into different types of stats

Hi,

On 11/1/22 1:30 AM, Andres Freund wrote:

Hi,

On 2022-10-31 14:14:15 +0100, Drouvot, Bertrand wrote:

Please find attached a patch proposal to split index and table statistics
into different types of stats.

This idea has been proposed by Andres in a couple of threads, see [1] and
[2].

Thanks for working on this!

Thanks for looking at it!

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 5b49cc5a09..8a715db82e 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1853,7 +1853,7 @@ heap_drop_with_catalog(Oid relid)
RelationDropStorage(rel);
/* ensure that stats are dropped if transaction commits */
-	pgstat_drop_relation(rel);
+	pgstat_drop_heap(rel);

I don't think "heap" is a good name for these, even if there's some historical
reasons for it. Particularly because you used "table" in some bits and pieces
too.

Agree, replaced by "table" where appropriate in V3 attached.

/*
@@ -168,39 +210,55 @@ pgstat_unlink_relation(Relation rel)
void
pgstat_create_relation(Relation rel)
{
-	pgstat_create_transactional(PGSTAT_KIND_RELATION,
-								rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
-								RelationGetRelid(rel));
+	if (rel->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_create_transactional(PGSTAT_KIND_INDEX,
+									rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+									RelationGetRelid(rel));
+	else
+		pgstat_create_transactional(PGSTAT_KIND_TABLE,
+									rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+									RelationGetRelid(rel));
+}

Hm - why is this best handled on this level, rather than at the caller?

Agree that it should be split in
pgstat_create_table()/pgstat_create_index() (also as it was already
split for the "drop" case): done in V3.

+/*
+ * 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);
}

Do we need this split anywhere for now? I suspect not, the table case is
mainly for the autovacuum launcher, which won't look at indexes "in isolation".

Yes I think so as pgstat_fetch_stat_indentry_ext() has its use case in
pgstat_copy_index_stats() (previously pgstat_copy_relation_stats()).

@@ -240,9 +293,23 @@ pg_stat_get_blocks_fetched(PG_FUNCTION_ARGS)
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;
+
+	if ((indentry = pgstat_fetch_stat_indentry(relid)) == NULL)
+		result = 0;
+	else
+		result = (int64) (indentry->blocks_fetched);
+
+	PG_RETURN_INT64(result);
+}

We have so many copies of this by now - perhaps we first should deduplicate
them somehow? Even if it's just a macro or such.

Yeah good point, a new macro has been defined for the "int64" return
case in V3 attached.

Regards,

--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

Attachments:

v3-0001-split_tables_indexes_stats.patchtext/plain; charset=UTF-8; name=v3-0001-split_tables_indexes_stats.patchDownload
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
#5Melanie Plageman
melanieplageman@gmail.com
In reply to: Drouvot, Bertrand (#4)
4 attachment(s)
Re: Split index and table statistics into different types of stats

Hi Bertrand,

I'm glad you are working on this.

I had a few thoughts/ideas

It seems better to have all of the counts in the various stats structs
not be prefixed with n_, i_, t_

typedef struct PgStat_StatDBEntry
{
...
PgStat_Counter n_blocks_fetched;
PgStat_Counter n_blocks_hit;
PgStat_Counter n_tuples_returned;
PgStat_Counter n_tuples_fetched;
...

I've attached a patch (0002) to change this in case you are interested
in making such a change (I've attached all of my suggestions in patches
along with your original patch so that cfbot still passes).

On Wed, Nov 2, 2022 at 5:00 AM Drouvot, Bertrand
<bertranddrouvot.pg@gmail.com> wrote:

On 11/1/22 1:30 AM, Andres Freund wrote:

On 2022-10-31 14:14:15 +0100, Drouvot, Bertrand wrote:

@@ -240,9 +293,23 @@ pg_stat_get_blocks_fetched(PG_FUNCTION_ARGS)
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;
+
+    if ((indentry = pgstat_fetch_stat_indentry(relid)) == NULL)
+            result = 0;
+    else
+            result = (int64) (indentry->blocks_fetched);
+
+    PG_RETURN_INT64(result);
+}

We have so many copies of this by now - perhaps we first should deduplicate
them somehow? Even if it's just a macro or such.

Yeah good point, a new macro has been defined for the "int64" return
case in V3 attached.

I looked for other opportunities to de-duplicate, but most of the functions
that were added that are identical except the return type and
PgStat_Kind are short enough that it doesn't make sense to make wrappers
or macros.

I do think it makes sense to reorder the members of the two structs
PgStat_IndexCounts and PgStat_TableCounts so that they have a common
header. I've done that in the attached patch (0003).

In the flush functions, I was also thinking it might be nice to use the
same pattern as is used in [1]https://github.com/postgres/postgres/blob/master/src/backend/utils/activity/pgstat_checkpointer.c#L49 and [2]https://github.com/postgres/postgres/blob/master/src/backend/utils/activity/pgstat_database.c#L370 to add the counts together. It
makes the lines a bit easier to read, IMO. If we remove the prefixes
from the count fields, this works for many of the fields. I've attached
a patch (0004) that does something like this, in case you wanted to go
in this direction.

Since you have made new parallel functions for indexes and tables for
many of the functions in pgstat_relation.c, perhaps it makes sense to
split it into pgstat_table.c and pgstat_index.c?

One question I had about the original code (not related to your
refactor) is why the pending stats aren't memset in the flush functions
after aggregating them into the shared stats.

- Melanie

[1]: https://github.com/postgres/postgres/blob/master/src/backend/utils/activity/pgstat_checkpointer.c#L49
[2]: https://github.com/postgres/postgres/blob/master/src/backend/utils/activity/pgstat_database.c#L370

Attachments:

v4-0003-reorder-members.patchtext/x-patch; charset=US-ASCII; name=v4-0003-reorder-members.patchDownload
From 507a0d00ff82b7ad356d7565a60024bd38292d46 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Fri, 4 Nov 2022 16:24:30 -0400
Subject: [PATCH v4 3/4] reorder members

---
 src/include/pgstat.h | 21 +++++++++------------
 1 file changed, 9 insertions(+), 12 deletions(-)

diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 4ec00afc46..eda3540561 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -146,6 +146,13 @@ typedef struct PgStat_BackendSubEntry
 	PgStat_Counter sync_error_count;
 } PgStat_BackendSubEntry;
 
+#define PgStat_RelationCounts \
+	PgStat_Counter numscans; \
+	PgStat_Counter tuples_returned; \
+	PgStat_Counter tuples_fetched; \
+	PgStat_Counter blocks_fetched; \
+	PgStat_Counter blocks_hit;
+
 /* ----------
  * PgStat_IndexCounts			The actual per-index counts kept by a backend
  *
@@ -160,12 +167,7 @@ typedef struct PgStat_BackendSubEntry
  */
 typedef struct PgStat_IndexCounts
 {
-	PgStat_Counter numscans;
-
-	PgStat_Counter tuples_returned;
-	PgStat_Counter tuples_fetched;
-	PgStat_Counter blocks_fetched;
-	PgStat_Counter blocks_hit;
+	PgStat_RelationCounts
 } PgStat_IndexCounts;
 
 /* ----------
@@ -187,10 +189,7 @@ typedef struct PgStat_IndexCounts
  */
 typedef struct PgStat_TableCounts
 {
-	PgStat_Counter numscans;
-
-	PgStat_Counter tuples_returned;
-	PgStat_Counter tuples_fetched;
+	PgStat_RelationCounts
 
 	PgStat_Counter tuples_inserted;
 	PgStat_Counter tuples_updated;
@@ -202,8 +201,6 @@ typedef struct PgStat_TableCounts
 	PgStat_Counter delta_dead_tuples;
 	PgStat_Counter changed_tuples;
 
-	PgStat_Counter blocks_fetched;
-	PgStat_Counter blocks_hit;
 } PgStat_TableCounts;
 
 /* ----------
-- 
2.34.1

v4-0001-Existing-patch-to-split-tab-and-idx.patchtext/x-patch; charset=US-ASCII; name=v4-0001-Existing-patch-to-split-tab-and-idx.patchDownload
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

v4-0004-condense-flush-functions.patchtext/x-patch; charset=US-ASCII; name=v4-0004-condense-flush-functions.patchDownload
From 8e6124eb50d09fa09bec07ade2e53fcec27033a3 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Fri, 4 Nov 2022 16:02:30 -0400
Subject: [PATCH v4 4/4] condense flush functions

---
 src/backend/utils/activity/pgstat_relation.c | 153 ++++++++++---------
 1 file changed, 82 insertions(+), 71 deletions(-)

diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c
index 94fbc6faea..d46d58da1e 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_relation.c
@@ -883,83 +883,92 @@ bool
 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_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_Table *) entry_ref->shared_stats;
+	PgStat_StatDBEntry *dbentry;
+	PgStat_StatTabEntry *stats_shmem;
+
+	PgStat_TableCounts *stats_pending =
+		&((PgStat_TableStatus *) entry_ref->pending)->t_counts;
 
 	/*
 	 * Ignore entries that didn't accumulate any actual counts.
 	 */
-	if (memcmp(&lstats->t_counts, &all_zeroes,
+	if (memcmp(stats_pending, &all_zeroes,
 			   sizeof(PgStat_TableCounts)) == 0)
-	{
 		return true;
-	}
 
 	if (!pgstat_lock_entry(entry_ref, nowait))
 		return false;
 
+	stats_shmem = &((PgStatShared_Table *) entry_ref->shared_stats)->stats;
+
 	/* add the values to the shared entry. */
-	tabentry = &shtabstats->stats;
+	stats_shmem->numscans += stats_pending->numscans;
 
-	tabentry->numscans += lstats->t_counts.t_numscans;
-	if (lstats->t_counts.t_numscans)
+	if (stats_pending->numscans)
 	{
 		TimestampTz t = GetCurrentTransactionStopTimestamp();
 
-		if (t > tabentry->lastscan)
-			tabentry->lastscan = t;
+		if (t > stats_shmem->lastscan)
+			stats_shmem->lastscan = t;
 	}
-	tabentry->tuples_returned += lstats->t_counts.t_tuples_returned;
-	tabentry->tuples_fetched += lstats->t_counts.t_tuples_fetched;
-	tabentry->tuples_inserted += lstats->t_counts.t_tuples_inserted;
-	tabentry->tuples_updated += lstats->t_counts.t_tuples_updated;
-	tabentry->tuples_deleted += lstats->t_counts.t_tuples_deleted;
-	tabentry->tuples_hot_updated += lstats->t_counts.t_tuples_hot_updated;
+
+#define PGSTAT_ACCUM_TAB(fld) \
+	(stats_shmem)->fld += (stats_pending)->fld
+
+	PGSTAT_ACCUM_TAB(tuples_returned);
+	PGSTAT_ACCUM_TAB(tuples_fetched);
+	PGSTAT_ACCUM_TAB(tuples_inserted);
+	PGSTAT_ACCUM_TAB(tuples_updated);
+	PGSTAT_ACCUM_TAB(tuples_deleted);
+	PGSTAT_ACCUM_TAB(tuples_hot_updated);
+	PGSTAT_ACCUM_TAB(blocks_fetched);
+	PGSTAT_ACCUM_TAB(blocks_hit);
+
+#undef PGSTAT_ACCUM_TAB
 
 	/*
 	 * If table was truncated/dropped, first reset the live/dead counters.
 	 */
-	if (lstats->t_counts.t_truncdropped)
+	if (stats_pending->truncdropped)
 	{
-		tabentry->n_live_tuples = 0;
-		tabentry->n_dead_tuples = 0;
-		tabentry->inserts_since_vacuum = 0;
+		stats_shmem->live_tuples          = 0;
+		stats_shmem->dead_tuples          = 0;
+		stats_shmem->inserts_since_vacuum = 0;
 	}
 
-	tabentry->n_live_tuples += lstats->t_counts.t_delta_live_tuples;
-	tabentry->n_dead_tuples += lstats->t_counts.t_delta_dead_tuples;
-	tabentry->changes_since_analyze += lstats->t_counts.t_changed_tuples;
-	tabentry->inserts_since_vacuum += lstats->t_counts.t_tuples_inserted;
-	tabentry->blocks_fetched += lstats->t_counts.t_blocks_fetched;
-	tabentry->blocks_hit += lstats->t_counts.t_blocks_hit;
+	stats_shmem->live_tuples += stats_pending->delta_live_tuples;
+	stats_shmem->dead_tuples += stats_pending->delta_dead_tuples;
+	stats_shmem->inserts_since_vacuum += stats_pending->tuples_inserted;
+	stats_shmem->changes_since_analyze += stats_pending->changed_tuples;
 
-	/* Clamp n_live_tuples in case of negative delta_live_tuples */
-	tabentry->n_live_tuples = Max(tabentry->n_live_tuples, 0);
-	/* Likewise for n_dead_tuples */
-	tabentry->n_dead_tuples = Max(tabentry->n_dead_tuples, 0);
+	/* Clamp live_tuples in case of negative delta_live_tuples */
+	stats_shmem->live_tuples = Max(stats_shmem->live_tuples, 0);
+	/* Likewise for dead_tuples */
+	stats_shmem->dead_tuples = Max(stats_shmem->dead_tuples, 0);
 
 	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->t_counts.t_tuples_returned;
-	dbentry->n_tuples_fetched += lstats->t_counts.t_tuples_fetched;
-	dbentry->n_tuples_inserted += lstats->t_counts.t_tuples_inserted;
-	dbentry->n_tuples_updated += lstats->t_counts.t_tuples_updated;
-	dbentry->n_tuples_deleted += lstats->t_counts.t_tuples_deleted;
-	dbentry->n_blocks_fetched += lstats->t_counts.t_blocks_fetched;
-	dbentry->n_blocks_hit += lstats->t_counts.t_blocks_hit;
+	dbentry = pgstat_prep_database_pending(entry_ref->shared_entry->key.dboid);
+
+#define PGSTAT_ACCUM_TAB(fld) \
+	(dbentry)->fld += (stats_pending)->fld
+
+	PGSTAT_ACCUM_TAB(tuples_returned);
+	PGSTAT_ACCUM_TAB(tuples_fetched);
+	PGSTAT_ACCUM_TAB(tuples_inserted);
+	PGSTAT_ACCUM_TAB(tuples_updated);
+	PGSTAT_ACCUM_TAB(tuples_deleted);
+	PGSTAT_ACCUM_TAB(blocks_fetched);
+	PGSTAT_ACCUM_TAB(blocks_hit);
+
+#undef PGSTAT_ACCUM_TAB
 
 	return true;
 }
 
+
 /*
  * Flush out pending stats for the entry
  *
@@ -973,59 +982,61 @@ 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 */
+	PgStat_StatDBEntry *dbentry;
+	PgStat_StatIndEntry *stats_shmem;
 
-	dboid = entry_ref->shared_entry->key.dboid;
-	lstats = (PgStat_IndexStatus *) entry_ref->pending;
-	shrelcomstats = (PgStatShared_Index *) entry_ref->shared_stats;
+	PgStat_IndexCounts *stats_pending =
+		&((PgStat_IndexStatus *) entry_ref->pending)->i_counts;
 
 	/*
 	 * 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,
+	if (memcmp(stats_pending, &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;
+	stats_shmem = &((PgStatShared_Index *) entry_ref->shared_stats)->stats;
 
-	indentry->numscans += lstats->i_counts.i_numscans;
+	/* add the values to the shared entry. */
+	stats_shmem->numscans += stats_pending->numscans;
 
-	if (lstats->i_counts.i_numscans)
+	if (stats_pending->numscans)
 	{
 		TimestampTz t = GetCurrentTransactionStopTimestamp();
 
-		if (t > indentry->lastscan)
-			indentry->lastscan = t;
+		if (t > stats_shmem->lastscan)
+			stats_shmem->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;
+
+#define PGSTAT_ACCUM_IDX(fld) \
+	(stats_shmem)->fld += (stats_pending)->fld
+	PGSTAT_ACCUM_IDX(tuples_returned);
+	PGSTAT_ACCUM_IDX(tuples_fetched);
+	PGSTAT_ACCUM_IDX(blocks_fetched);
+	PGSTAT_ACCUM_IDX(blocks_hit);
+#undef PGSTAT_ACCUM_IDX
 
 	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;
+	dbentry = pgstat_prep_database_pending(entry_ref->shared_entry->key.dboid);
+
+#define PGSTAT_ACCUM_IDX(fld) \
+	(dbentry)->fld += (stats_pending)->fld
+	PGSTAT_ACCUM_IDX(tuples_returned);
+	PGSTAT_ACCUM_IDX(tuples_fetched);
+	PGSTAT_ACCUM_IDX(blocks_fetched);
+	PGSTAT_ACCUM_IDX(blocks_hit);
+#undef PGSTAT_ACCUM_IDX
 
 	return true;
 }
 
+
 void
 pgstat_table_delete_pending_cb(PgStat_EntryRef *entry_ref)
 {
-- 
2.34.1

v4-0002-Remove-n-i-t_-prefixes-from-stats-structs.patchtext/x-patch; charset=US-ASCII; name=v4-0002-Remove-n-i-t_-prefixes-from-stats-structs.patchDownload
From 8912daf3bccbff719f8ebb03448e43281f67b28d Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Fri, 4 Nov 2022 15:48:10 -0400
Subject: [PATCH v4 2/4] Remove n/i/t_ prefixes from stats structs

---
 src/backend/access/heap/README.HOT           |  2 +-
 src/backend/postmaster/autovacuum.c          |  2 +-
 src/backend/utils/activity/pgstat_database.c | 16 ++---
 src/backend/utils/activity/pgstat_relation.c | 72 +++++++++----------
 src/backend/utils/adt/pgstatfuncs.c          | 42 +++++------
 src/include/pgstat.h                         | 76 ++++++++++----------
 6 files changed, 105 insertions(+), 105 deletions(-)

diff --git a/src/backend/access/heap/README.HOT b/src/backend/access/heap/README.HOT
index 68c6709aa8..6fd1767f70 100644
--- a/src/backend/access/heap/README.HOT
+++ b/src/backend/access/heap/README.HOT
@@ -271,7 +271,7 @@ physical tuple by eliminating an intermediate heap-only tuple or
 replacing a physical root tuple by a redirect pointer, a decrement in
 the table's number of dead tuples is reported to pgstats, which may
 postpone autovacuuming.  Note that we do not count replacing a root tuple
-by a DEAD line pointer as decrementing n_dead_tuples; we still want
+by a DEAD line pointer as decrementing dead_tuples; we still want
 autovacuum to run to clean up the index entries and DEAD item.
 
 This area probably needs further work ...
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 601834d4b4..2a295ce884 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -3081,7 +3081,7 @@ relation_needs_vacanalyze(Oid relid,
 	if (PointerIsValid(tabentry) && AutoVacuumingActive())
 	{
 		reltuples = classForm->reltuples;
-		vactuples = tabentry->n_dead_tuples;
+		vactuples = tabentry->dead_tuples;
 		instuples = tabentry->inserts_since_vacuum;
 		anltuples = tabentry->changes_since_analyze;
 
diff --git a/src/backend/utils/activity/pgstat_database.c b/src/backend/utils/activity/pgstat_database.c
index d9275611f0..ce8a9f4ac7 100644
--- a/src/backend/utils/activity/pgstat_database.c
+++ b/src/backend/utils/activity/pgstat_database.c
@@ -372,14 +372,14 @@ pgstat_database_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
 
 	PGSTAT_ACCUM_DBCOUNT(n_xact_commit);
 	PGSTAT_ACCUM_DBCOUNT(n_xact_rollback);
-	PGSTAT_ACCUM_DBCOUNT(n_blocks_fetched);
-	PGSTAT_ACCUM_DBCOUNT(n_blocks_hit);
-
-	PGSTAT_ACCUM_DBCOUNT(n_tuples_returned);
-	PGSTAT_ACCUM_DBCOUNT(n_tuples_fetched);
-	PGSTAT_ACCUM_DBCOUNT(n_tuples_inserted);
-	PGSTAT_ACCUM_DBCOUNT(n_tuples_updated);
-	PGSTAT_ACCUM_DBCOUNT(n_tuples_deleted);
+	PGSTAT_ACCUM_DBCOUNT(blocks_fetched);
+	PGSTAT_ACCUM_DBCOUNT(blocks_hit);
+
+	PGSTAT_ACCUM_DBCOUNT(tuples_returned);
+	PGSTAT_ACCUM_DBCOUNT(tuples_fetched);
+	PGSTAT_ACCUM_DBCOUNT(tuples_inserted);
+	PGSTAT_ACCUM_DBCOUNT(tuples_updated);
+	PGSTAT_ACCUM_DBCOUNT(tuples_deleted);
 
 	/* last_autovac_time is reported immediately */
 	Assert(pendingent->last_autovac_time == 0);
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c
index 13cdd7f849..94fbc6faea 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_relation.c
@@ -294,8 +294,8 @@ pgstat_report_vacuum(Oid tableoid, bool shared,
 	shtabentry = (PgStatShared_Table *) entry_ref->shared_stats;
 	tabentry = &shtabentry->stats;
 
-	tabentry->n_live_tuples = livetuples;
-	tabentry->n_dead_tuples = deadtuples;
+	tabentry->live_tuples = livetuples;
+	tabentry->dead_tuples = deadtuples;
 
 	/*
 	 * It is quite possible that a non-aggressive VACUUM ended up skipping
@@ -365,7 +365,7 @@ pgstat_report_analyze(Relation rel,
 			deadtuples -= trans->tuples_updated + trans->tuples_deleted;
 		}
 		/* count stuff inserted by already-aborted subxacts, too */
-		deadtuples -= rel->pgstattab_info->t_counts.t_delta_dead_tuples;
+		deadtuples -= rel->pgstattab_info->t_counts.delta_dead_tuples;
 		/* Since ANALYZE's counts are estimates, we could have underflowed */
 		livetuples = Max(livetuples, 0);
 		deadtuples = Max(deadtuples, 0);
@@ -381,8 +381,8 @@ pgstat_report_analyze(Relation rel,
 	shtabentry = (PgStatShared_Table *) entry_ref->shared_stats;
 	tabentry = &shtabentry->stats;
 
-	tabentry->n_live_tuples = livetuples;
-	tabentry->n_dead_tuples = deadtuples;
+	tabentry->live_tuples = livetuples;
+	tabentry->dead_tuples = deadtuples;
 
 	/*
 	 * If commanded, reset changes_since_analyze to zero.  This forgets any
@@ -434,9 +434,9 @@ pgstat_count_heap_update(Relation rel, bool hot)
 		ensure_tabstat_xact_level(pgstattab_info);
 		pgstattab_info->trans->tuples_updated++;
 
-		/* t_tuples_hot_updated is nontransactional, so just advance it */
+		/* tuples_hot_updated is nontransactional, so just advance it */
 		if (hot)
-			pgstattab_info->t_counts.t_tuples_hot_updated++;
+			pgstattab_info->t_counts.tuples_hot_updated++;
 	}
 }
 
@@ -477,7 +477,7 @@ pgstat_count_truncate(Relation rel)
  * update dead-tuples count
  *
  * The semantics of this are that we are reporting the nontransactional
- * recovery of "delta" dead tuples; so t_delta_dead_tuples decreases
+ * recovery of "delta" dead tuples; so delta_dead_tuples decreases
  * rather than increasing, and the change goes straight into the per-table
  * counter, not into transactional state.
  */
@@ -488,7 +488,7 @@ pgstat_update_heap_dead_tuples(Relation rel, int delta)
 	{
 		PgStat_TableStatus *pgstattab_info = rel->pgstattab_info;
 
-		pgstattab_info->t_counts.t_delta_dead_tuples -= delta;
+		pgstattab_info->t_counts.delta_dead_tuples -= delta;
 	}
 }
 
@@ -631,33 +631,33 @@ AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit)
 		if (!isCommit)
 			restore_truncdrop_counters(trans);
 		/* count attempted actions regardless of commit/abort */
-		tabstat->t_counts.t_tuples_inserted += trans->tuples_inserted;
-		tabstat->t_counts.t_tuples_updated += trans->tuples_updated;
-		tabstat->t_counts.t_tuples_deleted += trans->tuples_deleted;
+		tabstat->t_counts.tuples_inserted += trans->tuples_inserted;
+		tabstat->t_counts.tuples_updated += trans->tuples_updated;
+		tabstat->t_counts.tuples_deleted += trans->tuples_deleted;
 		if (isCommit)
 		{
-			tabstat->t_counts.t_truncdropped = trans->truncdropped;
+			tabstat->t_counts.truncdropped = trans->truncdropped;
 			if (trans->truncdropped)
 			{
 				/* forget live/dead stats seen by backend thus far */
-				tabstat->t_counts.t_delta_live_tuples = 0;
-				tabstat->t_counts.t_delta_dead_tuples = 0;
+				tabstat->t_counts.delta_live_tuples = 0;
+				tabstat->t_counts.delta_dead_tuples = 0;
 			}
 			/* insert adds a live tuple, delete removes one */
-			tabstat->t_counts.t_delta_live_tuples +=
+			tabstat->t_counts.delta_live_tuples +=
 				trans->tuples_inserted - trans->tuples_deleted;
 			/* update and delete each create a dead tuple */
-			tabstat->t_counts.t_delta_dead_tuples +=
+			tabstat->t_counts.delta_dead_tuples +=
 				trans->tuples_updated + trans->tuples_deleted;
 			/* insert, update, delete each count as one change event */
-			tabstat->t_counts.t_changed_tuples +=
+			tabstat->t_counts.changed_tuples +=
 				trans->tuples_inserted + trans->tuples_updated +
 				trans->tuples_deleted;
 		}
 		else
 		{
 			/* inserted tuples are dead, deleted tuples are unaffected */
-			tabstat->t_counts.t_delta_dead_tuples +=
+			tabstat->t_counts.delta_dead_tuples +=
 				trans->tuples_inserted + trans->tuples_updated;
 			/* an aborted xact generates no changed_tuple events */
 		}
@@ -737,11 +737,11 @@ AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, in
 			/* first restore values obliterated by truncate/drop */
 			restore_truncdrop_counters(trans);
 			/* count attempted actions regardless of commit/abort */
-			tabstat->t_counts.t_tuples_inserted += trans->tuples_inserted;
-			tabstat->t_counts.t_tuples_updated += trans->tuples_updated;
-			tabstat->t_counts.t_tuples_deleted += trans->tuples_deleted;
+			tabstat->t_counts.tuples_inserted += trans->tuples_inserted;
+			tabstat->t_counts.tuples_updated += trans->tuples_updated;
+			tabstat->t_counts.tuples_deleted += trans->tuples_deleted;
 			/* inserted tuples are dead, deleted tuples are unaffected */
-			tabstat->t_counts.t_delta_dead_tuples +=
+			tabstat->t_counts.delta_dead_tuples +=
 				trans->tuples_inserted + trans->tuples_updated;
 			tabstat->trans = trans->upper;
 			pfree(trans);
@@ -821,21 +821,21 @@ pgstat_twophase_postcommit(TransactionId xid, uint16 info,
 	pgstattab_info = pgstat_prep_table_pending(rec->t_id, rec->t_shared);
 
 	/* Same math as in AtEOXact_PgStat, commit case */
-	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;
+	pgstattab_info->t_counts.tuples_inserted += rec->tuples_inserted;
+	pgstattab_info->t_counts.tuples_updated += rec->tuples_updated;
+	pgstattab_info->t_counts.tuples_deleted += rec->tuples_deleted;
+	pgstattab_info->t_counts.truncdropped = rec->t_truncdropped;
 	if (rec->t_truncdropped)
 	{
 		/* forget live/dead stats seen by backend thus far */
-		pgstattab_info->t_counts.t_delta_live_tuples = 0;
-		pgstattab_info->t_counts.t_delta_dead_tuples = 0;
+		pgstattab_info->t_counts.delta_live_tuples = 0;
+		pgstattab_info->t_counts.delta_dead_tuples = 0;
 	}
-	pgstattab_info->t_counts.t_delta_live_tuples +=
+	pgstattab_info->t_counts.delta_live_tuples +=
 		rec->tuples_inserted - rec->tuples_deleted;
-	pgstattab_info->t_counts.t_delta_dead_tuples +=
+	pgstattab_info->t_counts.delta_dead_tuples +=
 		rec->tuples_updated + rec->tuples_deleted;
-	pgstattab_info->t_counts.t_changed_tuples +=
+	pgstattab_info->t_counts.changed_tuples +=
 		rec->tuples_inserted + rec->tuples_updated +
 		rec->tuples_deleted;
 }
@@ -863,10 +863,10 @@ pgstat_twophase_postabort(TransactionId xid, uint16 info,
 		rec->tuples_updated = rec->updated_pre_truncdrop;
 		rec->tuples_deleted = rec->deleted_pre_truncdrop;
 	}
-	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 +=
+	pgstattab_info->t_counts.tuples_inserted += rec->tuples_inserted;
+	pgstattab_info->t_counts.tuples_updated += rec->tuples_updated;
+	pgstattab_info->t_counts.tuples_deleted += rec->tuples_deleted;
+	pgstattab_info->t_counts.delta_dead_tuples +=
 		rec->tuples_inserted + rec->tuples_updated;
 }
 
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 0dd6148ed1..17e044b90a 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -193,7 +193,7 @@ pg_stat_get_live_tuples(PG_FUNCTION_ARGS)
 	int64		result;
 	PgStat_StatTabEntry *tabentry = pgstat_fetch_stat_tabentry(relid);
 
-	result = PGSTAT_FETCH_STAT_ENTRY(tabentry, n_live_tuples);
+	result = PGSTAT_FETCH_STAT_ENTRY(tabentry, live_tuples);
 
 	PG_RETURN_INT64(result);
 }
@@ -206,7 +206,7 @@ pg_stat_get_dead_tuples(PG_FUNCTION_ARGS)
 	int64		result;
 	PgStat_StatTabEntry *tabentry = pgstat_fetch_stat_tabentry(relid);
 
-	result = PGSTAT_FETCH_STAT_ENTRY(tabentry, n_dead_tuples);
+	result = PGSTAT_FETCH_STAT_ENTRY(tabentry, dead_tuples);
 
 	PG_RETURN_INT64(result);
 }
@@ -1249,7 +1249,7 @@ pg_stat_get_db_blocks_fetched(PG_FUNCTION_ARGS)
 	if ((dbentry = pgstat_fetch_stat_dbentry(dbid)) == NULL)
 		result = 0;
 	else
-		result = (int64) (dbentry->n_blocks_fetched);
+		result = (int64) (dbentry->blocks_fetched);
 
 	PG_RETURN_INT64(result);
 }
@@ -1265,7 +1265,7 @@ pg_stat_get_db_blocks_hit(PG_FUNCTION_ARGS)
 	if ((dbentry = pgstat_fetch_stat_dbentry(dbid)) == NULL)
 		result = 0;
 	else
-		result = (int64) (dbentry->n_blocks_hit);
+		result = (int64) (dbentry->blocks_hit);
 
 	PG_RETURN_INT64(result);
 }
@@ -1281,7 +1281,7 @@ pg_stat_get_db_tuples_returned(PG_FUNCTION_ARGS)
 	if ((dbentry = pgstat_fetch_stat_dbentry(dbid)) == NULL)
 		result = 0;
 	else
-		result = (int64) (dbentry->n_tuples_returned);
+		result = (int64) (dbentry->tuples_returned);
 
 	PG_RETURN_INT64(result);
 }
@@ -1297,7 +1297,7 @@ pg_stat_get_db_tuples_fetched(PG_FUNCTION_ARGS)
 	if ((dbentry = pgstat_fetch_stat_dbentry(dbid)) == NULL)
 		result = 0;
 	else
-		result = (int64) (dbentry->n_tuples_fetched);
+		result = (int64) (dbentry->tuples_fetched);
 
 	PG_RETURN_INT64(result);
 }
@@ -1313,7 +1313,7 @@ pg_stat_get_db_tuples_inserted(PG_FUNCTION_ARGS)
 	if ((dbentry = pgstat_fetch_stat_dbentry(dbid)) == NULL)
 		result = 0;
 	else
-		result = (int64) (dbentry->n_tuples_inserted);
+		result = (int64) (dbentry->tuples_inserted);
 
 	PG_RETURN_INT64(result);
 }
@@ -1329,7 +1329,7 @@ pg_stat_get_db_tuples_updated(PG_FUNCTION_ARGS)
 	if ((dbentry = pgstat_fetch_stat_dbentry(dbid)) == NULL)
 		result = 0;
 	else
-		result = (int64) (dbentry->n_tuples_updated);
+		result = (int64) (dbentry->tuples_updated);
 
 	PG_RETURN_INT64(result);
 }
@@ -1345,7 +1345,7 @@ pg_stat_get_db_tuples_deleted(PG_FUNCTION_ARGS)
 	if ((dbentry = pgstat_fetch_stat_dbentry(dbid)) == NULL)
 		result = 0;
 	else
-		result = (int64) (dbentry->n_tuples_deleted);
+		result = (int64) (dbentry->tuples_deleted);
 
 	PG_RETURN_INT64(result);
 }
@@ -1864,7 +1864,7 @@ pg_stat_get_table_xact_numscans(PG_FUNCTION_ARGS)
 	if ((tabentry = find_tabstat_entry(relid)) == NULL)
 		result = 0;
 	else
-		result = (int64) (tabentry->t_counts.t_numscans);
+		result = (int64) (tabentry->t_counts.numscans);
 
 	PG_RETURN_INT64(result);
 }
@@ -1879,7 +1879,7 @@ pg_stat_get_index_xact_numscans(PG_FUNCTION_ARGS)
 	if ((indentry = find_indstat_entry(relid)) == NULL)
 		result = 0;
 	else
-		result = (int64) (indentry->i_counts.i_numscans);
+		result = (int64) (indentry->i_counts.numscans);
 
 	PG_RETURN_INT64(result);
 }
@@ -1894,7 +1894,7 @@ pg_stat_get_xact_tuples_returned(PG_FUNCTION_ARGS)
 	if ((indentry = find_indstat_entry(relid)) == NULL)
 		result = 0;
 	else
-		result = (int64) (indentry->i_counts.i_tuples_returned);
+		result = (int64) (indentry->i_counts.tuples_returned);
 
 	PG_RETURN_INT64(result);
 }
@@ -1909,7 +1909,7 @@ pg_stat_get_xact_tuples_fetched(PG_FUNCTION_ARGS)
 	if ((indentry = find_indstat_entry(relid)) == NULL)
 		result = 0;
 	else
-		result = (int64) (indentry->i_counts.i_tuples_fetched);
+		result = (int64) (indentry->i_counts.tuples_fetched);
 
 	PG_RETURN_INT64(result);
 }
@@ -1926,8 +1926,8 @@ pg_stat_get_xact_tuples_inserted(PG_FUNCTION_ARGS)
 		result = 0;
 	else
 	{
-		result = tabentry->t_counts.t_tuples_inserted;
-		/* live subtransactions' counts aren't in t_tuples_inserted yet */
+		result = tabentry->t_counts.tuples_inserted;
+		/* live subtransactions' counts aren't in tuples_inserted yet */
 		for (trans = tabentry->trans; trans != NULL; trans = trans->upper)
 			result += trans->tuples_inserted;
 	}
@@ -1947,8 +1947,8 @@ pg_stat_get_xact_tuples_updated(PG_FUNCTION_ARGS)
 		result = 0;
 	else
 	{
-		result = tabentry->t_counts.t_tuples_updated;
-		/* live subtransactions' counts aren't in t_tuples_updated yet */
+		result = tabentry->t_counts.tuples_updated;
+		/* live subtransactions' counts aren't in tuples_updated yet */
 		for (trans = tabentry->trans; trans != NULL; trans = trans->upper)
 			result += trans->tuples_updated;
 	}
@@ -1968,7 +1968,7 @@ pg_stat_get_xact_tuples_deleted(PG_FUNCTION_ARGS)
 		result = 0;
 	else
 	{
-		result = tabentry->t_counts.t_tuples_deleted;
+		result = tabentry->t_counts.tuples_deleted;
 		/* live subtransactions' counts aren't in t_tuples_deleted yet */
 		for (trans = tabentry->trans; trans != NULL; trans = trans->upper)
 			result += trans->tuples_deleted;
@@ -1987,7 +1987,7 @@ pg_stat_get_xact_tuples_hot_updated(PG_FUNCTION_ARGS)
 	if ((tabentry = find_tabstat_entry(relid)) == NULL)
 		result = 0;
 	else
-		result = (int64) (tabentry->t_counts.t_tuples_hot_updated);
+		result = (int64) (tabentry->t_counts.tuples_hot_updated);
 
 	PG_RETURN_INT64(result);
 }
@@ -2002,7 +2002,7 @@ pg_stat_get_xact_blocks_fetched(PG_FUNCTION_ARGS)
 	if ((indentry = find_indstat_entry(relid)) == NULL)
 		result = 0;
 	else
-		result = (int64) (indentry->i_counts.i_blocks_fetched);
+		result = (int64) (indentry->i_counts.blocks_fetched);
 
 	PG_RETURN_INT64(result);
 }
@@ -2017,7 +2017,7 @@ pg_stat_get_xact_blocks_hit(PG_FUNCTION_ARGS)
 	if ((indentry = find_indstat_entry(relid)) == NULL)
 		result = 0;
 	else
-		result = (int64) (indentry->i_counts.i_blocks_hit);
+		result = (int64) (indentry->i_counts.blocks_hit);
 
 	PG_RETURN_INT64(result);
 }
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 8dae39b917..4ec00afc46 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -160,12 +160,12 @@ typedef struct PgStat_BackendSubEntry
  */
 typedef struct PgStat_IndexCounts
 {
-	PgStat_Counter i_numscans;
+	PgStat_Counter numscans;
 
-	PgStat_Counter i_tuples_returned;
-	PgStat_Counter i_tuples_fetched;
-	PgStat_Counter i_blocks_fetched;
-	PgStat_Counter i_blocks_hit;
+	PgStat_Counter tuples_returned;
+	PgStat_Counter tuples_fetched;
+	PgStat_Counter blocks_fetched;
+	PgStat_Counter blocks_hit;
 } PgStat_IndexCounts;
 
 /* ----------
@@ -187,23 +187,23 @@ typedef struct PgStat_IndexCounts
  */
 typedef struct PgStat_TableCounts
 {
-	PgStat_Counter t_numscans;
+	PgStat_Counter numscans;
 
-	PgStat_Counter t_tuples_returned;
-	PgStat_Counter t_tuples_fetched;
+	PgStat_Counter tuples_returned;
+	PgStat_Counter tuples_fetched;
 
-	PgStat_Counter t_tuples_inserted;
-	PgStat_Counter t_tuples_updated;
-	PgStat_Counter t_tuples_deleted;
-	PgStat_Counter t_tuples_hot_updated;
-	bool		t_truncdropped;
+	PgStat_Counter tuples_inserted;
+	PgStat_Counter tuples_updated;
+	PgStat_Counter tuples_deleted;
+	PgStat_Counter tuples_hot_updated;
+	bool		truncdropped;
 
-	PgStat_Counter t_delta_live_tuples;
-	PgStat_Counter t_delta_dead_tuples;
-	PgStat_Counter t_changed_tuples;
+	PgStat_Counter delta_live_tuples;
+	PgStat_Counter delta_dead_tuples;
+	PgStat_Counter changed_tuples;
 
-	PgStat_Counter t_blocks_fetched;
-	PgStat_Counter t_blocks_hit;
+	PgStat_Counter blocks_fetched;
+	PgStat_Counter blocks_hit;
 } PgStat_TableCounts;
 
 /* ----------
@@ -316,14 +316,14 @@ typedef struct PgStat_StatDBEntry
 {
 	PgStat_Counter n_xact_commit;
 	PgStat_Counter n_xact_rollback;
-	PgStat_Counter n_blocks_fetched;
-	PgStat_Counter n_blocks_hit;
-	PgStat_Counter n_tuples_returned;
-	PgStat_Counter n_tuples_fetched;
-	PgStat_Counter n_tuples_inserted;
-	PgStat_Counter n_tuples_updated;
-	PgStat_Counter n_tuples_deleted;
-	TimestampTz last_autovac_time;
+	PgStat_Counter blocks_fetched;
+	PgStat_Counter blocks_hit;
+	PgStat_Counter tuples_returned;
+	PgStat_Counter tuples_fetched;
+	PgStat_Counter tuples_inserted;
+	PgStat_Counter tuples_updated;
+	PgStat_Counter tuples_deleted;
+	TimestampTz    last_autovac_time;
 	PgStat_Counter n_conflict_tablespace;
 	PgStat_Counter n_conflict_lock;
 	PgStat_Counter n_conflict_snapshot;
@@ -400,8 +400,8 @@ typedef struct PgStat_StatTabEntry
 	PgStat_Counter tuples_deleted;
 	PgStat_Counter tuples_hot_updated;
 
-	PgStat_Counter n_live_tuples;
-	PgStat_Counter n_dead_tuples;
+	PgStat_Counter live_tuples;
+	PgStat_Counter dead_tuples;
 	PgStat_Counter changes_since_analyze;
 	PgStat_Counter inserts_since_vacuum;
 
@@ -580,52 +580,52 @@ extern void pgstat_report_analyze(Relation rel,
 #define pgstat_count_heap_scan(rel)									\
 	do {															\
 		if (pgstat_should_count_table(rel))						\
-			(rel)->pgstattab_info->t_counts.t_numscans++;				\
+			(rel)->pgstattab_info->t_counts.numscans++;				\
 	} while (0)
 #define pgstat_count_heap_getnext(rel)								\
 	do {															\
 		if (pgstat_should_count_table(rel))						\
-			(rel)->pgstattab_info->t_counts.t_tuples_returned++;		\
+			(rel)->pgstattab_info->t_counts.tuples_returned++;		\
 	} while (0)
 #define pgstat_count_heap_fetch(rel)								\
 	do {															\
 		if (pgstat_should_count_table(rel))						\
-			(rel)->pgstattab_info->t_counts.t_tuples_fetched++;		\
+			(rel)->pgstattab_info->t_counts.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++;		\
+			(rel)->pgstatind_info->i_counts.tuples_fetched++;		\
 	} while (0)
 #define pgstat_count_index_scan(rel)								\
 	do {															\
 		if (pgstat_should_count_index(rel))						\
-			(rel)->pgstatind_info->i_counts.i_numscans++;			\
+			(rel)->pgstatind_info->i_counts.numscans++;			\
 	} while (0)
 #define pgstat_count_index_tuples(rel, n)							\
 	do {															\
 		if (pgstat_should_count_index(rel))						\
-			(rel)->pgstatind_info->i_counts.i_tuples_returned += (n);	\
+			(rel)->pgstatind_info->i_counts.tuples_returned += (n);	\
 	} while (0)
 #define pgstat_count_table_buffer_read(rel)								\
 	do {															\
 		if (pgstat_should_count_table(rel))	\
-			(rel)->pgstattab_info->t_counts.t_blocks_fetched++;		\
+			(rel)->pgstattab_info->t_counts.blocks_fetched++;		\
 	} while (0)
 #define pgstat_count_index_buffer_read(rel)								\
 	do {															\
 		if (pgstat_should_count_index(rel))	\
-			(rel)->pgstatind_info->i_counts.i_blocks_fetched++;		\
+			(rel)->pgstatind_info->i_counts.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++; \
+			(rel)->pgstattab_info->t_counts.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++; \
+			(rel)->pgstatind_info->i_counts.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);
-- 
2.34.1

#6Drouvot, Bertrand
bertranddrouvot.pg@gmail.com
In reply to: Melanie Plageman (#5)
Re: Split index and table statistics into different types of stats

Hi,

On 11/4/22 9:51 PM, Melanie Plageman wrote:

Hi Bertrand,

I'm glad you are working on this.

I had a few thoughts/ideas

Thanks for looking at it!

It seems better to have all of the counts in the various stats structs
not be prefixed with n_, i_, t_

typedef struct PgStat_StatDBEntry
{
...
PgStat_Counter n_blocks_fetched;
PgStat_Counter n_blocks_hit;
PgStat_Counter n_tuples_returned;
PgStat_Counter n_tuples_fetched;
...

I've attached a patch (0002) to change this in case you are interested
in making such a change

I did not renamed initially the fields/columns to ease the review.

Indeed, I think we should go further than removing the n_, i_ and t_
prefixes so that the fields actually match the view's columns.

For example, currently pg_stat_all_indexes.idx_tup_read is linked to
"tuples_returned", so that it would make sense to rename
"tuples_returned" to "tuples_read" or even "tup_read" in the indexes
counters.

That's why I had in mind to do this fields/columns renaming into a
separate patch (once this one is committed), so that the current one
focus only on splitting the stats: what do you think?

(I've attached all of my suggestions in patches
along with your original patch so that cfbot still passes).

On Wed, Nov 2, 2022 at 5:00 AM Drouvot, Bertrand
<bertranddrouvot.pg@gmail.com> wrote:

On 11/1/22 1:30 AM, Andres Freund wrote:

On 2022-10-31 14:14:15 +0100, Drouvot, Bertrand wrote:

@@ -240,9 +293,23 @@ pg_stat_get_blocks_fetched(PG_FUNCTION_ARGS)
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;
+
+    if ((indentry = pgstat_fetch_stat_indentry(relid)) == NULL)
+            result = 0;
+    else
+            result = (int64) (indentry->blocks_fetched);
+
+    PG_RETURN_INT64(result);
+}

We have so many copies of this by now - perhaps we first should deduplicate
them somehow? Even if it's just a macro or such.

Yeah good point, a new macro has been defined for the "int64" return
case in V3 attached.

I looked for other opportunities to de-duplicate, but most of the functions
that were added that are identical except the return type and
PgStat_Kind are short enough that it doesn't make sense to make wrappers
or macros.

Yeah, agree.

I do think it makes sense to reorder the members of the two structs
PgStat_IndexCounts and PgStat_TableCounts so that they have a common
header. I've done that in the attached patch (0003).

That's a good idea, thanks! But for that we would need to have the same
fields names, means:

- Remove the prefixes (as you've done in 0002)
- And probably reduce the number of fields in the new
PgStat_RelationCounts that 003 is introducing (for example
tuples_returned should be excluded if we're going to rename it later on
to "tuples_read" for the indexes to map the
pg_stat_all_indexes.idx_tup_read column).

ISTM that we should do it in the "renaming" effort, after this patch is
committed.

In the flush functions, I was also thinking it might be nice to use the
same pattern as is used in [1] and [2] to add the counts together. It
makes the lines a bit easier to read, IMO. If we remove the prefixes
from the count fields, this works for many of the fields. I've attached
a patch (0004) that does something like this, in case you wanted to go
in this direction.

I like it too but same remarks as previously. I think it should be part
of the "renaming" effort.

Since you have made new parallel functions for indexes and tables for
many of the functions in pgstat_relation.c, perhaps it makes sense to
split it into pgstat_table.c and pgstat_index.c?

Good point, thanks, I'll work on it.

One question I had about the original code (not related to your
refactor) is why the pending stats aren't memset in the flush functions
after aggregating them into the shared stats.

Not sure I'm getting your point, do you think something is not right
with the flush functions?

Regards,

--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

#7Drouvot, Bertrand
bertranddrouvot.pg@gmail.com
In reply to: Drouvot, Bertrand (#6)
1 attachment(s)
Re: Split index and table statistics into different types of stats

Hi,

On 11/14/22 11:36 AM, Drouvot, Bertrand wrote:

On 11/4/22 9:51 PM, Melanie Plageman wrote:

Since you have made new parallel functions for indexes and tables for
many of the functions in pgstat_relation.c, perhaps it makes sense to
split it into pgstat_table.c and pgstat_index.c?

Good point, thanks, I'll work on it.

Please find attached v4 adding pgstat_table.c and pgstat_index.c (and
removing pgstat_relation.c).

Regards,

--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

Attachments:

v4-0001-split_tables_indexes_stats.patchtext/plain; charset=UTF-8; name=v4-0001-split_tables_indexes_stats.patchDownload
diff --git a/src/backend/access/common/relation.c b/src/backend/access/common/relation.c
index 382a42ff7d..259bd06aed 100644
--- a/src/backend/access/common/relation.c
+++ b/src/backend/access/common/relation.c
@@ -73,7 +73,10 @@ relation_open(Oid relationId, LOCKMODE lockmode)
 	if (RelationUsesLocalBuffers(r))
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
-	pgstat_init_relation(r);
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_init_index(r);
+	else
+		pgstat_init_table(r);
 
 	return r;
 }
@@ -123,7 +126,10 @@ try_relation_open(Oid relationId, LOCKMODE lockmode)
 	if (RelationUsesLocalBuffers(r))
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
-	pgstat_init_relation(r);
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_init_index(r);
+	else
+		pgstat_init_table(r);
 
 	return r;
 }
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/Makefile b/src/backend/utils/activity/Makefile
index a2e8507fd6..7441f4ce85 100644
--- a/src/backend/utils/activity/Makefile
+++ b/src/backend/utils/activity/Makefile
@@ -22,11 +22,12 @@ OBJS = \
 	pgstat_checkpointer.o \
 	pgstat_database.o \
 	pgstat_function.o \
-	pgstat_relation.o \
+	pgstat_index.o \
 	pgstat_replslot.o \
 	pgstat_shmem.o \
 	pgstat_slru.o \
 	pgstat_subscription.o \
+	pgstat_table.o \
 	pgstat_wal.o \
 	pgstat_xact.o \
 	wait_event.o
diff --git a/src/backend/utils/activity/meson.build b/src/backend/utils/activity/meson.build
index 5b3b558a67..e6bf3c59b6 100644
--- a/src/backend/utils/activity/meson.build
+++ b/src/backend/utils/activity/meson.build
@@ -7,11 +7,12 @@ backend_sources += files(
   'pgstat_checkpointer.c',
   'pgstat_database.c',
   'pgstat_function.c',
-  'pgstat_relation.c',
+  'pgstat_index.c',
   'pgstat_replslot.c',
   'pgstat_shmem.c',
   'pgstat_slru.c',
   'pgstat_subscription.c',
+  'pgstat_table.c',
   'pgstat_wal.c',
   'pgstat_xact.c',
   'wait_event.c',
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index 1ebe3bbf29..d49a5ea4bb 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -72,10 +72,11 @@
  * - pgstat_checkpointer.c
  * - pgstat_database.c
  * - pgstat_function.c
- * - pgstat_relation.c
+ * - pgstat_index.c
  * - pgstat_replslot.c
  * - pgstat_slru.c
  * - pgstat_subscription.c
+ * - pgstat_table.c
  * - pgstat_wal.c
  *
  * Whenever possible infrastructure files should not contain code related to
@@ -269,18 +270,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_index.c b/src/backend/utils/activity/pgstat_index.c
new file mode 100644
index 0000000000..e07d8dbf21
--- /dev/null
+++ b/src/backend/utils/activity/pgstat_index.c
@@ -0,0 +1,306 @@
+/* -------------------------------------------------------------------------
+ *
+ * pgstat_index.c
+ *	  Implementation of index statistics.
+ *
+ * This file contains the implementation of function index. It is kept
+ * separate from pgstat.c to enforce the line between the statistics access /
+ * storage implementation and the details about individual types of
+ * statistics.
+ *
+ * Copyright (c) 2001-2022, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/activity/pgstat_index.c
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/xact.h"
+#include "utils/pgstat_internal.h"
+#include "utils/rel.h"
+
+static PgStat_IndexStatus *pgstat_prep_index_pending(Oid rel_id, bool isshared);
+
+/*
+ * Copy stats between indexes. This is used for things like REINDEX
+ * CONCURRENTLY.
+ */
+void
+pgstat_copy_index_stats(Relation dst, Relation src)
+{
+	PgStat_StatIndEntry *srcstats;
+	PgStatShared_Index *dstshstats;
+	PgStat_EntryRef *dst_ref;
+
+	srcstats = pgstat_fetch_stat_indentry_ext(src->rd_rel->relisshared,
+											  RelationGetRelid(src));
+	if (!srcstats)
+		return;
+
+	dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_INDEX,
+										  dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+										  RelationGetRelid(dst),
+										  false);
+
+	dstshstats = (PgStatShared_Index *) dst_ref->shared_stats;
+	dstshstats->stats = *srcstats;
+
+	pgstat_unlock_entry(dst_ref);
+}
+
+/*
+ * Initialize a relcache entry to count access statistics.  Called whenever an
+ * index is opened.
+ *
+ * We assume that a relcache entry's pgstatind_info field is 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
+ * pgstat_assoc_index().
+ */
+void
+pgstat_init_index(Relation rel)
+{
+	/*
+	 * We only count stats for indexes
+	 */
+	Assert(rel->rd_rel->relkind == RELKIND_INDEX);
+
+	if (!pgstat_track_counts)
+	{
+		if (rel->pgstatind_info != NULL)
+			pgstat_unlink_index(rel);
+
+		/* We're not counting at all */
+		rel->pgstat_enabled = false;
+		rel->pgstatind_info = NULL;
+		return;
+	}
+
+	rel->pgstat_enabled = true;
+}
+
+/*
+ * 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_index() 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->pgstatind_info->relation == NULL);
+
+	/* mark this relation as the owner */
+	rel->pgstatind_info->relation = rel;
+}
+
+/*
+ * Break the mutual link between a relcache entry and pending index stats entry.
+ * This must be called whenever one end of the link is removed.
+ */
+void
+pgstat_unlink_index(Relation rel)
+{
+
+	if (rel->pgstatind_info == NULL)
+		return;
+
+	/* 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 index stats are dropped if transaction aborts.
+ */
+void
+pgstat_create_index(Relation rel)
+{
+	pgstat_create_transactional(PGSTAT_KIND_INDEX,
+								rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+								RelationGetRelid(rel));
+}
+
+/*
+ * Ensure that index stats are dropped if transaction commits.
+ */
+void
+pgstat_drop_index(Relation rel)
+{
+	pgstat_drop_transactional(PGSTAT_KIND_INDEX,
+							  rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+							  RelationGetRelid(rel));
+}
+
+/*
+ * 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);
+}
+
+/*
+ * 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;
+	return NULL;
+}
+
+/*
+ * 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_index_delete_pending_cb(PgStat_EntryRef *entry_ref)
+{
+	PgStat_IndexStatus *pending = (PgStat_IndexStatus *) entry_ref->pending;
+
+	if (pending->relation)
+		pgstat_unlink_index(pending->relation);
+}
+
+/*
+ * 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;
+}
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_table.c
similarity index 76%
rename from src/backend/utils/activity/pgstat_relation.c
rename to src/backend/utils/activity/pgstat_table.c
index 55a355f583..26d47abee1 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_table.c
@@ -1,9 +1,9 @@
 /* -------------------------------------------------------------------------
  *
- * pgstat_relation.c
- *	  Implementation of relation statistics.
+ * pgstat_table.c
+ *	  Implementation of table statistics.
  *
- * This file contains the implementation of function relation. It is kept
+ * This file contains the implementation of function table. It is kept
  * separate from pgstat.c to enforce the line between the statistics access /
  * storage implementation and the details about individual types of
  * statistics.
@@ -11,7 +11,7 @@
  * Copyright (c) 2001-2022, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *	  src/backend/utils/activity/pgstat_relation.c
+ *	  src/backend/utils/activity/pgstat_table.c
  * -------------------------------------------------------------------------
  */
 
@@ -43,74 +43,47 @@ 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 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
- * CONCURRENTLY.
- */
-void
-pgstat_copy_relation_stats(Relation dst, Relation src)
-{
-	PgStat_StatTabEntry *srcstats;
-	PgStatShared_Relation *dstshstats;
-	PgStat_EntryRef *dst_ref;
-
-	srcstats = pgstat_fetch_stat_tabentry_ext(src->rd_rel->relisshared,
-											  RelationGetRelid(src));
-	if (!srcstats)
-		return;
-
-	dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
-										  dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
-										  RelationGetRelid(dst),
-										  false);
-
-	dstshstats = (PgStatShared_Relation *) dst_ref->shared_stats;
-	dstshstats->stats = *srcstats;
-
-	pgstat_unlock_entry(dst_ref);
-}
-
 /*
  * Initialize a relcache entry to count access statistics.  Called whenever a
- * relation is opened.
+ * table is opened.
  *
- * We assume that a relcache entry's pgstat_info field is zeroed by relcache.c
+ * We assume that a relcache entry's pgstattab_info field is 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
- * pgstat_assoc_relation().
+ * pgstat_assoc_table().
  */
 void
-pgstat_init_relation(Relation rel)
+pgstat_init_table(Relation rel)
 {
 	char		relkind = rel->rd_rel->relkind;
 
+	Assert(relkind != RELKIND_INDEX);
 	/*
 	 * We only count stats for relations with storage and partitioned tables
 	 */
 	if (!RELKIND_HAS_STORAGE(relkind) && relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		rel->pgstat_enabled = false;
-		rel->pgstat_info = NULL;
+		rel->pgstattab_info = NULL;
 		return;
 	}
 
 	if (!pgstat_track_counts)
 	{
-		if (rel->pgstat_info)
-			pgstat_unlink_relation(rel);
+		if (rel->pgstattab_info != NULL)
+			pgstat_unlink_table(rel);
 
 		/* We're not counting at all */
 		rel->pgstat_enabled = false;
-		rel->pgstat_info = NULL;
+		rel->pgstattab_info = NULL;
 		return;
 	}
 
@@ -118,89 +91,92 @@ 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.
  *
- * This is separate from pgstat_init_relation() as it is not uncommon for
+ * This is separate from pgstat_init_table() as it is not uncommon for
  * 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->pgstat_info->relation == NULL);
+	Assert(rel->pgstattab_info->relation == NULL);
 
 	/* mark this relation as the owner */
-	rel->pgstat_info->relation = rel;
+	rel->pgstattab_info->relation = rel;
 }
 
 /*
- * Break the mutual link between a relcache entry and pending stats entry.
+ * Break the mutual link between a relcache entry and pending table stats entry.
  * This must be called whenever one end of the link is removed.
  */
 void
-pgstat_unlink_relation(Relation rel)
+pgstat_unlink_table(Relation rel)
 {
-	/* remove the link to stats info if any */
-	if (rel->pgstat_info == NULL)
+
+	if (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;
+	}
 }
 
 /*
- * Ensure that stats are dropped if transaction aborts.
+ * Ensure that table stats are dropped if transaction aborts.
  */
 void
-pgstat_create_relation(Relation rel)
+pgstat_create_table(Relation rel)
 {
-	pgstat_create_transactional(PGSTAT_KIND_RELATION,
+	pgstat_create_transactional(PGSTAT_KIND_TABLE,
 								rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
 								RelationGetRelid(rel));
 }
 
 /*
- * Ensure that stats are dropped if transaction commits.
+ * Ensure that table stats are dropped if transaction commits.
  */
 void
-pgstat_drop_relation(Relation rel)
+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 +188,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 +200,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 +247,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 +266,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 +324,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 +339,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 +358,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 +373,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 +396,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 +436,7 @@ 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);
 }
 
 /*
@@ -476,9 +452,9 @@ 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;
@@ -486,7 +462,7 @@ find_tabstat_entry(Oid rel_id)
 }
 
 /*
- * Perform relation stats specific end-of-transaction work. Helper for
+ * Perform table stats specific end-of-transaction work. Helper for
  * AtEOXact_PgStat.
  *
  * Transfer transactional insert/update counts into the base tabstat entries.
@@ -494,7 +470,7 @@ find_tabstat_entry(Oid rel_id)
  * TopTransactionContext and will go away anyway.
  */
 void
-AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit)
+AtEOXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit)
 {
 	PgStat_TableXactStatus *trans;
 
@@ -545,14 +521,14 @@ AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit)
 }
 
 /*
- * Perform relation stats specific end-of-sub-transaction work. Helper for
+ * Perform table stats specific end-of-sub-transaction work. Helper for
  * AtEOSubXact_PgStat.
  *
  * Transfer transactional insert/update counts into the next higher
  * subtransaction state.
  */
 void
-AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
+AtEOSubXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
 {
 	PgStat_TableXactStatus *trans;
 	PgStat_TableXactStatus *next_trans;
@@ -629,11 +605,11 @@ AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, in
 }
 
 /*
- * Generate 2PC records for all the pending transaction-dependent relation
+ * Generate 2PC records for all the pending transaction-dependent table
  * stats.
  */
 void
-AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
+AtPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state)
 {
 	PgStat_TableXactStatus *trans;
 
@@ -668,10 +644,10 @@ AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
  * reported to the stats system immediately, while the effects on live and
  * dead tuple counts are preserved in the 2PC state file.
  *
- * Note: AtEOXact_PgStat_Relations is not called during PREPARE.
+ * Note: AtEOXact_PgStat_Tables is not called during PREPARE.
  */
 void
-PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
+PostPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state)
 {
 	PgStat_TableXactStatus *trans;
 
@@ -694,27 +670,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 +706,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 +718,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 +735,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 +767,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;
 	}
@@ -840,12 +816,12 @@ pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
 }
 
 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;
 
 	if (pending->relation)
-		pgstat_unlink_relation(pending->relation);
+		pgstat_unlink_table(pending->relation);
 }
 
 /*
@@ -853,12 +829,12 @@ pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref)
  * 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;
@@ -872,7 +848,7 @@ pgstat_prep_relation_pending(Oid rel_id, bool isshared)
  * 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 +864,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/activity/pgstat_xact.c b/src/backend/utils/activity/pgstat_xact.c
index 5a3aca4aef..870f1b1103 100644
--- a/src/backend/utils/activity/pgstat_xact.c
+++ b/src/backend/utils/activity/pgstat_xact.c
@@ -51,7 +51,7 @@ AtEOXact_PgStat(bool isCommit, bool parallel)
 		Assert(xact_state->nest_level == 1);
 		Assert(xact_state->prev == NULL);
 
-		AtEOXact_PgStat_Relations(xact_state, isCommit);
+		AtEOXact_PgStat_Tables(xact_state, isCommit);
 		AtEOXact_PgStat_DroppedStats(xact_state, isCommit);
 	}
 	pgStatXactStack = NULL;
@@ -122,7 +122,7 @@ AtEOSubXact_PgStat(bool isCommit, int nestDepth)
 		/* delink xact_state from stack immediately to simplify reuse case */
 		pgStatXactStack = xact_state->prev;
 
-		AtEOSubXact_PgStat_Relations(xact_state, isCommit, nestDepth);
+		AtEOSubXact_PgStat_Tables(xact_state, isCommit, nestDepth);
 		AtEOSubXact_PgStat_DroppedStats(xact_state, isCommit, nestDepth);
 
 		pfree(xact_state);
@@ -197,7 +197,7 @@ AtPrepare_PgStat(void)
 		Assert(xact_state->nest_level == 1);
 		Assert(xact_state->prev == NULL);
 
-		AtPrepare_PgStat_Relations(xact_state);
+		AtPrepare_PgStat_Tables(xact_state);
 	}
 }
 
@@ -221,7 +221,7 @@ PostPrepare_PgStat(void)
 		Assert(xact_state->nest_level == 1);
 		Assert(xact_state->prev == NULL);
 
-		PostPrepare_PgStat_Relations(xact_state);
+		PostPrepare_PgStat_Tables(xact_state);
 	}
 	pgStatXactStack = NULL;
 
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index ae3365d917..be7f175bf1 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);
 	TimestampTz result;
@@ -70,50 +80,80 @@ pg_stat_get_lastscan(PG_FUNCTION_ARGS)
 		PG_RETURN_TIMESTAMPTZ(result);
 }
 
-
 Datum
-pg_stat_get_tuples_returned(PG_FUNCTION_ARGS)
+pg_stat_get_index_lastscan(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
-	int64		result;
-	PgStat_StatTabEntry *tabentry;
+	TimestampTz result;
+	PgStat_StatIndEntry *indentry;
 
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
+	if ((indentry = pgstat_fetch_stat_indentry(relid)) == NULL)
 		result = 0;
 	else
-		result = (int64) (tabentry->tuples_returned);
+		result = indentry->lastscan;
+
+	if (result == 0)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_TIMESTAMPTZ(result);
+}
+
+Datum
+pg_stat_get_table_tuples_returned(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, 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);
+}
 
 Datum
 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);
 }
@@ -124,12 +164,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);
 }
@@ -140,12 +177,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);
 }
@@ -156,12 +190,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);
 }
@@ -172,12 +203,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);
 }
@@ -188,12 +216,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);
 }
@@ -204,12 +229,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);
 }
@@ -220,44 +242,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);
 }
@@ -339,12 +375,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);
 }
@@ -354,12 +387,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);
 }
@@ -369,12 +399,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);
 }
@@ -384,12 +411,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);
 }
@@ -1843,7 +1867,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;
@@ -1857,17 +1881,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);
 }
@@ -1877,12 +1916,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);
 }
@@ -1970,12 +2009,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);
 }
@@ -1985,12 +2024,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);
 }
@@ -2109,7 +2148,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..da9e60225f 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -2411,7 +2411,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 	RelationCloseSmgr(relation);
 
 	/* break mutual link with stats entry */
-	pgstat_unlink_relation(relation);
+	if (relation->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_unlink_index(relation);
+	else
+		pgstat_unlink_table(relation);
 
 	/*
 	 * Free all the subsidiary data structures of the relcache entry, then the
@@ -2720,8 +2723,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 +6318,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 9dbe9ec801..8adbade072 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..149394c7ef 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;
@@ -494,16 +541,15 @@ extern PgStat_BackendFunctionEntry *find_funcstat_entry(Oid func_id);
 
 
 /*
- * Functions in pgstat_relation.c
+ * Functions in pgstat_table.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_drop_table(Relation rel);
+extern void pgstat_init_table(Relation rel);
 
-extern void pgstat_init_relation(Relation rel);
-extern void pgstat_assoc_relation(Relation rel);
-extern void pgstat_unlink_relation(Relation rel);
+extern void pgstat_assoc_table(Relation rel);
+extern void pgstat_unlink_table(Relation rel);
 
 extern void pgstat_report_vacuum(Oid tableoid, bool shared,
 								 PgStat_Counter livetuples, PgStat_Counter deadtuples);
@@ -511,53 +557,83 @@ extern void pgstat_report_analyze(Relation rel,
 								  PgStat_Counter livetuples, PgStat_Counter deadtuples,
 								  bool resetcounter);
 
+/*
+ * Functions in pgstat_index.c
+ */
+
+extern void pgstat_create_index(Relation rel);
+extern void pgstat_drop_index(Relation rel);
+extern void pgstat_copy_index_stats(Relation dst, Relation src);
+extern void pgstat_init_index(Relation rel);
+extern void pgstat_assoc_index(Relation rel);
+extern void pgstat_unlink_index(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_table()
+ * and pgstat_init_index().
  */
-#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 +649,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..004e5412ab 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
 {
@@ -550,16 +556,23 @@ extern bool pgstat_function_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
 
 
 /*
- * Functions in pgstat_relation.c
+ * Functions in pgstat_table.c
  */
 
-extern void AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit);
-extern void AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth);
-extern void AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state);
-extern void PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state);
+extern void AtEOXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit);
+extern void AtEOSubXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth);
+extern void AtPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state);
+extern void PostPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state);
+
+extern bool pgstat_table_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+extern void pgstat_table_delete_pending_cb(PgStat_EntryRef *entry_ref);
+
+/*
+ * Functions in pgstat_index.c
+ */
 
-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_index_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+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 1d84407a03..e3f1ae7821 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;$$;
@@ -1021,21 +1132,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
@@ -1051,14 +1162,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
@@ -1073,7 +1184,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
@@ -1081,7 +1192,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
@@ -1089,7 +1200,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
@@ -1097,7 +1208,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
@@ -1105,7 +1216,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 b4d6753c71..12ec87a6d1 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
 ---
@@ -493,40 +564,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 f8302f1ed1..898f09206a 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
#8Andres Freund
andres@anarazel.de
In reply to: Drouvot, Bertrand (#7)
Re: Split index and table statistics into different types of stats

Hi,

On 2022-11-15 10:48:40 +0100, Drouvot, Bertrand wrote:

diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index ae3365d917..be7f175bf1 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);
}

This still leaves a fair bit of boilerplate. ISTM that the function body
really should just be a single line.

Might even be worth defining the whole function via a macro. Perhaps something like

PGSTAT_DEFINE_REL_FIELD_ACCESSOR(PGSTAT_KIND_INDEX, pg_stat_get_index, numscans);

I think the logic to infer which DB oid to use for a stats entry could be
shared between different kinds of stats. We don't need to duplicate it.

Is there any reason to not replace the "double lookup" in
pgstat_fetch_stat_tabentry() with IsSharedRelation()?

This should probably be done in a preparatory commit.

Greetings,

Andres Freund

#9Drouvot, Bertrand
bertranddrouvot.pg@gmail.com
In reply to: Andres Freund (#8)
Re: Split index and table statistics into different types of stats

Hi,

On 11/16/22 9:12 PM, Andres Freund wrote:

Hi,

On 2022-11-15 10:48:40 +0100, Drouvot, Bertrand wrote:

diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index ae3365d917..be7f175bf1 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);
}

This still leaves a fair bit of boilerplate. ISTM that the function body
really should just be a single line.

Might even be worth defining the whole function via a macro. Perhaps something like

PGSTAT_DEFINE_REL_FIELD_ACCESSOR(PGSTAT_KIND_INDEX, pg_stat_get_index, numscans);

Thanks for the feedback!

Right, what about something like the following?

"
#define PGSTAT_FETCH_STAT_ENTRY(pgstat_entry_kind, pgstat_fetch_stat_function, relid, stat_name) \
do { \
pgstat_entry_kind *entry = pgstat_fetch_stat_function(relid); \
PG_RETURN_INT64(entry == NULL ? 0 : (int64) (entry->stat_name)); \
} while (0)

Datum
pg_stat_get_index_numscans(PG_FUNCTION_ARGS)
{
PGSTAT_FETCH_STAT_ENTRY(PgStat_StatIndEntry, pgstat_fetch_stat_indentry, PG_GETARG_OID(0), numscans);
}
"

I think the logic to infer which DB oid to use for a stats entry could be
shared between different kinds of stats. We don't need to duplicate it.

Agree, will provide a new patch version once [1]/messages/by-id/2e4a0ae1-2696-9f0c-301c-2330e447133f@gmail.com is committed.

Is there any reason to not replace the "double lookup" in
pgstat_fetch_stat_tabentry() with IsSharedRelation()?

Thanks for the suggestion!

This should probably be done in a preparatory commit.

Proposal submitted in [1]/messages/by-id/2e4a0ae1-2696-9f0c-301c-2330e447133f@gmail.com.

[1]: /messages/by-id/2e4a0ae1-2696-9f0c-301c-2330e447133f@gmail.com

Regards,

--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

#10Andres Freund
andres@anarazel.de
In reply to: Drouvot, Bertrand (#9)
Re: Split index and table statistics into different types of stats

Hi,

On 2022-11-18 12:18:38 +0100, Drouvot, Bertrand wrote:

On 11/16/22 9:12 PM, Andres Freund wrote:

This still leaves a fair bit of boilerplate. ISTM that the function body
really should just be a single line.

Might even be worth defining the whole function via a macro. Perhaps something like

PGSTAT_DEFINE_REL_FIELD_ACCESSOR(PGSTAT_KIND_INDEX, pg_stat_get_index, numscans);

Thanks for the feedback!

Right, what about something like the following?

"
#define PGSTAT_FETCH_STAT_ENTRY(pgstat_entry_kind, pgstat_fetch_stat_function, relid, stat_name) \
do { \
pgstat_entry_kind *entry = pgstat_fetch_stat_function(relid); \
PG_RETURN_INT64(entry == NULL ? 0 : (int64) (entry->stat_name)); \
} while (0)

Datum
pg_stat_get_index_numscans(PG_FUNCTION_ARGS)
{
PGSTAT_FETCH_STAT_ENTRY(PgStat_StatIndEntry, pgstat_fetch_stat_indentry, PG_GETARG_OID(0), numscans);
}
"

That's better, but still seems like quite a bit of repetition, given the
number of accessors. I think I like my idea of a macro defining the whole
function a bit better.

I'd define a "base" macro and then a version that's specific to tables and
indexes each, so that the pieces related to that don't have to be repeated as
often.

This should probably be done in a preparatory commit.

Proposal submitted in [1].

Now merged.

Greetings,

Andres Freund

#11Drouvot, Bertrand
bertranddrouvot.pg@gmail.com
In reply to: Andres Freund (#10)
Re: Split index and table statistics into different types of stats

Hi,

On 11/21/22 12:19 AM, Andres Freund wrote:

Hi,

On 2022-11-18 12:18:38 +0100, Drouvot, Bertrand wrote:

On 11/16/22 9:12 PM, Andres Freund wrote:

This still leaves a fair bit of boilerplate. ISTM that the function body
really should just be a single line.

Might even be worth defining the whole function via a macro. Perhaps something like

PGSTAT_DEFINE_REL_FIELD_ACCESSOR(PGSTAT_KIND_INDEX, pg_stat_get_index, numscans);

Thanks for the feedback!

Right, what about something like the following?

"
#define PGSTAT_FETCH_STAT_ENTRY(pgstat_entry_kind, pgstat_fetch_stat_function, relid, stat_name) \
do { \
pgstat_entry_kind *entry = pgstat_fetch_stat_function(relid); \
PG_RETURN_INT64(entry == NULL ? 0 : (int64) (entry->stat_name)); \
} while (0)

Datum
pg_stat_get_index_numscans(PG_FUNCTION_ARGS)
{
PGSTAT_FETCH_STAT_ENTRY(PgStat_StatIndEntry, pgstat_fetch_stat_indentry, PG_GETARG_OID(0), numscans);
}
"

That's better, but still seems like quite a bit of repetition, given the
number of accessors. I think I like my idea of a macro defining the whole
function a bit better.

Got it, what about creating another preparatory commit to first introduce something like:

"
#define PGSTAT_DEFINE_REL_FIELD_ACCESSOR(function_name_prefix, stat_name) \
Datum \
function_name_prefix##_##stat_name(PG_FUNCTION_ARGS) \
{ \
Oid relid = PG_GETARG_OID(0); \
int64 result; \
PgStat_StatTabEntry *tabentry; \
if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL) \
result = 0; \
else \
result = (int64) (tabentry->stat_name); \
PG_RETURN_INT64(result); \
} \

PGSTAT_DEFINE_REL_FIELD_ACCESSOR(pg_stat_get, numscans);

PGSTAT_DEFINE_REL_FIELD_ACCESSOR(pg_stat_get, tuples_returned);
.
.
.
"

If that makes sense to you, I'll submit this preparatory patch.

Now merged.

Thanks!

Regards,

--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

#12Bharath Rupireddy
bharath.rupireddyforpostgres@gmail.com
In reply to: Drouvot, Bertrand (#11)
Re: Split index and table statistics into different types of stats

On Mon, Nov 21, 2022 at 7:03 PM Drouvot, Bertrand
<bertranddrouvot.pg@gmail.com> wrote:

On 11/21/22 12:19 AM, Andres Freund wrote:

That's better, but still seems like quite a bit of repetition, given the
number of accessors. I think I like my idea of a macro defining the whole
function a bit better.

Got it, what about creating another preparatory commit to first introduce something like:

"
#define PGSTAT_DEFINE_REL_FIELD_ACCESSOR(function_name_prefix, stat_name) \
Datum \
function_name_prefix##_##stat_name(PG_FUNCTION_ARGS) \
{ \
Oid relid = PG_GETARG_OID(0); \
int64 result; \
PgStat_StatTabEntry *tabentry; \
if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL) \
result = 0; \
else \
result = (int64) (tabentry->stat_name); \
PG_RETURN_INT64(result); \
} \

PGSTAT_DEFINE_REL_FIELD_ACCESSOR(pg_stat_get, numscans);

PGSTAT_DEFINE_REL_FIELD_ACCESSOR(pg_stat_get, tuples_returned);
.
.
.
"

If that makes sense to you, I'll submit this preparatory patch.

I think the macros stitching the function declarations and definitions
is a great idea to avoid code duplicacy. We seem to be using that
approach already - PG_FUNCTION_INFO_V1, SH_DECLARE, SH_DEFINE and its
friends, STEMMER_MODULE and so on. +1 for first applying this
principle for existing functions. Looking forward to the patch.

--
Bharath Rupireddy
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

#13Drouvot, Bertrand
bertranddrouvot.pg@gmail.com
In reply to: Bharath Rupireddy (#12)
Re: Split index and table statistics into different types of stats

Hi,

On 11/22/22 7:19 AM, Bharath Rupireddy wrote:

On Mon, Nov 21, 2022 at 7:03 PM Drouvot, Bertrand
<bertranddrouvot.pg@gmail.com> wrote:

On 11/21/22 12:19 AM, Andres Freund wrote:

That's better, but still seems like quite a bit of repetition, given the
number of accessors. I think I like my idea of a macro defining the whole
function a bit better.

Got it, what about creating another preparatory commit to first introduce something like:

"
#define PGSTAT_DEFINE_REL_FIELD_ACCESSOR(function_name_prefix, stat_name) \
Datum \
function_name_prefix##_##stat_name(PG_FUNCTION_ARGS) \
{ \
Oid relid = PG_GETARG_OID(0); \
int64 result; \
PgStat_StatTabEntry *tabentry; \
if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL) \
result = 0; \
else \
result = (int64) (tabentry->stat_name); \
PG_RETURN_INT64(result); \
} \

PGSTAT_DEFINE_REL_FIELD_ACCESSOR(pg_stat_get, numscans);

PGSTAT_DEFINE_REL_FIELD_ACCESSOR(pg_stat_get, tuples_returned);
.
.
.
"

If that makes sense to you, I'll submit this preparatory patch.

I think the macros stitching the function declarations and definitions
is a great idea to avoid code duplicacy. We seem to be using that
approach already - PG_FUNCTION_INFO_V1, SH_DECLARE, SH_DEFINE and its
friends, STEMMER_MODULE and so on. +1 for first applying this
principle for existing functions. Looking forward to the patch.

Thanks! Patch proposal submitted in [1]/messages/by-id/d547a9bc-76c2-f875-df74-3ad6fd9d6236@gmail.com.

I'll resume working on the current thread once [1]/messages/by-id/d547a9bc-76c2-f875-df74-3ad6fd9d6236@gmail.com is committed.

[1]: /messages/by-id/d547a9bc-76c2-f875-df74-3ad6fd9d6236@gmail.com

Regards,

--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

#14Drouvot, Bertrand
bertranddrouvot.pg@gmail.com
In reply to: Drouvot, Bertrand (#13)
1 attachment(s)
Re: Split index and table statistics into different types of stats

Hi,

On 11/22/22 8:12 AM, Drouvot, Bertrand wrote:

Hi,

On 11/22/22 7:19 AM, Bharath Rupireddy wrote:

On Mon, Nov 21, 2022 at 7:03 PM Drouvot, Bertrand
<bertranddrouvot.pg@gmail.com> wrote:

On 11/21/22 12:19 AM, Andres Freund wrote:

That's better, but still seems like quite a bit of repetition, given the
number of accessors. I think I like my idea of a macro defining the whole
function a bit better.

Got it, what about creating another preparatory commit to first introduce something like:

"
#define PGSTAT_DEFINE_REL_FIELD_ACCESSOR(function_name_prefix, stat_name) \
Datum \
function_name_prefix##_##stat_name(PG_FUNCTION_ARGS) \
{ \
Oid                     relid = PG_GETARG_OID(0); \
int64           result; \
PgStat_StatTabEntry *tabentry; \
if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL) \
         result = 0; \
else \
         result = (int64) (tabentry->stat_name); \
PG_RETURN_INT64(result); \
} \

PGSTAT_DEFINE_REL_FIELD_ACCESSOR(pg_stat_get, numscans);

PGSTAT_DEFINE_REL_FIELD_ACCESSOR(pg_stat_get, tuples_returned);
.
.
.
"

If that makes sense to you, I'll submit this preparatory patch.

I think the macros stitching the function declarations and definitions
is a great idea to avoid code duplicacy. We seem to be using that
approach already - PG_FUNCTION_INFO_V1, SH_DECLARE, SH_DEFINE and its
friends, STEMMER_MODULE and so on. +1 for first applying this
principle for existing functions. Looking forward to the patch.

Thanks! Patch proposal submitted in [1].

I'll resume working on the current thread once [1] is committed.

[1]: /messages/by-id/d547a9bc-76c2-f875-df74-3ad6fd9d6236@gmail.com

As [1] mentioned above has been committed (83a1a1b566), please find attached V5 related to this thread making use of the new macros (namely PG_STAT_GET_RELENTRY_INT64 and PG_STAT_GET_RELENTRY_TIMESTAMPTZ).

I switched from using "CppConcat" to using "##", as it looks to me it's easier to read now that we are adding another concatenation to the game (due to the table/index split).

The (Tab,tab) or (Ind,ind) passed as arguments to the macros look "weird" (I don't have a better idea yet): purpose is to follow the naming convention for PgStat_StatTabEntry/PgStat_StatIndEntry and pgstat_fetch_stat_tabentry/pgstat_fetch_stat_indentry, thoughts?

Looking forward to your feedback,

Regards,

--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

Attachments:

v5-0001-split_tables_indexes_stats.patchtext/plain; charset=UTF-8; name=v5-0001-split_tables_indexes_stats.patchDownload
diff --git a/src/backend/access/common/relation.c b/src/backend/access/common/relation.c
index 382a42ff7d..259bd06aed 100644
--- a/src/backend/access/common/relation.c
+++ b/src/backend/access/common/relation.c
@@ -73,7 +73,10 @@ relation_open(Oid relationId, LOCKMODE lockmode)
 	if (RelationUsesLocalBuffers(r))
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
-	pgstat_init_relation(r);
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_init_index(r);
+	else
+		pgstat_init_table(r);
 
 	return r;
 }
@@ -123,7 +126,10 @@ try_relation_open(Oid relationId, LOCKMODE lockmode)
 	if (RelationUsesLocalBuffers(r))
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
-	pgstat_init_relation(r);
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_init_index(r);
+	else
+		pgstat_init_table(r);
 
 	return r;
 }
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index dc303995e5..ceebc79302 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -590,7 +590,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 bdd413f01b..d5b71fdd1e 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..a612ae81ed 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -655,29 +655,29 @@ 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_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,
-            pg_stat_get_tuples_hot_updated(C.oid) AS n_tup_hot_upd,
-            pg_stat_get_live_tuples(C.oid) AS n_live_tup,
-            pg_stat_get_dead_tuples(C.oid) AS n_dead_tup,
-            pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze,
-            pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum,
-            pg_stat_get_last_vacuum_time(C.oid) as last_vacuum,
-            pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum,
-            pg_stat_get_last_analyze_time(C.oid) as last_analyze,
-            pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze,
-            pg_stat_get_vacuum_count(C.oid) AS vacuum_count,
-            pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count,
-            pg_stat_get_analyze_count(C.oid) AS analyze_count,
-            pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count
+            pg_stat_get_tab_numscans(C.oid) AS seq_scan,
+            pg_stat_get_tab_lastscan(C.oid) AS last_seq_scan,
+            pg_stat_get_tab_tuples_returned(C.oid) AS seq_tup_read,
+            sum(pg_stat_get_ind_numscans(I.indexrelid))::bigint AS idx_scan,
+            max(pg_stat_get_ind_lastscan(I.indexrelid)) AS last_idx_scan,
+            sum(pg_stat_get_ind_tuples_fetched(I.indexrelid))::bigint +
+            pg_stat_get_tab_tuples_fetched(C.oid) AS idx_tup_fetch,
+            pg_stat_get_tab_tuples_inserted(C.oid) AS n_tup_ins,
+            pg_stat_get_tab_tuples_updated(C.oid) AS n_tup_upd,
+            pg_stat_get_tab_tuples_deleted(C.oid) AS n_tup_del,
+            pg_stat_get_tab_tuples_hot_updated(C.oid) AS n_tup_hot_upd,
+            pg_stat_get_tab_live_tuples(C.oid) AS n_live_tup,
+            pg_stat_get_tab_dead_tuples(C.oid) AS n_dead_tup,
+            pg_stat_get_tab_mod_since_analyze(C.oid) AS n_mod_since_analyze,
+            pg_stat_get_tab_ins_since_vacuum(C.oid) AS n_ins_since_vacuum,
+            pg_stat_get_tab_last_vacuum_time(C.oid) as last_vacuum,
+            pg_stat_get_tab_last_autovacuum_time(C.oid) as last_autovacuum,
+            pg_stat_get_tab_last_analyze_time(C.oid) as last_analyze,
+            pg_stat_get_tab_last_autoanalyze_time(C.oid) as last_autoanalyze,
+            pg_stat_get_tab_vacuum_count(C.oid) AS vacuum_count,
+            pg_stat_get_tab_autovacuum_count(C.oid) AS autovacuum_count,
+            pg_stat_get_tab_analyze_count(C.oid) AS analyze_count,
+            pg_stat_get_tab_autoanalyze_count(C.oid) AS autoanalyze_count
     FROM pg_class C LEFT JOIN
          pg_index I ON C.oid = I.indrelid
          LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
@@ -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_tab_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_ind_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_tab_blocks_fetched(C.oid) -
+                    pg_stat_get_tab_blocks_hit(C.oid) AS heap_blks_read,
+            pg_stat_get_tab_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_tab_blocks_fetched(T.oid) -
+                    pg_stat_get_tab_blocks_hit(T.oid) AS toast_blks_read,
+            pg_stat_get_tab_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_ind_blocks_fetched(indexrelid) -
+                         pg_stat_get_ind_blocks_hit(indexrelid))::bigint
                      AS idx_blks_read,
-                     sum(pg_stat_get_blocks_hit(indexrelid))::bigint
+                     sum(pg_stat_get_ind_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_ind_blocks_fetched(indexrelid) -
+                         pg_stat_get_ind_blocks_hit(indexrelid))::bigint
                      AS idx_blks_read,
-                     sum(pg_stat_get_blocks_hit(indexrelid))::bigint
+                     sum(pg_stat_get_ind_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_ind_numscans(I.oid) AS idx_scan,
+            pg_stat_get_ind_lastscan(I.oid) AS last_idx_scan,
+            pg_stat_get_ind_tuples_returned(I.oid) AS idx_tup_read,
+            pg_stat_get_ind_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_ind_blocks_fetched(I.oid) -
+                    pg_stat_get_ind_blocks_hit(I.oid) AS idx_blks_read,
+            pg_stat_get_ind_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_ind_blocks_fetched(C.oid) -
+                    pg_stat_get_tab_blocks_hit(C.oid) AS blks_read,
+            pg_stat_get_tab_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/Makefile b/src/backend/utils/activity/Makefile
index a2e8507fd6..7441f4ce85 100644
--- a/src/backend/utils/activity/Makefile
+++ b/src/backend/utils/activity/Makefile
@@ -22,11 +22,12 @@ OBJS = \
 	pgstat_checkpointer.o \
 	pgstat_database.o \
 	pgstat_function.o \
-	pgstat_relation.o \
+	pgstat_index.o \
 	pgstat_replslot.o \
 	pgstat_shmem.o \
 	pgstat_slru.o \
 	pgstat_subscription.o \
+	pgstat_table.o \
 	pgstat_wal.o \
 	pgstat_xact.o \
 	wait_event.o
diff --git a/src/backend/utils/activity/meson.build b/src/backend/utils/activity/meson.build
index 5b3b558a67..e6bf3c59b6 100644
--- a/src/backend/utils/activity/meson.build
+++ b/src/backend/utils/activity/meson.build
@@ -7,11 +7,12 @@ backend_sources += files(
   'pgstat_checkpointer.c',
   'pgstat_database.c',
   'pgstat_function.c',
-  'pgstat_relation.c',
+  'pgstat_index.c',
   'pgstat_replslot.c',
   'pgstat_shmem.c',
   'pgstat_slru.c',
   'pgstat_subscription.c',
+  'pgstat_table.c',
   'pgstat_wal.c',
   'pgstat_xact.c',
   'wait_event.c',
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index 1ebe3bbf29..d49a5ea4bb 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -72,10 +72,11 @@
  * - pgstat_checkpointer.c
  * - pgstat_database.c
  * - pgstat_function.c
- * - pgstat_relation.c
+ * - pgstat_index.c
  * - pgstat_replslot.c
  * - pgstat_slru.c
  * - pgstat_subscription.c
+ * - pgstat_table.c
  * - pgstat_wal.c
  *
  * Whenever possible infrastructure files should not contain code related to
@@ -269,18 +270,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_index.c b/src/backend/utils/activity/pgstat_index.c
new file mode 100644
index 0000000000..13b48b11e5
--- /dev/null
+++ b/src/backend/utils/activity/pgstat_index.c
@@ -0,0 +1,297 @@
+/* -------------------------------------------------------------------------
+ *
+ * pgstat_index.c
+ *	  Implementation of index statistics.
+ *
+ * This file contains the implementation of function index. It is kept
+ * separate from pgstat.c to enforce the line between the statistics access /
+ * storage implementation and the details about individual types of
+ * statistics.
+ *
+ * Copyright (c) 2001-2022, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/activity/pgstat_index.c
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/xact.h"
+#include "utils/pgstat_internal.h"
+#include "utils/rel.h"
+#include "catalog/catalog.h"
+
+static PgStat_IndexStatus *pgstat_prep_index_pending(Oid rel_id, bool isshared);
+
+/*
+ * Copy stats between indexes. This is used for things like REINDEX
+ * CONCURRENTLY.
+ */
+void
+pgstat_copy_index_stats(Relation dst, Relation src)
+{
+	PgStat_StatIndEntry *srcstats;
+	PgStatShared_Index *dstshstats;
+	PgStat_EntryRef *dst_ref;
+
+	srcstats = pgstat_fetch_stat_indentry_ext(src->rd_rel->relisshared,
+											  RelationGetRelid(src));
+	if (!srcstats)
+		return;
+
+	dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_INDEX,
+										  dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+										  RelationGetRelid(dst),
+										  false);
+
+	dstshstats = (PgStatShared_Index *) dst_ref->shared_stats;
+	dstshstats->stats = *srcstats;
+
+	pgstat_unlock_entry(dst_ref);
+}
+
+/*
+ * Initialize a relcache entry to count access statistics.  Called whenever an
+ * index is opened.
+ *
+ * We assume that a relcache entry's pgstatind_info field is 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
+ * pgstat_assoc_index().
+ */
+void
+pgstat_init_index(Relation rel)
+{
+	/*
+	 * We only count stats for indexes
+	 */
+	Assert(rel->rd_rel->relkind == RELKIND_INDEX);
+
+	if (!pgstat_track_counts)
+	{
+		if (rel->pgstatind_info != NULL)
+			pgstat_unlink_index(rel);
+
+		/* We're not counting at all */
+		rel->pgstat_enabled = false;
+		rel->pgstatind_info = NULL;
+		return;
+	}
+
+	rel->pgstat_enabled = true;
+}
+
+/*
+ * 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_index() 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->pgstatind_info->relation == NULL);
+
+	/* mark this relation as the owner */
+	rel->pgstatind_info->relation = rel;
+}
+
+/*
+ * Break the mutual link between a relcache entry and pending index stats entry.
+ * This must be called whenever one end of the link is removed.
+ */
+void
+pgstat_unlink_index(Relation rel)
+{
+
+	if (rel->pgstatind_info == NULL)
+		return;
+
+	/* 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 index stats are dropped if transaction aborts.
+ */
+void
+pgstat_create_index(Relation rel)
+{
+	pgstat_create_transactional(PGSTAT_KIND_INDEX,
+								rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+								RelationGetRelid(rel));
+}
+
+/*
+ * Ensure that index stats are dropped if transaction commits.
+ */
+void
+pgstat_drop_index(Relation rel)
+{
+	pgstat_drop_transactional(PGSTAT_KIND_INDEX,
+							  rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+							  RelationGetRelid(rel));
+}
+
+/*
+ * 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)
+{
+	return pgstat_fetch_stat_indentry_ext(IsSharedRelation(relid), relid);
+}
+
+/*
+ * 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);
+}
+
+/*
+ * 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;
+	return NULL;
+}
+
+/*
+ * 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_index_delete_pending_cb(PgStat_EntryRef *entry_ref)
+{
+	PgStat_IndexStatus *pending = (PgStat_IndexStatus *) entry_ref->pending;
+
+	if (pending->relation)
+		pgstat_unlink_index(pending->relation);
+}
+
+/*
+ * 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;
+}
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_table.c
similarity index 75%
rename from src/backend/utils/activity/pgstat_relation.c
rename to src/backend/utils/activity/pgstat_table.c
index a9c05153d9..b9c29bd7c2 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_table.c
@@ -1,9 +1,9 @@
 /* -------------------------------------------------------------------------
  *
- * pgstat_relation.c
- *	  Implementation of relation statistics.
+ * pgstat_table.c
+ *	  Implementation of table statistics.
  *
- * This file contains the implementation of function relation. It is kept
+ * This file contains the implementation of function table. It is kept
  * separate from pgstat.c to enforce the line between the statistics access /
  * storage implementation and the details about individual types of
  * statistics.
@@ -11,7 +11,7 @@
  * Copyright (c) 2001-2022, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *	  src/backend/utils/activity/pgstat_relation.c
+ *	  src/backend/utils/activity/pgstat_table.c
  * -------------------------------------------------------------------------
  */
 
@@ -44,74 +44,48 @@ 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 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
- * CONCURRENTLY.
- */
-void
-pgstat_copy_relation_stats(Relation dst, Relation src)
-{
-	PgStat_StatTabEntry *srcstats;
-	PgStatShared_Relation *dstshstats;
-	PgStat_EntryRef *dst_ref;
-
-	srcstats = pgstat_fetch_stat_tabentry_ext(src->rd_rel->relisshared,
-											  RelationGetRelid(src));
-	if (!srcstats)
-		return;
-
-	dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
-										  dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
-										  RelationGetRelid(dst),
-										  false);
-
-	dstshstats = (PgStatShared_Relation *) dst_ref->shared_stats;
-	dstshstats->stats = *srcstats;
-
-	pgstat_unlock_entry(dst_ref);
-}
-
 /*
  * Initialize a relcache entry to count access statistics.  Called whenever a
- * relation is opened.
+ * table is opened.
  *
- * We assume that a relcache entry's pgstat_info field is zeroed by relcache.c
+ * We assume that a relcache entry's pgstattab_info field is 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
- * pgstat_assoc_relation().
+ * pgstat_assoc_table().
  */
 void
-pgstat_init_relation(Relation rel)
+pgstat_init_table(Relation rel)
 {
 	char		relkind = rel->rd_rel->relkind;
 
+	Assert(relkind != RELKIND_INDEX);
+
 	/*
 	 * We only count stats for relations with storage and partitioned tables
 	 */
 	if (!RELKIND_HAS_STORAGE(relkind) && relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		rel->pgstat_enabled = false;
-		rel->pgstat_info = NULL;
+		rel->pgstattab_info = NULL;
 		return;
 	}
 
 	if (!pgstat_track_counts)
 	{
-		if (rel->pgstat_info)
-			pgstat_unlink_relation(rel);
+		if (rel->pgstattab_info != NULL)
+			pgstat_unlink_table(rel);
 
 		/* We're not counting at all */
 		rel->pgstat_enabled = false;
-		rel->pgstat_info = NULL;
+		rel->pgstattab_info = NULL;
 		return;
 	}
 
@@ -119,89 +93,92 @@ 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.
  *
- * This is separate from pgstat_init_relation() as it is not uncommon for
+ * This is separate from pgstat_init_table() as it is not uncommon for
  * 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->pgstat_info->relation == NULL);
+	Assert(rel->pgstattab_info->relation == NULL);
 
 	/* mark this relation as the owner */
-	rel->pgstat_info->relation = rel;
+	rel->pgstattab_info->relation = rel;
 }
 
 /*
- * Break the mutual link between a relcache entry and pending stats entry.
+ * Break the mutual link between a relcache entry and pending table stats entry.
  * This must be called whenever one end of the link is removed.
  */
 void
-pgstat_unlink_relation(Relation rel)
+pgstat_unlink_table(Relation rel)
 {
-	/* remove the link to stats info if any */
-	if (rel->pgstat_info == NULL)
+
+	if (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;
+	}
 }
 
 /*
- * Ensure that stats are dropped if transaction aborts.
+ * Ensure that table stats are dropped if transaction aborts.
  */
 void
-pgstat_create_relation(Relation rel)
+pgstat_create_table(Relation rel)
 {
-	pgstat_create_transactional(PGSTAT_KIND_RELATION,
+	pgstat_create_transactional(PGSTAT_KIND_TABLE,
 								rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
 								RelationGetRelid(rel));
 }
 
 /*
- * Ensure that stats are dropped if transaction commits.
+ * Ensure that table stats are dropped if transaction commits.
  */
 void
-pgstat_drop_relation(Relation rel)
+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;
 	}
 }
 
@@ -213,7 +190,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;
@@ -225,10 +202,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->live_tuples = livetuples;
@@ -272,7 +249,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);
 
@@ -291,31 +268,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->live_tuples = livetuples;
@@ -349,12 +326,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;
 	}
 }
 
@@ -364,16 +341,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++;
 	}
 }
 
@@ -383,12 +360,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++;
 	}
 }
 
@@ -398,15 +375,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;
 	}
 }
 
@@ -421,11 +398,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;
 	}
 }
 
@@ -451,7 +428,7 @@ 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);
 }
 
 /*
@@ -467,9 +444,9 @@ 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;
@@ -477,7 +454,7 @@ find_tabstat_entry(Oid rel_id)
 }
 
 /*
- * Perform relation stats specific end-of-transaction work. Helper for
+ * Perform table stats specific end-of-transaction work. Helper for
  * AtEOXact_PgStat.
  *
  * Transfer transactional insert/update counts into the base tabstat entries.
@@ -485,7 +462,7 @@ find_tabstat_entry(Oid rel_id)
  * TopTransactionContext and will go away anyway.
  */
 void
-AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit)
+AtEOXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit)
 {
 	PgStat_TableXactStatus *trans;
 
@@ -536,14 +513,14 @@ AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit)
 }
 
 /*
- * Perform relation stats specific end-of-sub-transaction work. Helper for
+ * Perform table stats specific end-of-sub-transaction work. Helper for
  * AtEOSubXact_PgStat.
  *
  * Transfer transactional insert/update counts into the next higher
  * subtransaction state.
  */
 void
-AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
+AtEOSubXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
 {
 	PgStat_TableXactStatus *trans;
 	PgStat_TableXactStatus *next_trans;
@@ -620,11 +597,11 @@ AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, in
 }
 
 /*
- * Generate 2PC records for all the pending transaction-dependent relation
+ * Generate 2PC records for all the pending transaction-dependent table
  * stats.
  */
 void
-AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
+AtPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state)
 {
 	PgStat_TableXactStatus *trans;
 
@@ -659,10 +636,10 @@ AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
  * reported to the stats system immediately, while the effects on live and
  * dead tuple counts are preserved in the 2PC state file.
  *
- * Note: AtEOXact_PgStat_Relations is not called during PREPARE.
+ * Note: AtEOXact_PgStat_Tables is not called during PREPARE.
  */
 void
-PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
+PostPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state)
 {
 	PgStat_TableXactStatus *trans;
 
@@ -685,27 +662,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;
 }
@@ -721,10 +698,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)
@@ -733,10 +710,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;
 }
 
@@ -750,22 +727,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)
@@ -783,6 +759,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;
 	}
@@ -831,12 +808,12 @@ pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
 }
 
 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;
 
 	if (pending->relation)
-		pgstat_unlink_relation(pending->relation);
+		pgstat_unlink_table(pending->relation);
 }
 
 /*
@@ -844,12 +821,12 @@ pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref)
  * 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;
@@ -863,7 +840,7 @@ pgstat_prep_relation_pending(Oid rel_id, bool isshared)
  * 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;
@@ -879,24 +856,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/activity/pgstat_xact.c b/src/backend/utils/activity/pgstat_xact.c
index 5a3aca4aef..870f1b1103 100644
--- a/src/backend/utils/activity/pgstat_xact.c
+++ b/src/backend/utils/activity/pgstat_xact.c
@@ -51,7 +51,7 @@ AtEOXact_PgStat(bool isCommit, bool parallel)
 		Assert(xact_state->nest_level == 1);
 		Assert(xact_state->prev == NULL);
 
-		AtEOXact_PgStat_Relations(xact_state, isCommit);
+		AtEOXact_PgStat_Tables(xact_state, isCommit);
 		AtEOXact_PgStat_DroppedStats(xact_state, isCommit);
 	}
 	pgStatXactStack = NULL;
@@ -122,7 +122,7 @@ AtEOSubXact_PgStat(bool isCommit, int nestDepth)
 		/* delink xact_state from stack immediately to simplify reuse case */
 		pgStatXactStack = xact_state->prev;
 
-		AtEOSubXact_PgStat_Relations(xact_state, isCommit, nestDepth);
+		AtEOSubXact_PgStat_Tables(xact_state, isCommit, nestDepth);
 		AtEOSubXact_PgStat_DroppedStats(xact_state, isCommit, nestDepth);
 
 		pfree(xact_state);
@@ -197,7 +197,7 @@ AtPrepare_PgStat(void)
 		Assert(xact_state->nest_level == 1);
 		Assert(xact_state->prev == NULL);
 
-		AtPrepare_PgStat_Relations(xact_state);
+		AtPrepare_PgStat_Tables(xact_state);
 	}
 }
 
@@ -221,7 +221,7 @@ PostPrepare_PgStat(void)
 		Assert(xact_state->nest_level == 1);
 		Assert(xact_state->prev == NULL);
 
-		PostPrepare_PgStat_Relations(xact_state);
+		PostPrepare_PgStat_Tables(xact_state);
 	}
 	pgStatXactStack = NULL;
 
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 973979508d..dea762c116 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -36,106 +36,124 @@
 
 #define HAS_PGSTAT_PERMISSIONS(role)	 (has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS) || has_privs_of_role(GetUserId(), role))
 
-#define PG_STAT_GET_RELENTRY_INT64(stat)						\
-Datum															\
-CppConcat(pg_stat_get_,stat)(PG_FUNCTION_ARGS)					\
-{																\
-	Oid			relid = PG_GETARG_OID(0);						\
-	int64		result;											\
-	PgStat_StatTabEntry *tabentry;								\
-																\
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)	\
-		result = 0;												\
-	else														\
-		result = (int64) (tabentry->stat);						\
-																\
-	PG_RETURN_INT64(result);									\
-}																\
+#define PG_STAT_GET_RELENTRY_INT64(Relkind,relkind,stat)				\
+Datum																	\
+pg_stat_get_##relkind##_##stat(PG_FUNCTION_ARGS)						\
+{																		\
+	Oid			relid = PG_GETARG_OID(0);								\
+	int64		result;													\
+	PgStat_Stat##Relkind##Entry *entry;									\
+																		\
+	if ((entry = pgstat_fetch_stat_##relkind##entry(relid)) == NULL)	\
+		result = 0;														\
+	else																\
+		result = (int64) (entry->stat);									\
+																		\
+	PG_RETURN_INT64(result);											\
+}
+
+/* pg_stat_get_tab_analyze_count */
+PG_STAT_GET_RELENTRY_INT64(Tab,tab,analyze_count);
+
+/* pg_stat_get_tab_autoanalyze_count */
+PG_STAT_GET_RELENTRY_INT64(Tab,tab,autoanalyze_count);
+
+/* pg_stat_get_tab_autovacuum_count */
+PG_STAT_GET_RELENTRY_INT64(Tab,tab,autovacuum_count);
+
+/* pg_stat_get_tab_blocks_fetched */
+PG_STAT_GET_RELENTRY_INT64(Tab,tab,blocks_fetched);
+
+/* pg_stat_get_tab_blocks_hit */
+PG_STAT_GET_RELENTRY_INT64(Tab,tab,blocks_hit);
+
+/* pg_stat_get_tab_dead_tuples */
+PG_STAT_GET_RELENTRY_INT64(Tab,tab,dead_tuples);
 
-/* pg_stat_get_analyze_count */
-PG_STAT_GET_RELENTRY_INT64(analyze_count);
+/* pg_stat_get_tab_ins_since_vacuum */
+PG_STAT_GET_RELENTRY_INT64(Tab,tab,ins_since_vacuum);
 
-/* pg_stat_get_autoanalyze_count */
-PG_STAT_GET_RELENTRY_INT64(autoanalyze_count);
+/* pg_stat_get_tab_live_tuples */
+PG_STAT_GET_RELENTRY_INT64(Tab,tab,live_tuples);
 
-/* pg_stat_get_autovacuum_count */
-PG_STAT_GET_RELENTRY_INT64(autovacuum_count);
+/* pg_stat_get_mod_since_analyze */
+PG_STAT_GET_RELENTRY_INT64(Tab,tab,mod_since_analyze);
 
-/* pg_stat_get_blocks_fetched */
-PG_STAT_GET_RELENTRY_INT64(blocks_fetched);
+/* pg_stat_get_tab_numscans */
+PG_STAT_GET_RELENTRY_INT64(Tab,tab,numscans);
 
-/* pg_stat_get_blocks_hit */
-PG_STAT_GET_RELENTRY_INT64(blocks_hit);
+/* pg_stat_get_tab_tuples_deleted */
+PG_STAT_GET_RELENTRY_INT64(Tab,tab,tuples_deleted);
 
-/* pg_stat_get_dead_tuples */
-PG_STAT_GET_RELENTRY_INT64(dead_tuples);
+/* pg_stat_get_tab_tuples_fetched */
+PG_STAT_GET_RELENTRY_INT64(Tab,tab,tuples_fetched);
 
-/* pg_stat_get_ins_since_vacuum */
-PG_STAT_GET_RELENTRY_INT64(ins_since_vacuum);
+/* pg_stat_get_tab_tuples_hot_updated */
+PG_STAT_GET_RELENTRY_INT64(Tab,tab,tuples_hot_updated);
 
-/* pg_stat_get_live_tuples */
-PG_STAT_GET_RELENTRY_INT64(live_tuples);
+/* pg_stat_get_tab_tuples_inserted */
+PG_STAT_GET_RELENTRY_INT64(Tab,tab,tuples_inserted);
 
-/* pg_stat_get_mods_since_analyze */
-PG_STAT_GET_RELENTRY_INT64(mod_since_analyze);
+/* pg_stat_get_tab_tuples_returned */
+PG_STAT_GET_RELENTRY_INT64(Tab,tab,tuples_returned);
 
-/* pg_stat_get_numscans */
-PG_STAT_GET_RELENTRY_INT64(numscans);
+/* pg_stat_get_tab_tuples_updated */
+PG_STAT_GET_RELENTRY_INT64(Tab,tab,tuples_updated);
 
-/* pg_stat_get_tuples_deleted */
-PG_STAT_GET_RELENTRY_INT64(tuples_deleted);
+/* pg_stat_get_tab_vacuum_count */
+PG_STAT_GET_RELENTRY_INT64(Tab,tab,vacuum_count);
 
-/* pg_stat_get_tuples_fetched */
-PG_STAT_GET_RELENTRY_INT64(tuples_fetched);
+/* pg_stat_get_ind_numscans */
+PG_STAT_GET_RELENTRY_INT64(Ind,ind,numscans);
 
-/* pg_stat_get_tuples_hot_updated */
-PG_STAT_GET_RELENTRY_INT64(tuples_hot_updated);
+/* pg_stat_get_ind_tuples_returned */
+PG_STAT_GET_RELENTRY_INT64(Ind,ind,tuples_returned);
 
-/* pg_stat_get_tuples_inserted */
-PG_STAT_GET_RELENTRY_INT64(tuples_inserted);
+/* pg_stat_get_ind_tuples_fetched */
+PG_STAT_GET_RELENTRY_INT64(Ind,ind,tuples_fetched);
 
-/* pg_stat_get_tuples_returned */
-PG_STAT_GET_RELENTRY_INT64(tuples_returned);
+/* pg_stat_get_ind_blocks_fetched */
+PG_STAT_GET_RELENTRY_INT64(Ind,ind,blocks_fetched);
 
-/* pg_stat_get_tuples_updated */
-PG_STAT_GET_RELENTRY_INT64(tuples_updated);
+/* pg_stat_get_ind_blocks_hit */
+PG_STAT_GET_RELENTRY_INT64(Ind,ind,blocks_hit);
 
-/* pg_stat_get_vacuum_count */
-PG_STAT_GET_RELENTRY_INT64(vacuum_count);
+#define PG_STAT_GET_RELENTRY_TIMESTAMPTZ(Relkind,relkind,stat)			\
+Datum																	\
+pg_stat_get_##relkind##_##stat(PG_FUNCTION_ARGS)						\
+{																		\
+	Oid			relid = PG_GETARG_OID(0);								\
+	TimestampTz		result;												\
+	PgStat_Stat##Relkind##Entry *entry;									\
+																		\
+	if ((entry = pgstat_fetch_stat_##relkind##entry(relid)) == NULL)	\
+		result = 0;														\
+	else																\
+		result = entry->stat;											\
+																		\
+	if (result == 0)													\
+		PG_RETURN_NULL();												\
+	else																\
+		PG_RETURN_TIMESTAMPTZ(result);									\
+}
 
-#define PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat)					\
-Datum															\
-CppConcat(pg_stat_get_,stat)(PG_FUNCTION_ARGS)					\
-{																\
-	Oid			relid = PG_GETARG_OID(0);						\
-	TimestampTz result;											\
-	PgStat_StatTabEntry *tabentry;								\
-																\
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)	\
-		result = 0;												\
-	else														\
-		result = tabentry->stat;								\
-																\
-	if (result == 0)											\
-		PG_RETURN_NULL();										\
-	else														\
-		PG_RETURN_TIMESTAMPTZ(result);							\
-}																\
+/* pg_stat_get_tab_last_analyze_time */
+PG_STAT_GET_RELENTRY_TIMESTAMPTZ(Tab,tab,last_analyze_time);
 
-/* pg_stat_get_last_analyze_time */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_analyze_time);
+/* pg_stat_get_tab_last_autoanalyze_time */
+PG_STAT_GET_RELENTRY_TIMESTAMPTZ(Tab,tab,last_autoanalyze_time);
 
-/* pg_stat_get_last_autoanalyze_time */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_autoanalyze_time);
+/* pg_stat_get_tab_last_autovacuum_time */
+PG_STAT_GET_RELENTRY_TIMESTAMPTZ(Tab,tab,last_autovacuum_time);
 
-/* pg_stat_get_last_autovacuum_time */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_autovacuum_time);
+/* pg_stat_get_tab_last_vacuum_time */
+PG_STAT_GET_RELENTRY_TIMESTAMPTZ(Tab,tab,last_vacuum_time);
 
-/* pg_stat_get_last_vacuum_time */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time);
+/* pg_stat_get_tab_lastscan */
+PG_STAT_GET_RELENTRY_TIMESTAMPTZ(Tab,tab,lastscan);
 
-/* pg_stat_get_lastscan */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan);
+/* pg_stat_get_ind_lastscan */
+PG_STAT_GET_RELENTRY_TIMESTAMPTZ(Ind,ind,lastscan);
 
 Datum
 pg_stat_get_function_calls(PG_FUNCTION_ARGS)
@@ -1586,7 +1604,7 @@ pg_stat_get_slru(PG_FUNCTION_ARGS)
 }
 
 Datum
-pg_stat_get_xact_numscans(PG_FUNCTION_ARGS)
+pg_stat_get_tab_xact_numscans(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
@@ -1600,17 +1618,32 @@ pg_stat_get_xact_numscans(PG_FUNCTION_ARGS)
 	PG_RETURN_INT64(result);
 }
 
+Datum
+pg_stat_get_ind_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);
 }
@@ -1620,12 +1653,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);
 }
@@ -1713,12 +1746,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);
 }
@@ -1728,12 +1761,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);
 }
@@ -1852,7 +1885,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 a50eecc7c8..e74704742c 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -2411,7 +2411,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 	RelationCloseSmgr(relation);
 
 	/* break mutual link with stats entry */
-	pgstat_unlink_relation(relation);
+	if (relation->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_unlink_index(relation);
+	else
+		pgstat_unlink_table(relation);
 
 	/*
 	 * Free all the subsidiary data structures of the relcache entry, then the
@@ -2727,8 +2730,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)
@@ -6321,7 +6325,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 f9301b2627..c3fe4af669 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5274,96 +5274,120 @@
   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_tab_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_tab_numscans' },
+{ oid => '8296', descr => 'statistics: number of scans done for index',
+  proname => 'pg_stat_get_ind_numscans', provolatile => 's', proparallel => 'r',
+  prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_ind_numscans' },
+{ oid => '9976', descr => 'statistics: time of the last scan for table',
+  proname => 'pg_stat_get_tab_lastscan', provolatile => 's', proparallel => 'r',
+  prorettype => 'timestamptz', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tab_lastscan' },
+{ oid => '8626', descr => 'statistics: time of the last scan for index',
+  proname => 'pg_stat_get_ind_lastscan', provolatile => 's', proparallel => 'r',
   prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_lastscan' },
+  prosrc => 'pg_stat_get_ind_lastscan' },
 { oid => '1929', descr => 'statistics: number of tuples read by seqscan',
-  proname => 'pg_stat_get_tuples_returned', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_returned', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_returned' },
+  prosrc => 'pg_stat_get_tab_tuples_returned' },
+{ oid => '9603', descr => 'statistics: number of tuples read by seqscan',
+  proname => 'pg_stat_get_ind_tuples_returned', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_ind_tuples_returned' },
 { oid => '1930', descr => 'statistics: number of tuples fetched by idxscan',
-  proname => 'pg_stat_get_tuples_fetched', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_fetched', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tab_tuples_fetched' },
+{ oid => '8526', descr => 'statistics: number of tuples fetched by idxscan',
+  proname => 'pg_stat_get_ind_tuples_fetched', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_fetched' },
+  prosrc => 'pg_stat_get_ind_tuples_fetched' },
 { oid => '1931', descr => 'statistics: number of tuples inserted',
-  proname => 'pg_stat_get_tuples_inserted', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_inserted', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_inserted' },
+  prosrc => 'pg_stat_get_tab_tuples_inserted' },
 { oid => '1932', descr => 'statistics: number of tuples updated',
-  proname => 'pg_stat_get_tuples_updated', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_updated', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_updated' },
+  prosrc => 'pg_stat_get_tab_tuples_updated' },
 { oid => '1933', descr => 'statistics: number of tuples deleted',
-  proname => 'pg_stat_get_tuples_deleted', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_deleted', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_deleted' },
+  prosrc => 'pg_stat_get_tab_tuples_deleted' },
 { oid => '1972', descr => 'statistics: number of tuples hot updated',
-  proname => 'pg_stat_get_tuples_hot_updated', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_hot_updated', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_hot_updated' },
+  prosrc => 'pg_stat_get_tab_tuples_hot_updated' },
 { oid => '2878', descr => 'statistics: number of live tuples',
-  proname => 'pg_stat_get_live_tuples', provolatile => 's', proparallel => 'r',
+  proname => 'pg_stat_get_tab_live_tuples', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_live_tuples' },
+  prosrc => 'pg_stat_get_tab_live_tuples' },
 { oid => '2879', descr => 'statistics: number of dead tuples',
-  proname => 'pg_stat_get_dead_tuples', provolatile => 's', proparallel => 'r',
+  proname => 'pg_stat_get_tab_dead_tuples', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_dead_tuples' },
+  prosrc => 'pg_stat_get_tab_dead_tuples' },
 { oid => '3177',
   descr => 'statistics: number of tuples changed since last analyze',
-  proname => 'pg_stat_get_mod_since_analyze', provolatile => 's',
+  proname => 'pg_stat_get_tab_mod_since_analyze', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_mod_since_analyze' },
+  prosrc => 'pg_stat_get_tab_mod_since_analyze' },
 { oid => '5053',
   descr => 'statistics: number of tuples inserted since last vacuum',
-  proname => 'pg_stat_get_ins_since_vacuum', provolatile => 's',
+  proname => 'pg_stat_get_tab_ins_since_vacuum', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_ins_since_vacuum' },
+  prosrc => 'pg_stat_get_tab_ins_since_vacuum' },
 { oid => '1934', descr => 'statistics: number of blocks fetched',
-  proname => 'pg_stat_get_blocks_fetched', provolatile => 's',
+  proname => 'pg_stat_get_tab_blocks_fetched', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_blocks_fetched' },
+  prosrc => 'pg_stat_get_tab_blocks_fetched' },
+{ oid => '9432', descr => 'statistics: number of blocks fetched',
+  proname => 'pg_stat_get_ind_blocks_fetched', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_ind_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_tab_blocks_hit', provolatile => 's', proparallel => 'r',
+  prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tab_blocks_hit' },
+{ oid => '8354', descr => 'statistics: number of blocks found in cache',
+  proname => 'pg_stat_get_ind_blocks_hit', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_blocks_hit' },
+  prosrc => 'pg_stat_get_ind_blocks_hit' },
 { oid => '2781', descr => 'statistics: last manual vacuum time for a table',
-  proname => 'pg_stat_get_last_vacuum_time', provolatile => 's',
+  proname => 'pg_stat_get_tab_last_vacuum_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_last_vacuum_time' },
+  prosrc => 'pg_stat_get_tab_last_vacuum_time' },
 { oid => '2782', descr => 'statistics: last auto vacuum time for a table',
-  proname => 'pg_stat_get_last_autovacuum_time', provolatile => 's',
+  proname => 'pg_stat_get_tab_last_autovacuum_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_last_autovacuum_time' },
+  prosrc => 'pg_stat_get_tab_last_autovacuum_time' },
 { oid => '2783', descr => 'statistics: last manual analyze time for a table',
-  proname => 'pg_stat_get_last_analyze_time', provolatile => 's',
+  proname => 'pg_stat_get_tab_last_analyze_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_last_analyze_time' },
+  prosrc => 'pg_stat_get_tab_last_analyze_time' },
 { oid => '2784', descr => 'statistics: last auto analyze time for a table',
-  proname => 'pg_stat_get_last_autoanalyze_time', provolatile => 's',
+  proname => 'pg_stat_get_tab_last_autoanalyze_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_last_autoanalyze_time' },
+  prosrc => 'pg_stat_get_tab_last_autoanalyze_time' },
 { oid => '3054', descr => 'statistics: number of manual vacuums for a table',
-  proname => 'pg_stat_get_vacuum_count', provolatile => 's', proparallel => 'r',
+  proname => 'pg_stat_get_tab_vacuum_count', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_vacuum_count' },
+  prosrc => 'pg_stat_get_tab_vacuum_count' },
 { oid => '3055', descr => 'statistics: number of auto vacuums for a table',
-  proname => 'pg_stat_get_autovacuum_count', provolatile => 's',
+  proname => 'pg_stat_get_tab_autovacuum_count', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_autovacuum_count' },
+  prosrc => 'pg_stat_get_tab_autovacuum_count' },
 { oid => '3056', descr => 'statistics: number of manual analyzes for a table',
-  proname => 'pg_stat_get_analyze_count', provolatile => 's',
+  proname => 'pg_stat_get_tab_analyze_count', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_analyze_count' },
+  prosrc => 'pg_stat_get_tab_analyze_count' },
 { oid => '3057', descr => 'statistics: number of auto analyzes for a table',
-  proname => 'pg_stat_get_autoanalyze_count', provolatile => 's',
+  proname => 'pg_stat_get_tab_autoanalyze_count', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_autoanalyze_count' },
+  prosrc => 'pg_stat_get_tab_autoanalyze_count' },
 { oid => '1936', descr => 'statistics: currently active backend IDs',
   proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't',
   provolatile => 's', proparallel => 'r', prorettype => 'int4',
@@ -5719,10 +5743,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_tab_xact_numscans', provolatile => 'v',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tab_xact_numscans' },
+{ oid => '8892',
+  descr => 'statistics: number of scans done for index in current transaction',
+  proname => 'pg_stat_get_ind_xact_numscans', provolatile => 'v',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_xact_numscans' },
+  prosrc => 'pg_stat_get_ind_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 bc6349727b..57f5c8657a 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 autoanalyze_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;
@@ -494,16 +541,15 @@ extern PgStat_BackendFunctionEntry *find_funcstat_entry(Oid func_id);
 
 
 /*
- * Functions in pgstat_relation.c
+ * Functions in pgstat_table.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_drop_table(Relation rel);
+extern void pgstat_init_table(Relation rel);
 
-extern void pgstat_init_relation(Relation rel);
-extern void pgstat_assoc_relation(Relation rel);
-extern void pgstat_unlink_relation(Relation rel);
+extern void pgstat_assoc_table(Relation rel);
+extern void pgstat_unlink_table(Relation rel);
 
 extern void pgstat_report_vacuum(Oid tableoid, bool shared,
 								 PgStat_Counter livetuples, PgStat_Counter deadtuples);
@@ -511,53 +557,83 @@ extern void pgstat_report_analyze(Relation rel,
 								  PgStat_Counter livetuples, PgStat_Counter deadtuples,
 								  bool resetcounter);
 
+/*
+ * Functions in pgstat_index.c
+ */
+
+extern void pgstat_create_index(Relation rel);
+extern void pgstat_drop_index(Relation rel);
+extern void pgstat_copy_index_stats(Relation dst, Relation src);
+extern void pgstat_init_index(Relation rel);
+extern void pgstat_assoc_index(Relation rel);
+extern void pgstat_unlink_index(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_table()
+ * and pgstat_init_index().
  */
-#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 +649,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..004e5412ab 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
 {
@@ -550,16 +556,23 @@ extern bool pgstat_function_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
 
 
 /*
- * Functions in pgstat_relation.c
+ * Functions in pgstat_table.c
  */
 
-extern void AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit);
-extern void AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth);
-extern void AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state);
-extern void PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state);
+extern void AtEOXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit);
+extern void AtEOSubXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth);
+extern void AtPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state);
+extern void PostPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state);
+
+extern bool pgstat_table_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+extern void pgstat_table_delete_pending_cb(PgStat_EntryRef *entry_ref);
+
+/*
+ * Functions in pgstat_index.c
+ */
 
-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_index_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+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..494adee88b 100644
--- a/src/test/isolation/expected/stats.out
+++ b/src/test/isolation/expected/stats.out
@@ -2166,14 +2166,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2210,14 +2210,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2236,14 +2236,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2275,14 +2275,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2314,14 +2314,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2369,14 +2369,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2401,14 +2401,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2463,14 +2463,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2495,14 +2495,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2558,14 +2558,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2627,14 +2627,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2688,14 +2688,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2755,14 +2755,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2795,14 +2795,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2841,14 +2841,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2881,14 +2881,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2927,14 +2927,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2968,14 +2968,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -3015,14 +3015,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
diff --git a/src/test/isolation/expected/stats_1.out b/src/test/isolation/expected/stats_1.out
index 3854320106..7981747732 100644
--- a/src/test/isolation/expected/stats_1.out
+++ b/src/test/isolation/expected/stats_1.out
@@ -2174,14 +2174,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2218,14 +2218,14 @@ 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_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2244,14 +2244,14 @@ 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_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2283,14 +2283,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2322,14 +2322,14 @@ 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_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2377,14 +2377,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2409,14 +2409,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2471,14 +2471,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2503,14 +2503,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2566,14 +2566,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2635,14 +2635,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2698,14 +2698,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2767,14 +2767,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2809,14 +2809,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2857,14 +2857,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2899,14 +2899,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2947,14 +2947,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2990,14 +2990,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -3039,14 +3039,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
diff --git a/src/test/isolation/specs/stats.spec b/src/test/isolation/specs/stats.spec
index 5b922d788c..53d78f7db1 100644
--- a/src/test/isolation/specs/stats.spec
+++ b/src/test/isolation/specs/stats.spec
@@ -93,14 +93,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 }
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 532ea36990..c2b9f41bf2 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1767,10 +1767,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_ind_numscans(i.oid) AS idx_scan,
+    pg_stat_get_ind_lastscan(i.oid) AS last_idx_scan,
+    pg_stat_get_ind_tuples_returned(i.oid) AS idx_tup_read,
+    pg_stat_get_ind_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)))
@@ -1779,28 +1779,28 @@ 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_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,
-    pg_stat_get_tuples_hot_updated(c.oid) AS n_tup_hot_upd,
-    pg_stat_get_live_tuples(c.oid) AS n_live_tup,
-    pg_stat_get_dead_tuples(c.oid) AS n_dead_tup,
-    pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze,
-    pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum,
-    pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum,
-    pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum,
-    pg_stat_get_last_analyze_time(c.oid) AS last_analyze,
-    pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze,
-    pg_stat_get_vacuum_count(c.oid) AS vacuum_count,
-    pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count,
-    pg_stat_get_analyze_count(c.oid) AS analyze_count,
-    pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count
+    pg_stat_get_tab_numscans(c.oid) AS seq_scan,
+    pg_stat_get_tab_lastscan(c.oid) AS last_seq_scan,
+    pg_stat_get_tab_tuples_returned(c.oid) AS seq_tup_read,
+    (sum(pg_stat_get_ind_numscans(i.indexrelid)))::bigint AS idx_scan,
+    max(pg_stat_get_ind_lastscan(i.indexrelid)) AS last_idx_scan,
+    ((sum(pg_stat_get_ind_tuples_fetched(i.indexrelid)))::bigint + pg_stat_get_tab_tuples_fetched(c.oid)) AS idx_tup_fetch,
+    pg_stat_get_tab_tuples_inserted(c.oid) AS n_tup_ins,
+    pg_stat_get_tab_tuples_updated(c.oid) AS n_tup_upd,
+    pg_stat_get_tab_tuples_deleted(c.oid) AS n_tup_del,
+    pg_stat_get_tab_tuples_hot_updated(c.oid) AS n_tup_hot_upd,
+    pg_stat_get_tab_live_tuples(c.oid) AS n_live_tup,
+    pg_stat_get_tab_dead_tuples(c.oid) AS n_dead_tup,
+    pg_stat_get_tab_mod_since_analyze(c.oid) AS n_mod_since_analyze,
+    pg_stat_get_tab_ins_since_vacuum(c.oid) AS n_ins_since_vacuum,
+    pg_stat_get_tab_last_vacuum_time(c.oid) AS last_vacuum,
+    pg_stat_get_tab_last_autovacuum_time(c.oid) AS last_autovacuum,
+    pg_stat_get_tab_last_analyze_time(c.oid) AS last_analyze,
+    pg_stat_get_tab_last_autoanalyze_time(c.oid) AS last_autoanalyze,
+    pg_stat_get_tab_vacuum_count(c.oid) AS vacuum_count,
+    pg_stat_get_tab_autovacuum_count(c.oid) AS autovacuum_count,
+    pg_stat_get_tab_analyze_count(c.oid) AS analyze_count,
+    pg_stat_get_tab_autoanalyze_count(c.oid) AS autoanalyze_count
    FROM ((pg_class c
      LEFT JOIN pg_index i ON ((c.oid = i.indrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
@@ -2224,9 +2224,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_tab_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_ind_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,
@@ -2277,8 +2277,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_ind_blocks_fetched(i.oid) - pg_stat_get_ind_blocks_hit(i.oid)) AS idx_blks_read,
+    pg_stat_get_ind_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)))
@@ -2287,31 +2287,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_ind_blocks_fetched(c.oid) - pg_stat_get_tab_blocks_hit(c.oid)) AS blks_read,
+    pg_stat_get_tab_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_tab_blocks_fetched(c.oid) - pg_stat_get_tab_blocks_hit(c.oid)) AS heap_blks_read,
+    pg_stat_get_tab_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_tab_blocks_fetched(t.oid) - pg_stat_get_tab_blocks_hit(t.oid)) AS toast_blks_read,
+    pg_stat_get_tab_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_ind_blocks_fetched(pg_index.indexrelid) - pg_stat_get_ind_blocks_hit(pg_index.indexrelid))))::bigint AS idx_blks_read,
+            (sum(pg_stat_get_ind_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_ind_blocks_fetched(pg_index.indexrelid) - pg_stat_get_ind_blocks_hit(pg_index.indexrelid))))::bigint AS idx_blks_read,
+            (sum(pg_stat_get_ind_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 1d84407a03..d5ee0f4845 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;$$;
@@ -357,17 +468,17 @@ SELECT pg_stat_force_next_flush();
  
 (1 row)
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           1
 (1 row)
 
 DROP TABLE drop_stats_test;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       0
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           0
 (1 row)
 
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_oid);
@@ -378,18 +489,18 @@ SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_oid);
 
 -- check that rollback protects against having stats dropped and that local
 -- modifications don't pose a problem
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
-(1 row)
-
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_tuples_inserted 
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_live_tuples 
 -----------------------------
                            1
 (1 row)
 
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_tuples_inserted 
+---------------------------------
+                               1
+(1 row)
+
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
  pg_stat_get_xact_tuples_inserted 
 ----------------------------------
@@ -418,29 +529,29 @@ SELECT pg_stat_force_next_flush();
  
 (1 row)
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           1
 (1 row)
 
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_tuples_inserted 
------------------------------
-                           2
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_tuples_inserted 
+---------------------------------
+                               2
 (1 row)
 
 -- transactional drop
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           1
 (1 row)
 
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_tuples_inserted 
------------------------------
-                           2
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_tuples_inserted 
+---------------------------------
+                               2
 (1 row)
 
 BEGIN;
@@ -465,23 +576,23 @@ SELECT pg_stat_force_next_flush();
  
 (1 row)
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       0
-(1 row)
-
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_tuples_inserted 
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_live_tuples 
 -----------------------------
                            0
 (1 row)
 
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_tuples_inserted 
+---------------------------------
+                               0
+(1 row)
+
 -- savepoint rollback (2 levels)
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           1
 (1 row)
 
 BEGIN;
@@ -510,17 +621,17 @@ SELECT pg_stat_force_next_flush();
  
 (1 row)
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       3
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           3
 (1 row)
 
 -- savepoint rolback (1 level)
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       3
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           3
 (1 row)
 
 BEGIN;
@@ -529,17 +640,17 @@ DROP TABLE drop_stats_test_subxact;
 SAVEPOINT sp2;
 ROLLBACK TO SAVEPOINT sp1;
 COMMIT;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       3
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           3
 (1 row)
 
 -- and now actually drop
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       3
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           3
 (1 row)
 
 BEGIN;
@@ -548,10 +659,10 @@ DROP TABLE drop_stats_test_subxact;
 SAVEPOINT sp2;
 RELEASE SAVEPOINT sp1;
 COMMIT;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       0
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           0
 (1 row)
 
 DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
@@ -1021,21 +1132,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
@@ -1051,14 +1162,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
@@ -1073,7 +1184,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
@@ -1081,7 +1192,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
@@ -1089,7 +1200,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
@@ -1097,7 +1208,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
@@ -1105,7 +1216,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 b4d6753c71..1e9ae74b2a 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
 ---
@@ -218,15 +289,15 @@ SELECT 'drop_stats_test_subxact'::regclass::oid AS drop_stats_test_subxact_oid \
 
 SELECT pg_stat_force_next_flush();
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_oid);
 DROP TABLE drop_stats_test;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_oid);
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_oid);
 
 -- check that rollback protects against having stats dropped and that local
 -- modifications don't pose a problem
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
 BEGIN;
 INSERT INTO drop_stats_test_xact DEFAULT VALUES;
@@ -235,12 +306,12 @@ DROP TABLE drop_stats_test_xact;
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
 ROLLBACK;
 SELECT pg_stat_force_next_flush();
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
 
 -- transactional drop
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
 BEGIN;
 INSERT INTO drop_stats_test_xact DEFAULT VALUES;
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
@@ -248,11 +319,11 @@ DROP TABLE drop_stats_test_xact;
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
 COMMIT;
 SELECT pg_stat_force_next_flush();
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
 
 -- savepoint rollback (2 levels)
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 BEGIN;
 INSERT INTO drop_stats_test_subxact DEFAULT VALUES;
 SAVEPOINT sp1;
@@ -264,27 +335,27 @@ ROLLBACK TO SAVEPOINT sp2;
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_subxact_oid);
 COMMIT;
 SELECT pg_stat_force_next_flush();
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 
 -- savepoint rolback (1 level)
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 BEGIN;
 SAVEPOINT sp1;
 DROP TABLE drop_stats_test_subxact;
 SAVEPOINT sp2;
 ROLLBACK TO SAVEPOINT sp1;
 COMMIT;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 
 -- and now actually drop
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 BEGIN;
 SAVEPOINT sp1;
 DROP TABLE drop_stats_test_subxact;
 SAVEPOINT sp2;
 RELEASE SAVEPOINT sp1;
 COMMIT;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 
 DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
 DROP TABLE prevstats;
@@ -493,40 +564,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 58daeca831..f9009263bc 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2008,10 +2008,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
@@ -2025,6 +2026,7 @@ PgStat_FetchConsistency
 PgStat_FunctionCallUsage
 PgStat_FunctionCounts
 PgStat_HashKey
+PgStat_IndexStatus
 PgStat_Kind
 PgStat_KindInfo
 PgStat_LocalState
#15Drouvot, Bertrand
bertranddrouvot.pg@gmail.com
In reply to: Drouvot, Bertrand (#14)
1 attachment(s)
Re: Split index and table statistics into different types of stats

Hi,

Hi,

As [1] mentioned above has been committed (83a1a1b566), please find attached V5 related to this thread making use of the new macros (namely PG_STAT_GET_RELENTRY_INT64 and PG_STAT_GET_RELENTRY_TIMESTAMPTZ).

I switched from using "CppConcat" to using "##", as it looks to me it's easier to read now that we are adding another concatenation to the game (due to the table/index split).

The (Tab,tab) or (Ind,ind) passed as arguments to the macros look "weird" (I don't have a better idea yet): purpose is to follow the naming convention for PgStat_StatTabEntry/PgStat_StatIndEntry and pgstat_fetch_stat_tabentry/pgstat_fetch_stat_indentry, thoughts?

Looking forward to your feedback,

Attaching V6 (mandatory rebase due to 8018ffbf58).

While at it, I got rid of the weirdness mentioned above by creating 2 sets of macros (one for the tables and one for the indexes).

Looking forward to your feedback,

Regards,

--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

Attachments:

v6-0001-split_tables_indexes_stats.patchtext/plain; charset=UTF-8; name=v6-0001-split_tables_indexes_stats.patchDownload
diff --git a/src/backend/access/common/relation.c b/src/backend/access/common/relation.c
index 382a42ff7d..259bd06aed 100644
--- a/src/backend/access/common/relation.c
+++ b/src/backend/access/common/relation.c
@@ -73,7 +73,10 @@ relation_open(Oid relationId, LOCKMODE lockmode)
 	if (RelationUsesLocalBuffers(r))
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
-	pgstat_init_relation(r);
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_init_index(r);
+	else
+		pgstat_init_table(r);
 
 	return r;
 }
@@ -123,7 +126,10 @@ try_relation_open(Oid relationId, LOCKMODE lockmode)
 	if (RelationUsesLocalBuffers(r))
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
-	pgstat_init_relation(r);
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_init_index(r);
+	else
+		pgstat_init_table(r);
 
 	return r;
 }
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index dc303995e5..ceebc79302 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -590,7 +590,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 bdd413f01b..d5b71fdd1e 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..a612ae81ed 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -655,29 +655,29 @@ 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_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,
-            pg_stat_get_tuples_hot_updated(C.oid) AS n_tup_hot_upd,
-            pg_stat_get_live_tuples(C.oid) AS n_live_tup,
-            pg_stat_get_dead_tuples(C.oid) AS n_dead_tup,
-            pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze,
-            pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum,
-            pg_stat_get_last_vacuum_time(C.oid) as last_vacuum,
-            pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum,
-            pg_stat_get_last_analyze_time(C.oid) as last_analyze,
-            pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze,
-            pg_stat_get_vacuum_count(C.oid) AS vacuum_count,
-            pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count,
-            pg_stat_get_analyze_count(C.oid) AS analyze_count,
-            pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count
+            pg_stat_get_tab_numscans(C.oid) AS seq_scan,
+            pg_stat_get_tab_lastscan(C.oid) AS last_seq_scan,
+            pg_stat_get_tab_tuples_returned(C.oid) AS seq_tup_read,
+            sum(pg_stat_get_ind_numscans(I.indexrelid))::bigint AS idx_scan,
+            max(pg_stat_get_ind_lastscan(I.indexrelid)) AS last_idx_scan,
+            sum(pg_stat_get_ind_tuples_fetched(I.indexrelid))::bigint +
+            pg_stat_get_tab_tuples_fetched(C.oid) AS idx_tup_fetch,
+            pg_stat_get_tab_tuples_inserted(C.oid) AS n_tup_ins,
+            pg_stat_get_tab_tuples_updated(C.oid) AS n_tup_upd,
+            pg_stat_get_tab_tuples_deleted(C.oid) AS n_tup_del,
+            pg_stat_get_tab_tuples_hot_updated(C.oid) AS n_tup_hot_upd,
+            pg_stat_get_tab_live_tuples(C.oid) AS n_live_tup,
+            pg_stat_get_tab_dead_tuples(C.oid) AS n_dead_tup,
+            pg_stat_get_tab_mod_since_analyze(C.oid) AS n_mod_since_analyze,
+            pg_stat_get_tab_ins_since_vacuum(C.oid) AS n_ins_since_vacuum,
+            pg_stat_get_tab_last_vacuum_time(C.oid) as last_vacuum,
+            pg_stat_get_tab_last_autovacuum_time(C.oid) as last_autovacuum,
+            pg_stat_get_tab_last_analyze_time(C.oid) as last_analyze,
+            pg_stat_get_tab_last_autoanalyze_time(C.oid) as last_autoanalyze,
+            pg_stat_get_tab_vacuum_count(C.oid) AS vacuum_count,
+            pg_stat_get_tab_autovacuum_count(C.oid) AS autovacuum_count,
+            pg_stat_get_tab_analyze_count(C.oid) AS analyze_count,
+            pg_stat_get_tab_autoanalyze_count(C.oid) AS autoanalyze_count
     FROM pg_class C LEFT JOIN
          pg_index I ON C.oid = I.indrelid
          LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
@@ -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_tab_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_ind_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_tab_blocks_fetched(C.oid) -
+                    pg_stat_get_tab_blocks_hit(C.oid) AS heap_blks_read,
+            pg_stat_get_tab_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_tab_blocks_fetched(T.oid) -
+                    pg_stat_get_tab_blocks_hit(T.oid) AS toast_blks_read,
+            pg_stat_get_tab_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_ind_blocks_fetched(indexrelid) -
+                         pg_stat_get_ind_blocks_hit(indexrelid))::bigint
                      AS idx_blks_read,
-                     sum(pg_stat_get_blocks_hit(indexrelid))::bigint
+                     sum(pg_stat_get_ind_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_ind_blocks_fetched(indexrelid) -
+                         pg_stat_get_ind_blocks_hit(indexrelid))::bigint
                      AS idx_blks_read,
-                     sum(pg_stat_get_blocks_hit(indexrelid))::bigint
+                     sum(pg_stat_get_ind_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_ind_numscans(I.oid) AS idx_scan,
+            pg_stat_get_ind_lastscan(I.oid) AS last_idx_scan,
+            pg_stat_get_ind_tuples_returned(I.oid) AS idx_tup_read,
+            pg_stat_get_ind_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_ind_blocks_fetched(I.oid) -
+                    pg_stat_get_ind_blocks_hit(I.oid) AS idx_blks_read,
+            pg_stat_get_ind_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_ind_blocks_fetched(C.oid) -
+                    pg_stat_get_tab_blocks_hit(C.oid) AS blks_read,
+            pg_stat_get_tab_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/Makefile b/src/backend/utils/activity/Makefile
index a2e8507fd6..7441f4ce85 100644
--- a/src/backend/utils/activity/Makefile
+++ b/src/backend/utils/activity/Makefile
@@ -22,11 +22,12 @@ OBJS = \
 	pgstat_checkpointer.o \
 	pgstat_database.o \
 	pgstat_function.o \
-	pgstat_relation.o \
+	pgstat_index.o \
 	pgstat_replslot.o \
 	pgstat_shmem.o \
 	pgstat_slru.o \
 	pgstat_subscription.o \
+	pgstat_table.o \
 	pgstat_wal.o \
 	pgstat_xact.o \
 	wait_event.o
diff --git a/src/backend/utils/activity/meson.build b/src/backend/utils/activity/meson.build
index 5b3b558a67..e6bf3c59b6 100644
--- a/src/backend/utils/activity/meson.build
+++ b/src/backend/utils/activity/meson.build
@@ -7,11 +7,12 @@ backend_sources += files(
   'pgstat_checkpointer.c',
   'pgstat_database.c',
   'pgstat_function.c',
-  'pgstat_relation.c',
+  'pgstat_index.c',
   'pgstat_replslot.c',
   'pgstat_shmem.c',
   'pgstat_slru.c',
   'pgstat_subscription.c',
+  'pgstat_table.c',
   'pgstat_wal.c',
   'pgstat_xact.c',
   'wait_event.c',
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index 1ebe3bbf29..d49a5ea4bb 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -72,10 +72,11 @@
  * - pgstat_checkpointer.c
  * - pgstat_database.c
  * - pgstat_function.c
- * - pgstat_relation.c
+ * - pgstat_index.c
  * - pgstat_replslot.c
  * - pgstat_slru.c
  * - pgstat_subscription.c
+ * - pgstat_table.c
  * - pgstat_wal.c
  *
  * Whenever possible infrastructure files should not contain code related to
@@ -269,18 +270,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_index.c b/src/backend/utils/activity/pgstat_index.c
new file mode 100644
index 0000000000..820592c119
--- /dev/null
+++ b/src/backend/utils/activity/pgstat_index.c
@@ -0,0 +1,297 @@
+/* -------------------------------------------------------------------------
+ *
+ * pgstat_index.c
+ *	  Implementation of index statistics.
+ *
+ * This file contains the implementation of function index. It is kept
+ * separate from pgstat.c to enforce the line between the statistics access /
+ * storage implementation and the details about individual types of
+ * statistics.
+ *
+ * Copyright (c) 2001-2022, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/activity/pgstat_index.c
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/xact.h"
+#include "utils/pgstat_internal.h"
+#include "utils/rel.h"
+#include "catalog/catalog.h"
+
+static PgStat_IndexStatus *pgstat_prep_index_pending(Oid rel_id, bool isshared);
+
+/*
+ * Copy stats between indexes. This is used for things like REINDEX
+ * CONCURRENTLY.
+ */
+void
+pgstat_copy_index_stats(Relation dst, Relation src)
+{
+	PgStat_StatIndEntry *srcstats;
+	PgStatShared_Index *dstshstats;
+	PgStat_EntryRef *dst_ref;
+
+	srcstats = pgstat_fetch_stat_indentry_ext(src->rd_rel->relisshared,
+											  RelationGetRelid(src));
+	if (!srcstats)
+		return;
+
+	dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_INDEX,
+										  dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+										  RelationGetRelid(dst),
+										  false);
+
+	dstshstats = (PgStatShared_Index *) dst_ref->shared_stats;
+	dstshstats->stats = *srcstats;
+
+	pgstat_unlock_entry(dst_ref);
+}
+
+/*
+ * Initialize a relcache entry to count access statistics.  Called whenever an
+ * index is opened.
+ *
+ * We assume that a relcache entry's pgstatind_info field is 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
+ * pgstat_assoc_index().
+ */
+void
+pgstat_init_index(Relation rel)
+{
+	/*
+	 * We only count stats for indexes
+	 */
+	Assert(rel->rd_rel->relkind == RELKIND_INDEX);
+
+	if (!pgstat_track_counts)
+	{
+		if (rel->pgstatind_info != NULL)
+			pgstat_unlink_index(rel);
+
+		/* We're not counting at all */
+		rel->pgstat_enabled = false;
+		rel->pgstatind_info = NULL;
+		return;
+	}
+
+	rel->pgstat_enabled = true;
+}
+
+/*
+ * 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_index() 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->pgstatind_info->relation == NULL);
+
+	/* mark this relation as the owner */
+	rel->pgstatind_info->relation = rel;
+}
+
+/*
+ * Break the mutual link between a relcache entry and pending index stats entry.
+ * This must be called whenever one end of the link is removed.
+ */
+void
+pgstat_unlink_index(Relation rel)
+{
+
+	if (rel->pgstatind_info == NULL)
+		return;
+
+	/* 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 index stats are dropped if transaction aborts.
+ */
+void
+pgstat_create_index(Relation rel)
+{
+	pgstat_create_transactional(PGSTAT_KIND_INDEX,
+								rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+								RelationGetRelid(rel));
+}
+
+/*
+ * Ensure that index stats are dropped if transaction commits.
+ */
+void
+pgstat_drop_index(Relation rel)
+{
+	pgstat_drop_transactional(PGSTAT_KIND_INDEX,
+							  rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+							  RelationGetRelid(rel));
+}
+
+/*
+ * 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)
+{
+	return pgstat_fetch_stat_indentry_ext(IsSharedRelation(relid), relid);
+}
+
+/*
+ * 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);
+}
+
+/*
+ * 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;
+	return NULL;
+}
+
+/*
+ * 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->tuples_returned += lstats->i_counts.i_tuples_returned;
+	dbentry->tuples_fetched += lstats->i_counts.i_tuples_fetched;
+	dbentry->blocks_fetched += lstats->i_counts.i_blocks_fetched;
+	dbentry->blocks_hit += lstats->i_counts.i_blocks_hit;
+
+	return true;
+}
+
+void
+pgstat_index_delete_pending_cb(PgStat_EntryRef *entry_ref)
+{
+	PgStat_IndexStatus *pending = (PgStat_IndexStatus *) entry_ref->pending;
+
+	if (pending->relation)
+		pgstat_unlink_index(pending->relation);
+}
+
+/*
+ * 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;
+}
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_table.c
similarity index 75%
rename from src/backend/utils/activity/pgstat_relation.c
rename to src/backend/utils/activity/pgstat_table.c
index f9788c30ae..7e4e0134df 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_table.c
@@ -1,9 +1,9 @@
 /* -------------------------------------------------------------------------
  *
- * pgstat_relation.c
- *	  Implementation of relation statistics.
+ * pgstat_table.c
+ *	  Implementation of table statistics.
  *
- * This file contains the implementation of function relation. It is kept
+ * This file contains the implementation of function table. It is kept
  * separate from pgstat.c to enforce the line between the statistics access /
  * storage implementation and the details about individual types of
  * statistics.
@@ -11,7 +11,7 @@
  * Copyright (c) 2001-2022, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *	  src/backend/utils/activity/pgstat_relation.c
+ *	  src/backend/utils/activity/pgstat_table.c
  * -------------------------------------------------------------------------
  */
 
@@ -44,74 +44,48 @@ 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 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
- * CONCURRENTLY.
- */
-void
-pgstat_copy_relation_stats(Relation dst, Relation src)
-{
-	PgStat_StatTabEntry *srcstats;
-	PgStatShared_Relation *dstshstats;
-	PgStat_EntryRef *dst_ref;
-
-	srcstats = pgstat_fetch_stat_tabentry_ext(src->rd_rel->relisshared,
-											  RelationGetRelid(src));
-	if (!srcstats)
-		return;
-
-	dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
-										  dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
-										  RelationGetRelid(dst),
-										  false);
-
-	dstshstats = (PgStatShared_Relation *) dst_ref->shared_stats;
-	dstshstats->stats = *srcstats;
-
-	pgstat_unlock_entry(dst_ref);
-}
-
 /*
  * Initialize a relcache entry to count access statistics.  Called whenever a
- * relation is opened.
+ * table is opened.
  *
- * We assume that a relcache entry's pgstat_info field is zeroed by relcache.c
+ * We assume that a relcache entry's pgstattab_info field is 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
- * pgstat_assoc_relation().
+ * pgstat_assoc_table().
  */
 void
-pgstat_init_relation(Relation rel)
+pgstat_init_table(Relation rel)
 {
 	char		relkind = rel->rd_rel->relkind;
 
+	Assert(relkind != RELKIND_INDEX);
+
 	/*
 	 * We only count stats for relations with storage and partitioned tables
 	 */
 	if (!RELKIND_HAS_STORAGE(relkind) && relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		rel->pgstat_enabled = false;
-		rel->pgstat_info = NULL;
+		rel->pgstattab_info = NULL;
 		return;
 	}
 
 	if (!pgstat_track_counts)
 	{
-		if (rel->pgstat_info)
-			pgstat_unlink_relation(rel);
+		if (rel->pgstattab_info != NULL)
+			pgstat_unlink_table(rel);
 
 		/* We're not counting at all */
 		rel->pgstat_enabled = false;
-		rel->pgstat_info = NULL;
+		rel->pgstattab_info = NULL;
 		return;
 	}
 
@@ -119,89 +93,92 @@ 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.
  *
- * This is separate from pgstat_init_relation() as it is not uncommon for
+ * This is separate from pgstat_init_table() as it is not uncommon for
  * 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->pgstat_info->relation == NULL);
+	Assert(rel->pgstattab_info->relation == NULL);
 
 	/* mark this relation as the owner */
-	rel->pgstat_info->relation = rel;
+	rel->pgstattab_info->relation = rel;
 }
 
 /*
- * Break the mutual link between a relcache entry and pending stats entry.
+ * Break the mutual link between a relcache entry and pending table stats entry.
  * This must be called whenever one end of the link is removed.
  */
 void
-pgstat_unlink_relation(Relation rel)
+pgstat_unlink_table(Relation rel)
 {
-	/* remove the link to stats info if any */
-	if (rel->pgstat_info == NULL)
+
+	if (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;
+	}
 }
 
 /*
- * Ensure that stats are dropped if transaction aborts.
+ * Ensure that table stats are dropped if transaction aborts.
  */
 void
-pgstat_create_relation(Relation rel)
+pgstat_create_table(Relation rel)
 {
-	pgstat_create_transactional(PGSTAT_KIND_RELATION,
+	pgstat_create_transactional(PGSTAT_KIND_TABLE,
 								rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
 								RelationGetRelid(rel));
 }
 
 /*
- * Ensure that stats are dropped if transaction commits.
+ * Ensure that table stats are dropped if transaction commits.
  */
 void
-pgstat_drop_relation(Relation rel)
+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;
 	}
 }
 
@@ -213,7 +190,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;
@@ -225,10 +202,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->live_tuples = livetuples;
@@ -272,7 +249,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);
 
@@ -291,31 +268,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->live_tuples = livetuples;
@@ -349,12 +326,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;
 	}
 }
 
@@ -364,16 +341,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++;
 	}
 }
 
@@ -383,12 +360,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++;
 	}
 }
 
@@ -398,15 +375,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;
 	}
 }
 
@@ -421,11 +398,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;
 	}
 }
 
@@ -451,7 +428,7 @@ 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);
 }
 
 /*
@@ -467,9 +444,9 @@ 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;
@@ -477,7 +454,7 @@ find_tabstat_entry(Oid rel_id)
 }
 
 /*
- * Perform relation stats specific end-of-transaction work. Helper for
+ * Perform table stats specific end-of-transaction work. Helper for
  * AtEOXact_PgStat.
  *
  * Transfer transactional insert/update counts into the base tabstat entries.
@@ -485,7 +462,7 @@ find_tabstat_entry(Oid rel_id)
  * TopTransactionContext and will go away anyway.
  */
 void
-AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit)
+AtEOXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit)
 {
 	PgStat_TableXactStatus *trans;
 
@@ -536,14 +513,14 @@ AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit)
 }
 
 /*
- * Perform relation stats specific end-of-sub-transaction work. Helper for
+ * Perform table stats specific end-of-sub-transaction work. Helper for
  * AtEOSubXact_PgStat.
  *
  * Transfer transactional insert/update counts into the next higher
  * subtransaction state.
  */
 void
-AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
+AtEOSubXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
 {
 	PgStat_TableXactStatus *trans;
 	PgStat_TableXactStatus *next_trans;
@@ -620,11 +597,11 @@ AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, in
 }
 
 /*
- * Generate 2PC records for all the pending transaction-dependent relation
+ * Generate 2PC records for all the pending transaction-dependent table
  * stats.
  */
 void
-AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
+AtPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state)
 {
 	PgStat_TableXactStatus *trans;
 
@@ -659,10 +636,10 @@ AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
  * reported to the stats system immediately, while the effects on live and
  * dead tuple counts are preserved in the 2PC state file.
  *
- * Note: AtEOXact_PgStat_Relations is not called during PREPARE.
+ * Note: AtEOXact_PgStat_Tables is not called during PREPARE.
  */
 void
-PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
+PostPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state)
 {
 	PgStat_TableXactStatus *trans;
 
@@ -685,27 +662,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;
 }
@@ -721,10 +698,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)
@@ -733,10 +710,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;
 }
 
@@ -750,22 +727,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)
@@ -783,6 +759,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;
 	}
@@ -831,12 +808,12 @@ pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
 }
 
 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;
 
 	if (pending->relation)
-		pgstat_unlink_relation(pending->relation);
+		pgstat_unlink_table(pending->relation);
 }
 
 /*
@@ -844,12 +821,12 @@ pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref)
  * 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;
@@ -863,7 +840,7 @@ pgstat_prep_relation_pending(Oid rel_id, bool isshared)
  * 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;
@@ -879,24 +856,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/activity/pgstat_xact.c b/src/backend/utils/activity/pgstat_xact.c
index 5a3aca4aef..870f1b1103 100644
--- a/src/backend/utils/activity/pgstat_xact.c
+++ b/src/backend/utils/activity/pgstat_xact.c
@@ -51,7 +51,7 @@ AtEOXact_PgStat(bool isCommit, bool parallel)
 		Assert(xact_state->nest_level == 1);
 		Assert(xact_state->prev == NULL);
 
-		AtEOXact_PgStat_Relations(xact_state, isCommit);
+		AtEOXact_PgStat_Tables(xact_state, isCommit);
 		AtEOXact_PgStat_DroppedStats(xact_state, isCommit);
 	}
 	pgStatXactStack = NULL;
@@ -122,7 +122,7 @@ AtEOSubXact_PgStat(bool isCommit, int nestDepth)
 		/* delink xact_state from stack immediately to simplify reuse case */
 		pgStatXactStack = xact_state->prev;
 
-		AtEOSubXact_PgStat_Relations(xact_state, isCommit, nestDepth);
+		AtEOSubXact_PgStat_Tables(xact_state, isCommit, nestDepth);
 		AtEOSubXact_PgStat_DroppedStats(xact_state, isCommit, nestDepth);
 
 		pfree(xact_state);
@@ -197,7 +197,7 @@ AtPrepare_PgStat(void)
 		Assert(xact_state->nest_level == 1);
 		Assert(xact_state->prev == NULL);
 
-		AtPrepare_PgStat_Relations(xact_state);
+		AtPrepare_PgStat_Tables(xact_state);
 	}
 }
 
@@ -221,7 +221,7 @@ PostPrepare_PgStat(void)
 		Assert(xact_state->nest_level == 1);
 		Assert(xact_state->prev == NULL);
 
-		PostPrepare_PgStat_Relations(xact_state);
+		PostPrepare_PgStat_Tables(xact_state);
 	}
 	pgStatXactStack = NULL;
 
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 25a159b5e5..31b3c1eb5d 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -36,106 +36,159 @@
 
 #define HAS_PGSTAT_PERMISSIONS(role)	 (has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS) || has_privs_of_role(GetUserId(), role))
 
-#define PG_STAT_GET_RELENTRY_INT64(stat)						\
-Datum															\
-CppConcat(pg_stat_get_,stat)(PG_FUNCTION_ARGS)					\
-{																\
-	Oid			relid = PG_GETARG_OID(0);						\
-	int64		result;											\
-	PgStat_StatTabEntry *tabentry;								\
-																\
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)	\
-		result = 0;												\
-	else														\
-		result = (int64) (tabentry->stat);						\
-																\
-	PG_RETURN_INT64(result);									\
-}																\
-
-/* pg_stat_get_analyze_count */
-PG_STAT_GET_RELENTRY_INT64(analyze_count);
-
-/* pg_stat_get_autoanalyze_count */
-PG_STAT_GET_RELENTRY_INT64(autoanalyze_count);
-
-/* pg_stat_get_autovacuum_count */
-PG_STAT_GET_RELENTRY_INT64(autovacuum_count);
-
-/* pg_stat_get_blocks_fetched */
-PG_STAT_GET_RELENTRY_INT64(blocks_fetched);
-
-/* pg_stat_get_blocks_hit */
-PG_STAT_GET_RELENTRY_INT64(blocks_hit);
-
-/* pg_stat_get_dead_tuples */
-PG_STAT_GET_RELENTRY_INT64(dead_tuples);
-
-/* pg_stat_get_ins_since_vacuum */
-PG_STAT_GET_RELENTRY_INT64(ins_since_vacuum);
-
-/* pg_stat_get_live_tuples */
-PG_STAT_GET_RELENTRY_INT64(live_tuples);
-
-/* pg_stat_get_mods_since_analyze */
-PG_STAT_GET_RELENTRY_INT64(mod_since_analyze);
-
-/* pg_stat_get_numscans */
-PG_STAT_GET_RELENTRY_INT64(numscans);
-
-/* pg_stat_get_tuples_deleted */
-PG_STAT_GET_RELENTRY_INT64(tuples_deleted);
-
-/* pg_stat_get_tuples_fetched */
-PG_STAT_GET_RELENTRY_INT64(tuples_fetched);
-
-/* pg_stat_get_tuples_hot_updated */
-PG_STAT_GET_RELENTRY_INT64(tuples_hot_updated);
-
-/* pg_stat_get_tuples_inserted */
-PG_STAT_GET_RELENTRY_INT64(tuples_inserted);
-
-/* pg_stat_get_tuples_returned */
-PG_STAT_GET_RELENTRY_INT64(tuples_returned);
-
-/* pg_stat_get_tuples_updated */
-PG_STAT_GET_RELENTRY_INT64(tuples_updated);
-
-/* pg_stat_get_vacuum_count */
-PG_STAT_GET_RELENTRY_INT64(vacuum_count);
-
-#define PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat)					\
-Datum															\
-CppConcat(pg_stat_get_,stat)(PG_FUNCTION_ARGS)					\
-{																\
-	Oid			relid = PG_GETARG_OID(0);						\
-	TimestampTz result;											\
-	PgStat_StatTabEntry *tabentry;								\
-																\
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)	\
-		result = 0;												\
-	else														\
-		result = tabentry->stat;								\
-																\
-	if (result == 0)											\
-		PG_RETURN_NULL();										\
-	else														\
-		PG_RETURN_TIMESTAMPTZ(result);							\
-}																\
-
-/* pg_stat_get_last_analyze_time */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_analyze_time);
-
-/* pg_stat_get_last_autoanalyze_time */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_autoanalyze_time);
-
-/* pg_stat_get_last_autovacuum_time */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_autovacuum_time);
-
-/* pg_stat_get_last_vacuum_time */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time);
-
-/* pg_stat_get_lastscan */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan);
+#define PG_STAT_GET_TABENTRY_INT64(stat)							\
+Datum																\
+CppConcat(pg_stat_get_tab_,stat)(PG_FUNCTION_ARGS)					\
+{																	\
+	Oid			relid = PG_GETARG_OID(0);							\
+	int64		result;												\
+	PgStat_StatTabEntry *tabentry;									\
+																	\
+	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)		\
+		result = 0;													\
+	else															\
+		result = (int64) (tabentry->stat);							\
+																	\
+	PG_RETURN_INT64(result);										\
+}																	\
+
+/* pg_stat_get_tab_analyze_count */
+PG_STAT_GET_TABENTRY_INT64(analyze_count);
+
+/* pg_stat_get_tab_autoanalyze_count */
+PG_STAT_GET_TABENTRY_INT64(autoanalyze_count);
+
+/* pg_stat_get_tab_autovacuum_count */
+PG_STAT_GET_TABENTRY_INT64(autovacuum_count);
+
+/* pg_stat_get_tab_blocks_fetched */
+PG_STAT_GET_TABENTRY_INT64(blocks_fetched);
+
+/* pg_stat_get_tab_blocks_hit */
+PG_STAT_GET_TABENTRY_INT64(blocks_hit);
+
+/* pg_stat_get_tab_dead_tuples */
+PG_STAT_GET_TABENTRY_INT64(dead_tuples);
+
+/* pg_stat_get_tab_ins_since_vacuum */
+PG_STAT_GET_TABENTRY_INT64(ins_since_vacuum);
+
+/* pg_stat_get_tab_live_tuples */
+PG_STAT_GET_TABENTRY_INT64(live_tuples);
+
+/* pg_stat_get_mod_since_analyze */
+PG_STAT_GET_TABENTRY_INT64(mod_since_analyze);
+
+/* pg_stat_get_tab_numscans */
+PG_STAT_GET_TABENTRY_INT64(numscans);
+
+/* pg_stat_get_tab_tuples_deleted */
+PG_STAT_GET_TABENTRY_INT64(tuples_deleted);
+
+/* pg_stat_get_tab_tuples_fetched */
+PG_STAT_GET_TABENTRY_INT64(tuples_fetched);
+
+/* pg_stat_get_tab_tuples_hot_updated */
+PG_STAT_GET_TABENTRY_INT64(tuples_hot_updated);
+
+/* pg_stat_get_tab_tuples_inserted */
+PG_STAT_GET_TABENTRY_INT64(tuples_inserted);
+
+/* pg_stat_get_tab_tuples_returned */
+PG_STAT_GET_TABENTRY_INT64(tuples_returned);
+
+/* pg_stat_get_tab_tuples_updated */
+PG_STAT_GET_TABENTRY_INT64(tuples_updated);
+
+/* pg_stat_get_tab_vacuum_count */
+PG_STAT_GET_TABENTRY_INT64(vacuum_count);
+
+#define PG_STAT_GET_INDENTRY_INT64(stat)							\
+Datum																\
+CppConcat(pg_stat_get_ind_,stat)(PG_FUNCTION_ARGS)					\
+{																	\
+	Oid			relid = PG_GETARG_OID(0);							\
+	int64		result;												\
+	PgStat_StatIndEntry *indentry;									\
+																	\
+	if ((indentry = pgstat_fetch_stat_indentry(relid)) == NULL)		\
+		result = 0;													\
+	else															\
+		result = (int64) (indentry->stat);							\
+																	\
+	PG_RETURN_INT64(result);										\
+}																	\
+
+/* pg_stat_get_ind_numscans */
+PG_STAT_GET_INDENTRY_INT64(numscans);
+
+/* pg_stat_get_ind_tuples_returned */
+PG_STAT_GET_INDENTRY_INT64(tuples_returned);
+
+/* pg_stat_get_ind_tuples_fetched */
+PG_STAT_GET_INDENTRY_INT64(tuples_fetched);
+
+/* pg_stat_get_ind_blocks_fetched */
+PG_STAT_GET_INDENTRY_INT64(blocks_fetched);
+
+/* pg_stat_get_ind_blocks_hit */
+PG_STAT_GET_INDENTRY_INT64(blocks_hit);
+
+#define PG_STAT_GET_TABENTRY_TIMESTAMPTZ(stat)						\
+Datum																\
+CppConcat(pg_stat_get_tab_,stat)(PG_FUNCTION_ARGS)					\
+{																	\
+	Oid			relid = PG_GETARG_OID(0);							\
+	TimestampTz result;												\
+	PgStat_StatTabEntry *tabentry;									\
+																	\
+	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)		\
+		result = 0;													\
+	else															\
+		result = tabentry->stat;									\
+																	\
+	if (result == 0)												\
+		PG_RETURN_NULL();											\
+	else															\
+		PG_RETURN_TIMESTAMPTZ(result);								\
+}																	\
+
+/* pg_stat_get_tab_last_analyze_time */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(last_analyze_time);
+
+/* pg_stat_get_tab_last_autoanalyze_time */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(last_autoanalyze_time);
+
+/* pg_stat_get_tab_last_autovacuum_time */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(last_autovacuum_time);
+
+/* pg_stat_get_tab_last_vacuum_time */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(last_vacuum_time);
+
+/* pg_stat_get_tab_lastscan */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(lastscan);
+
+#define PG_STAT_GET_INDENTRY_TIMESTAMPTZ(stat)						\
+Datum																\
+CppConcat(pg_stat_get_ind_,stat)(PG_FUNCTION_ARGS)					\
+{																	\
+	Oid			relid = PG_GETARG_OID(0);							\
+	TimestampTz result;												\
+	PgStat_StatIndEntry *indentry;									\
+																	\
+	if ((indentry = pgstat_fetch_stat_indentry(relid)) == NULL)		\
+		result = 0;													\
+	else															\
+		result = indentry->stat;									\
+																	\
+	if (result == 0)												\
+		PG_RETURN_NULL();											\
+	else															\
+		PG_RETURN_TIMESTAMPTZ(result);								\
+}																	\
+
+/* pg_stat_get_ind_lastscan */
+PG_STAT_GET_INDENTRY_TIMESTAMPTZ(lastscan);
 
 Datum
 pg_stat_get_function_calls(PG_FUNCTION_ARGS)
@@ -1308,7 +1361,7 @@ pg_stat_get_slru(PG_FUNCTION_ARGS)
 }
 
 Datum
-pg_stat_get_xact_numscans(PG_FUNCTION_ARGS)
+pg_stat_get_tab_xact_numscans(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
@@ -1322,17 +1375,32 @@ pg_stat_get_xact_numscans(PG_FUNCTION_ARGS)
 	PG_RETURN_INT64(result);
 }
 
+Datum
+pg_stat_get_ind_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);
 }
@@ -1342,12 +1410,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);
 }
@@ -1435,12 +1503,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);
 }
@@ -1450,12 +1518,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);
 }
@@ -1574,7 +1642,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 450e5124a5..f8e47f0f9d 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -2411,7 +2411,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 	RelationCloseSmgr(relation);
 
 	/* break mutual link with stats entry */
-	pgstat_unlink_relation(relation);
+	if (relation->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_unlink_index(relation);
+	else
+		pgstat_unlink_table(relation);
 
 	/*
 	 * Free all the subsidiary data structures of the relcache entry, then the
@@ -2727,8 +2730,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)
@@ -6321,7 +6325,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 f9301b2627..c3fe4af669 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5274,96 +5274,120 @@
   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_tab_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_tab_numscans' },
+{ oid => '8296', descr => 'statistics: number of scans done for index',
+  proname => 'pg_stat_get_ind_numscans', provolatile => 's', proparallel => 'r',
+  prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_ind_numscans' },
+{ oid => '9976', descr => 'statistics: time of the last scan for table',
+  proname => 'pg_stat_get_tab_lastscan', provolatile => 's', proparallel => 'r',
+  prorettype => 'timestamptz', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tab_lastscan' },
+{ oid => '8626', descr => 'statistics: time of the last scan for index',
+  proname => 'pg_stat_get_ind_lastscan', provolatile => 's', proparallel => 'r',
   prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_lastscan' },
+  prosrc => 'pg_stat_get_ind_lastscan' },
 { oid => '1929', descr => 'statistics: number of tuples read by seqscan',
-  proname => 'pg_stat_get_tuples_returned', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_returned', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_returned' },
+  prosrc => 'pg_stat_get_tab_tuples_returned' },
+{ oid => '9603', descr => 'statistics: number of tuples read by seqscan',
+  proname => 'pg_stat_get_ind_tuples_returned', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_ind_tuples_returned' },
 { oid => '1930', descr => 'statistics: number of tuples fetched by idxscan',
-  proname => 'pg_stat_get_tuples_fetched', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_fetched', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tab_tuples_fetched' },
+{ oid => '8526', descr => 'statistics: number of tuples fetched by idxscan',
+  proname => 'pg_stat_get_ind_tuples_fetched', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_fetched' },
+  prosrc => 'pg_stat_get_ind_tuples_fetched' },
 { oid => '1931', descr => 'statistics: number of tuples inserted',
-  proname => 'pg_stat_get_tuples_inserted', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_inserted', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_inserted' },
+  prosrc => 'pg_stat_get_tab_tuples_inserted' },
 { oid => '1932', descr => 'statistics: number of tuples updated',
-  proname => 'pg_stat_get_tuples_updated', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_updated', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_updated' },
+  prosrc => 'pg_stat_get_tab_tuples_updated' },
 { oid => '1933', descr => 'statistics: number of tuples deleted',
-  proname => 'pg_stat_get_tuples_deleted', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_deleted', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_deleted' },
+  prosrc => 'pg_stat_get_tab_tuples_deleted' },
 { oid => '1972', descr => 'statistics: number of tuples hot updated',
-  proname => 'pg_stat_get_tuples_hot_updated', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_hot_updated', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_hot_updated' },
+  prosrc => 'pg_stat_get_tab_tuples_hot_updated' },
 { oid => '2878', descr => 'statistics: number of live tuples',
-  proname => 'pg_stat_get_live_tuples', provolatile => 's', proparallel => 'r',
+  proname => 'pg_stat_get_tab_live_tuples', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_live_tuples' },
+  prosrc => 'pg_stat_get_tab_live_tuples' },
 { oid => '2879', descr => 'statistics: number of dead tuples',
-  proname => 'pg_stat_get_dead_tuples', provolatile => 's', proparallel => 'r',
+  proname => 'pg_stat_get_tab_dead_tuples', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_dead_tuples' },
+  prosrc => 'pg_stat_get_tab_dead_tuples' },
 { oid => '3177',
   descr => 'statistics: number of tuples changed since last analyze',
-  proname => 'pg_stat_get_mod_since_analyze', provolatile => 's',
+  proname => 'pg_stat_get_tab_mod_since_analyze', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_mod_since_analyze' },
+  prosrc => 'pg_stat_get_tab_mod_since_analyze' },
 { oid => '5053',
   descr => 'statistics: number of tuples inserted since last vacuum',
-  proname => 'pg_stat_get_ins_since_vacuum', provolatile => 's',
+  proname => 'pg_stat_get_tab_ins_since_vacuum', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_ins_since_vacuum' },
+  prosrc => 'pg_stat_get_tab_ins_since_vacuum' },
 { oid => '1934', descr => 'statistics: number of blocks fetched',
-  proname => 'pg_stat_get_blocks_fetched', provolatile => 's',
+  proname => 'pg_stat_get_tab_blocks_fetched', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_blocks_fetched' },
+  prosrc => 'pg_stat_get_tab_blocks_fetched' },
+{ oid => '9432', descr => 'statistics: number of blocks fetched',
+  proname => 'pg_stat_get_ind_blocks_fetched', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_ind_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_tab_blocks_hit', provolatile => 's', proparallel => 'r',
+  prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tab_blocks_hit' },
+{ oid => '8354', descr => 'statistics: number of blocks found in cache',
+  proname => 'pg_stat_get_ind_blocks_hit', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_blocks_hit' },
+  prosrc => 'pg_stat_get_ind_blocks_hit' },
 { oid => '2781', descr => 'statistics: last manual vacuum time for a table',
-  proname => 'pg_stat_get_last_vacuum_time', provolatile => 's',
+  proname => 'pg_stat_get_tab_last_vacuum_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_last_vacuum_time' },
+  prosrc => 'pg_stat_get_tab_last_vacuum_time' },
 { oid => '2782', descr => 'statistics: last auto vacuum time for a table',
-  proname => 'pg_stat_get_last_autovacuum_time', provolatile => 's',
+  proname => 'pg_stat_get_tab_last_autovacuum_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_last_autovacuum_time' },
+  prosrc => 'pg_stat_get_tab_last_autovacuum_time' },
 { oid => '2783', descr => 'statistics: last manual analyze time for a table',
-  proname => 'pg_stat_get_last_analyze_time', provolatile => 's',
+  proname => 'pg_stat_get_tab_last_analyze_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_last_analyze_time' },
+  prosrc => 'pg_stat_get_tab_last_analyze_time' },
 { oid => '2784', descr => 'statistics: last auto analyze time for a table',
-  proname => 'pg_stat_get_last_autoanalyze_time', provolatile => 's',
+  proname => 'pg_stat_get_tab_last_autoanalyze_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_last_autoanalyze_time' },
+  prosrc => 'pg_stat_get_tab_last_autoanalyze_time' },
 { oid => '3054', descr => 'statistics: number of manual vacuums for a table',
-  proname => 'pg_stat_get_vacuum_count', provolatile => 's', proparallel => 'r',
+  proname => 'pg_stat_get_tab_vacuum_count', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_vacuum_count' },
+  prosrc => 'pg_stat_get_tab_vacuum_count' },
 { oid => '3055', descr => 'statistics: number of auto vacuums for a table',
-  proname => 'pg_stat_get_autovacuum_count', provolatile => 's',
+  proname => 'pg_stat_get_tab_autovacuum_count', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_autovacuum_count' },
+  prosrc => 'pg_stat_get_tab_autovacuum_count' },
 { oid => '3056', descr => 'statistics: number of manual analyzes for a table',
-  proname => 'pg_stat_get_analyze_count', provolatile => 's',
+  proname => 'pg_stat_get_tab_analyze_count', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_analyze_count' },
+  prosrc => 'pg_stat_get_tab_analyze_count' },
 { oid => '3057', descr => 'statistics: number of auto analyzes for a table',
-  proname => 'pg_stat_get_autoanalyze_count', provolatile => 's',
+  proname => 'pg_stat_get_tab_autoanalyze_count', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_autoanalyze_count' },
+  prosrc => 'pg_stat_get_tab_autoanalyze_count' },
 { oid => '1936', descr => 'statistics: currently active backend IDs',
   proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't',
   provolatile => 's', proparallel => 'r', prorettype => 'int4',
@@ -5719,10 +5743,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_tab_xact_numscans', provolatile => 'v',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tab_xact_numscans' },
+{ oid => '8892',
+  descr => 'statistics: number of scans done for index in current transaction',
+  proname => 'pg_stat_get_ind_xact_numscans', provolatile => 'v',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_xact_numscans' },
+  prosrc => 'pg_stat_get_ind_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 a3df8d27c3..a35465bc92 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 autoanalyze_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;
@@ -494,16 +541,15 @@ extern PgStat_BackendFunctionEntry *find_funcstat_entry(Oid func_id);
 
 
 /*
- * Functions in pgstat_relation.c
+ * Functions in pgstat_table.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_drop_table(Relation rel);
+extern void pgstat_init_table(Relation rel);
 
-extern void pgstat_init_relation(Relation rel);
-extern void pgstat_assoc_relation(Relation rel);
-extern void pgstat_unlink_relation(Relation rel);
+extern void pgstat_assoc_table(Relation rel);
+extern void pgstat_unlink_table(Relation rel);
 
 extern void pgstat_report_vacuum(Oid tableoid, bool shared,
 								 PgStat_Counter livetuples, PgStat_Counter deadtuples);
@@ -511,53 +557,83 @@ extern void pgstat_report_analyze(Relation rel,
 								  PgStat_Counter livetuples, PgStat_Counter deadtuples,
 								  bool resetcounter);
 
+/*
+ * Functions in pgstat_index.c
+ */
+
+extern void pgstat_create_index(Relation rel);
+extern void pgstat_drop_index(Relation rel);
+extern void pgstat_copy_index_stats(Relation dst, Relation src);
+extern void pgstat_init_index(Relation rel);
+extern void pgstat_assoc_index(Relation rel);
+extern void pgstat_unlink_index(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_table()
+ * and pgstat_init_index().
  */
-#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 +649,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..004e5412ab 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
 {
@@ -550,16 +556,23 @@ extern bool pgstat_function_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
 
 
 /*
- * Functions in pgstat_relation.c
+ * Functions in pgstat_table.c
  */
 
-extern void AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit);
-extern void AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth);
-extern void AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state);
-extern void PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state);
+extern void AtEOXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit);
+extern void AtEOSubXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth);
+extern void AtPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state);
+extern void PostPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state);
+
+extern bool pgstat_table_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+extern void pgstat_table_delete_pending_cb(PgStat_EntryRef *entry_ref);
+
+/*
+ * Functions in pgstat_index.c
+ */
 
-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_index_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+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..494adee88b 100644
--- a/src/test/isolation/expected/stats.out
+++ b/src/test/isolation/expected/stats.out
@@ -2166,14 +2166,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2210,14 +2210,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2236,14 +2236,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2275,14 +2275,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2314,14 +2314,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2369,14 +2369,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2401,14 +2401,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2463,14 +2463,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2495,14 +2495,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2558,14 +2558,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2627,14 +2627,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2688,14 +2688,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2755,14 +2755,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2795,14 +2795,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2841,14 +2841,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2881,14 +2881,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2927,14 +2927,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2968,14 +2968,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -3015,14 +3015,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
diff --git a/src/test/isolation/expected/stats_1.out b/src/test/isolation/expected/stats_1.out
index 3854320106..7981747732 100644
--- a/src/test/isolation/expected/stats_1.out
+++ b/src/test/isolation/expected/stats_1.out
@@ -2174,14 +2174,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2218,14 +2218,14 @@ 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_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2244,14 +2244,14 @@ 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_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2283,14 +2283,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2322,14 +2322,14 @@ 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_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2377,14 +2377,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2409,14 +2409,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2471,14 +2471,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2503,14 +2503,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2566,14 +2566,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2635,14 +2635,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2698,14 +2698,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2767,14 +2767,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2809,14 +2809,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2857,14 +2857,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2899,14 +2899,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2947,14 +2947,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2990,14 +2990,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -3039,14 +3039,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
diff --git a/src/test/isolation/specs/stats.spec b/src/test/isolation/specs/stats.spec
index 5b922d788c..53d78f7db1 100644
--- a/src/test/isolation/specs/stats.spec
+++ b/src/test/isolation/specs/stats.spec
@@ -93,14 +93,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 }
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 fb9f936d43..9c2b813789 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1767,10 +1767,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_ind_numscans(i.oid) AS idx_scan,
+    pg_stat_get_ind_lastscan(i.oid) AS last_idx_scan,
+    pg_stat_get_ind_tuples_returned(i.oid) AS idx_tup_read,
+    pg_stat_get_ind_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)))
@@ -1779,28 +1779,28 @@ 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_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,
-    pg_stat_get_tuples_hot_updated(c.oid) AS n_tup_hot_upd,
-    pg_stat_get_live_tuples(c.oid) AS n_live_tup,
-    pg_stat_get_dead_tuples(c.oid) AS n_dead_tup,
-    pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze,
-    pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum,
-    pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum,
-    pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum,
-    pg_stat_get_last_analyze_time(c.oid) AS last_analyze,
-    pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze,
-    pg_stat_get_vacuum_count(c.oid) AS vacuum_count,
-    pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count,
-    pg_stat_get_analyze_count(c.oid) AS analyze_count,
-    pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count
+    pg_stat_get_tab_numscans(c.oid) AS seq_scan,
+    pg_stat_get_tab_lastscan(c.oid) AS last_seq_scan,
+    pg_stat_get_tab_tuples_returned(c.oid) AS seq_tup_read,
+    (sum(pg_stat_get_ind_numscans(i.indexrelid)))::bigint AS idx_scan,
+    max(pg_stat_get_ind_lastscan(i.indexrelid)) AS last_idx_scan,
+    ((sum(pg_stat_get_ind_tuples_fetched(i.indexrelid)))::bigint + pg_stat_get_tab_tuples_fetched(c.oid)) AS idx_tup_fetch,
+    pg_stat_get_tab_tuples_inserted(c.oid) AS n_tup_ins,
+    pg_stat_get_tab_tuples_updated(c.oid) AS n_tup_upd,
+    pg_stat_get_tab_tuples_deleted(c.oid) AS n_tup_del,
+    pg_stat_get_tab_tuples_hot_updated(c.oid) AS n_tup_hot_upd,
+    pg_stat_get_tab_live_tuples(c.oid) AS n_live_tup,
+    pg_stat_get_tab_dead_tuples(c.oid) AS n_dead_tup,
+    pg_stat_get_tab_mod_since_analyze(c.oid) AS n_mod_since_analyze,
+    pg_stat_get_tab_ins_since_vacuum(c.oid) AS n_ins_since_vacuum,
+    pg_stat_get_tab_last_vacuum_time(c.oid) AS last_vacuum,
+    pg_stat_get_tab_last_autovacuum_time(c.oid) AS last_autovacuum,
+    pg_stat_get_tab_last_analyze_time(c.oid) AS last_analyze,
+    pg_stat_get_tab_last_autoanalyze_time(c.oid) AS last_autoanalyze,
+    pg_stat_get_tab_vacuum_count(c.oid) AS vacuum_count,
+    pg_stat_get_tab_autovacuum_count(c.oid) AS autovacuum_count,
+    pg_stat_get_tab_analyze_count(c.oid) AS analyze_count,
+    pg_stat_get_tab_autoanalyze_count(c.oid) AS autoanalyze_count
    FROM ((pg_class c
      LEFT JOIN pg_index i ON ((c.oid = i.indrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
@@ -2224,9 +2224,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_tab_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_ind_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,
@@ -2277,8 +2277,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_ind_blocks_fetched(i.oid) - pg_stat_get_ind_blocks_hit(i.oid)) AS idx_blks_read,
+    pg_stat_get_ind_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)))
@@ -2287,31 +2287,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_ind_blocks_fetched(c.oid) - pg_stat_get_tab_blocks_hit(c.oid)) AS blks_read,
+    pg_stat_get_tab_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_tab_blocks_fetched(c.oid) - pg_stat_get_tab_blocks_hit(c.oid)) AS heap_blks_read,
+    pg_stat_get_tab_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_tab_blocks_fetched(t.oid) - pg_stat_get_tab_blocks_hit(t.oid)) AS toast_blks_read,
+    pg_stat_get_tab_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_ind_blocks_fetched(pg_index.indexrelid) - pg_stat_get_ind_blocks_hit(pg_index.indexrelid))))::bigint AS idx_blks_read,
+            (sum(pg_stat_get_ind_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_ind_blocks_fetched(pg_index.indexrelid) - pg_stat_get_ind_blocks_hit(pg_index.indexrelid))))::bigint AS idx_blks_read,
+            (sum(pg_stat_get_ind_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 1d84407a03..d5ee0f4845 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;$$;
@@ -357,17 +468,17 @@ SELECT pg_stat_force_next_flush();
  
 (1 row)
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           1
 (1 row)
 
 DROP TABLE drop_stats_test;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       0
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           0
 (1 row)
 
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_oid);
@@ -378,18 +489,18 @@ SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_oid);
 
 -- check that rollback protects against having stats dropped and that local
 -- modifications don't pose a problem
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
-(1 row)
-
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_tuples_inserted 
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_live_tuples 
 -----------------------------
                            1
 (1 row)
 
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_tuples_inserted 
+---------------------------------
+                               1
+(1 row)
+
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
  pg_stat_get_xact_tuples_inserted 
 ----------------------------------
@@ -418,29 +529,29 @@ SELECT pg_stat_force_next_flush();
  
 (1 row)
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           1
 (1 row)
 
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_tuples_inserted 
------------------------------
-                           2
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_tuples_inserted 
+---------------------------------
+                               2
 (1 row)
 
 -- transactional drop
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           1
 (1 row)
 
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_tuples_inserted 
------------------------------
-                           2
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_tuples_inserted 
+---------------------------------
+                               2
 (1 row)
 
 BEGIN;
@@ -465,23 +576,23 @@ SELECT pg_stat_force_next_flush();
  
 (1 row)
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       0
-(1 row)
-
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_tuples_inserted 
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_live_tuples 
 -----------------------------
                            0
 (1 row)
 
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_tuples_inserted 
+---------------------------------
+                               0
+(1 row)
+
 -- savepoint rollback (2 levels)
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           1
 (1 row)
 
 BEGIN;
@@ -510,17 +621,17 @@ SELECT pg_stat_force_next_flush();
  
 (1 row)
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       3
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           3
 (1 row)
 
 -- savepoint rolback (1 level)
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       3
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           3
 (1 row)
 
 BEGIN;
@@ -529,17 +640,17 @@ DROP TABLE drop_stats_test_subxact;
 SAVEPOINT sp2;
 ROLLBACK TO SAVEPOINT sp1;
 COMMIT;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       3
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           3
 (1 row)
 
 -- and now actually drop
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       3
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           3
 (1 row)
 
 BEGIN;
@@ -548,10 +659,10 @@ DROP TABLE drop_stats_test_subxact;
 SAVEPOINT sp2;
 RELEASE SAVEPOINT sp1;
 COMMIT;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       0
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           0
 (1 row)
 
 DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
@@ -1021,21 +1132,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
@@ -1051,14 +1162,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
@@ -1073,7 +1184,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
@@ -1081,7 +1192,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
@@ -1089,7 +1200,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
@@ -1097,7 +1208,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
@@ -1105,7 +1216,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 b4d6753c71..1e9ae74b2a 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
 ---
@@ -218,15 +289,15 @@ SELECT 'drop_stats_test_subxact'::regclass::oid AS drop_stats_test_subxact_oid \
 
 SELECT pg_stat_force_next_flush();
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_oid);
 DROP TABLE drop_stats_test;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_oid);
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_oid);
 
 -- check that rollback protects against having stats dropped and that local
 -- modifications don't pose a problem
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
 BEGIN;
 INSERT INTO drop_stats_test_xact DEFAULT VALUES;
@@ -235,12 +306,12 @@ DROP TABLE drop_stats_test_xact;
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
 ROLLBACK;
 SELECT pg_stat_force_next_flush();
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
 
 -- transactional drop
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
 BEGIN;
 INSERT INTO drop_stats_test_xact DEFAULT VALUES;
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
@@ -248,11 +319,11 @@ DROP TABLE drop_stats_test_xact;
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
 COMMIT;
 SELECT pg_stat_force_next_flush();
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
 
 -- savepoint rollback (2 levels)
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 BEGIN;
 INSERT INTO drop_stats_test_subxact DEFAULT VALUES;
 SAVEPOINT sp1;
@@ -264,27 +335,27 @@ ROLLBACK TO SAVEPOINT sp2;
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_subxact_oid);
 COMMIT;
 SELECT pg_stat_force_next_flush();
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 
 -- savepoint rolback (1 level)
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 BEGIN;
 SAVEPOINT sp1;
 DROP TABLE drop_stats_test_subxact;
 SAVEPOINT sp2;
 ROLLBACK TO SAVEPOINT sp1;
 COMMIT;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 
 -- and now actually drop
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 BEGIN;
 SAVEPOINT sp1;
 DROP TABLE drop_stats_test_subxact;
 SAVEPOINT sp2;
 RELEASE SAVEPOINT sp1;
 COMMIT;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 
 DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
 DROP TABLE prevstats;
@@ -493,40 +564,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 a2dfd5c9da..5784513538 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2008,10 +2008,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
@@ -2025,6 +2026,7 @@ PgStat_FetchConsistency
 PgStat_FunctionCallUsage
 PgStat_FunctionCounts
 PgStat_HashKey
+PgStat_IndexStatus
 PgStat_Kind
 PgStat_KindInfo
 PgStat_LocalState
#16Drouvot, Bertrand
bertranddrouvot.pg@gmail.com
In reply to: Drouvot, Bertrand (#15)
1 attachment(s)
Re: Split index and table statistics into different types of stats

Hi,

On 12/7/22 11:11 AM, Drouvot, Bertrand wrote:

Hi,

Hi,

As [1] mentioned above has been committed (83a1a1b566), please find attached V5 related to this thread making use of the new macros (namely PG_STAT_GET_RELENTRY_INT64 and PG_STAT_GET_RELENTRY_TIMESTAMPTZ).

I switched from using "CppConcat" to using "##", as it looks to me it's easier to read now that we are adding another concatenation to the game (due to the table/index split).

The (Tab,tab) or (Ind,ind) passed as arguments to the macros look "weird" (I don't have a better idea yet): purpose is to follow the naming convention for PgStat_StatTabEntry/PgStat_StatIndEntry and pgstat_fetch_stat_tabentry/pgstat_fetch_stat_indentry, thoughts?

Looking forward to your feedback,

Attaching V6 (mandatory rebase due to 8018ffbf58).

While at it, I got rid of the weirdness mentioned above by creating 2 sets of macros (one for the tables and one for the indexes).

Looking forward to your feedback,

Regards,

Attaching V7, mandatory rebase due to 66dcb09246.

Regards,

--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

Attachments:

v7-0001-split_tables_indexes_stats.patchtext/plain; charset=UTF-8; name=v7-0001-split_tables_indexes_stats.patchDownload
diff --git a/src/backend/access/common/relation.c b/src/backend/access/common/relation.c
index 382a42ff7d..259bd06aed 100644
--- a/src/backend/access/common/relation.c
+++ b/src/backend/access/common/relation.c
@@ -73,7 +73,10 @@ relation_open(Oid relationId, LOCKMODE lockmode)
 	if (RelationUsesLocalBuffers(r))
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
-	pgstat_init_relation(r);
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_init_index(r);
+	else
+		pgstat_init_table(r);
 
 	return r;
 }
@@ -123,7 +126,10 @@ try_relation_open(Oid relationId, LOCKMODE lockmode)
 	if (RelationUsesLocalBuffers(r))
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
-	pgstat_init_relation(r);
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_init_index(r);
+	else
+		pgstat_init_table(r);
 
 	return r;
 }
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index dc303995e5..ceebc79302 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -590,7 +590,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 bdd413f01b..d5b71fdd1e 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..a612ae81ed 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -655,29 +655,29 @@ 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_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,
-            pg_stat_get_tuples_hot_updated(C.oid) AS n_tup_hot_upd,
-            pg_stat_get_live_tuples(C.oid) AS n_live_tup,
-            pg_stat_get_dead_tuples(C.oid) AS n_dead_tup,
-            pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze,
-            pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum,
-            pg_stat_get_last_vacuum_time(C.oid) as last_vacuum,
-            pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum,
-            pg_stat_get_last_analyze_time(C.oid) as last_analyze,
-            pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze,
-            pg_stat_get_vacuum_count(C.oid) AS vacuum_count,
-            pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count,
-            pg_stat_get_analyze_count(C.oid) AS analyze_count,
-            pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count
+            pg_stat_get_tab_numscans(C.oid) AS seq_scan,
+            pg_stat_get_tab_lastscan(C.oid) AS last_seq_scan,
+            pg_stat_get_tab_tuples_returned(C.oid) AS seq_tup_read,
+            sum(pg_stat_get_ind_numscans(I.indexrelid))::bigint AS idx_scan,
+            max(pg_stat_get_ind_lastscan(I.indexrelid)) AS last_idx_scan,
+            sum(pg_stat_get_ind_tuples_fetched(I.indexrelid))::bigint +
+            pg_stat_get_tab_tuples_fetched(C.oid) AS idx_tup_fetch,
+            pg_stat_get_tab_tuples_inserted(C.oid) AS n_tup_ins,
+            pg_stat_get_tab_tuples_updated(C.oid) AS n_tup_upd,
+            pg_stat_get_tab_tuples_deleted(C.oid) AS n_tup_del,
+            pg_stat_get_tab_tuples_hot_updated(C.oid) AS n_tup_hot_upd,
+            pg_stat_get_tab_live_tuples(C.oid) AS n_live_tup,
+            pg_stat_get_tab_dead_tuples(C.oid) AS n_dead_tup,
+            pg_stat_get_tab_mod_since_analyze(C.oid) AS n_mod_since_analyze,
+            pg_stat_get_tab_ins_since_vacuum(C.oid) AS n_ins_since_vacuum,
+            pg_stat_get_tab_last_vacuum_time(C.oid) as last_vacuum,
+            pg_stat_get_tab_last_autovacuum_time(C.oid) as last_autovacuum,
+            pg_stat_get_tab_last_analyze_time(C.oid) as last_analyze,
+            pg_stat_get_tab_last_autoanalyze_time(C.oid) as last_autoanalyze,
+            pg_stat_get_tab_vacuum_count(C.oid) AS vacuum_count,
+            pg_stat_get_tab_autovacuum_count(C.oid) AS autovacuum_count,
+            pg_stat_get_tab_analyze_count(C.oid) AS analyze_count,
+            pg_stat_get_tab_autoanalyze_count(C.oid) AS autoanalyze_count
     FROM pg_class C LEFT JOIN
          pg_index I ON C.oid = I.indrelid
          LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
@@ -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_tab_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_ind_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_tab_blocks_fetched(C.oid) -
+                    pg_stat_get_tab_blocks_hit(C.oid) AS heap_blks_read,
+            pg_stat_get_tab_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_tab_blocks_fetched(T.oid) -
+                    pg_stat_get_tab_blocks_hit(T.oid) AS toast_blks_read,
+            pg_stat_get_tab_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_ind_blocks_fetched(indexrelid) -
+                         pg_stat_get_ind_blocks_hit(indexrelid))::bigint
                      AS idx_blks_read,
-                     sum(pg_stat_get_blocks_hit(indexrelid))::bigint
+                     sum(pg_stat_get_ind_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_ind_blocks_fetched(indexrelid) -
+                         pg_stat_get_ind_blocks_hit(indexrelid))::bigint
                      AS idx_blks_read,
-                     sum(pg_stat_get_blocks_hit(indexrelid))::bigint
+                     sum(pg_stat_get_ind_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_ind_numscans(I.oid) AS idx_scan,
+            pg_stat_get_ind_lastscan(I.oid) AS last_idx_scan,
+            pg_stat_get_ind_tuples_returned(I.oid) AS idx_tup_read,
+            pg_stat_get_ind_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_ind_blocks_fetched(I.oid) -
+                    pg_stat_get_ind_blocks_hit(I.oid) AS idx_blks_read,
+            pg_stat_get_ind_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_ind_blocks_fetched(C.oid) -
+                    pg_stat_get_tab_blocks_hit(C.oid) AS blks_read,
+            pg_stat_get_tab_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/Makefile b/src/backend/utils/activity/Makefile
index a2e8507fd6..7441f4ce85 100644
--- a/src/backend/utils/activity/Makefile
+++ b/src/backend/utils/activity/Makefile
@@ -22,11 +22,12 @@ OBJS = \
 	pgstat_checkpointer.o \
 	pgstat_database.o \
 	pgstat_function.o \
-	pgstat_relation.o \
+	pgstat_index.o \
 	pgstat_replslot.o \
 	pgstat_shmem.o \
 	pgstat_slru.o \
 	pgstat_subscription.o \
+	pgstat_table.o \
 	pgstat_wal.o \
 	pgstat_xact.o \
 	wait_event.o
diff --git a/src/backend/utils/activity/meson.build b/src/backend/utils/activity/meson.build
index 5b3b558a67..e6bf3c59b6 100644
--- a/src/backend/utils/activity/meson.build
+++ b/src/backend/utils/activity/meson.build
@@ -7,11 +7,12 @@ backend_sources += files(
   'pgstat_checkpointer.c',
   'pgstat_database.c',
   'pgstat_function.c',
-  'pgstat_relation.c',
+  'pgstat_index.c',
   'pgstat_replslot.c',
   'pgstat_shmem.c',
   'pgstat_slru.c',
   'pgstat_subscription.c',
+  'pgstat_table.c',
   'pgstat_wal.c',
   'pgstat_xact.c',
   'wait_event.c',
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index 1ebe3bbf29..d49a5ea4bb 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -72,10 +72,11 @@
  * - pgstat_checkpointer.c
  * - pgstat_database.c
  * - pgstat_function.c
- * - pgstat_relation.c
+ * - pgstat_index.c
  * - pgstat_replslot.c
  * - pgstat_slru.c
  * - pgstat_subscription.c
+ * - pgstat_table.c
  * - pgstat_wal.c
  *
  * Whenever possible infrastructure files should not contain code related to
@@ -269,18 +270,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_index.c b/src/backend/utils/activity/pgstat_index.c
new file mode 100644
index 0000000000..820592c119
--- /dev/null
+++ b/src/backend/utils/activity/pgstat_index.c
@@ -0,0 +1,297 @@
+/* -------------------------------------------------------------------------
+ *
+ * pgstat_index.c
+ *	  Implementation of index statistics.
+ *
+ * This file contains the implementation of function index. It is kept
+ * separate from pgstat.c to enforce the line between the statistics access /
+ * storage implementation and the details about individual types of
+ * statistics.
+ *
+ * Copyright (c) 2001-2022, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/activity/pgstat_index.c
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/xact.h"
+#include "utils/pgstat_internal.h"
+#include "utils/rel.h"
+#include "catalog/catalog.h"
+
+static PgStat_IndexStatus *pgstat_prep_index_pending(Oid rel_id, bool isshared);
+
+/*
+ * Copy stats between indexes. This is used for things like REINDEX
+ * CONCURRENTLY.
+ */
+void
+pgstat_copy_index_stats(Relation dst, Relation src)
+{
+	PgStat_StatIndEntry *srcstats;
+	PgStatShared_Index *dstshstats;
+	PgStat_EntryRef *dst_ref;
+
+	srcstats = pgstat_fetch_stat_indentry_ext(src->rd_rel->relisshared,
+											  RelationGetRelid(src));
+	if (!srcstats)
+		return;
+
+	dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_INDEX,
+										  dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+										  RelationGetRelid(dst),
+										  false);
+
+	dstshstats = (PgStatShared_Index *) dst_ref->shared_stats;
+	dstshstats->stats = *srcstats;
+
+	pgstat_unlock_entry(dst_ref);
+}
+
+/*
+ * Initialize a relcache entry to count access statistics.  Called whenever an
+ * index is opened.
+ *
+ * We assume that a relcache entry's pgstatind_info field is 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
+ * pgstat_assoc_index().
+ */
+void
+pgstat_init_index(Relation rel)
+{
+	/*
+	 * We only count stats for indexes
+	 */
+	Assert(rel->rd_rel->relkind == RELKIND_INDEX);
+
+	if (!pgstat_track_counts)
+	{
+		if (rel->pgstatind_info != NULL)
+			pgstat_unlink_index(rel);
+
+		/* We're not counting at all */
+		rel->pgstat_enabled = false;
+		rel->pgstatind_info = NULL;
+		return;
+	}
+
+	rel->pgstat_enabled = true;
+}
+
+/*
+ * 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_index() 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->pgstatind_info->relation == NULL);
+
+	/* mark this relation as the owner */
+	rel->pgstatind_info->relation = rel;
+}
+
+/*
+ * Break the mutual link between a relcache entry and pending index stats entry.
+ * This must be called whenever one end of the link is removed.
+ */
+void
+pgstat_unlink_index(Relation rel)
+{
+
+	if (rel->pgstatind_info == NULL)
+		return;
+
+	/* 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 index stats are dropped if transaction aborts.
+ */
+void
+pgstat_create_index(Relation rel)
+{
+	pgstat_create_transactional(PGSTAT_KIND_INDEX,
+								rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+								RelationGetRelid(rel));
+}
+
+/*
+ * Ensure that index stats are dropped if transaction commits.
+ */
+void
+pgstat_drop_index(Relation rel)
+{
+	pgstat_drop_transactional(PGSTAT_KIND_INDEX,
+							  rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+							  RelationGetRelid(rel));
+}
+
+/*
+ * 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)
+{
+	return pgstat_fetch_stat_indentry_ext(IsSharedRelation(relid), relid);
+}
+
+/*
+ * 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);
+}
+
+/*
+ * 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;
+	return NULL;
+}
+
+/*
+ * 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->tuples_returned += lstats->i_counts.i_tuples_returned;
+	dbentry->tuples_fetched += lstats->i_counts.i_tuples_fetched;
+	dbentry->blocks_fetched += lstats->i_counts.i_blocks_fetched;
+	dbentry->blocks_hit += lstats->i_counts.i_blocks_hit;
+
+	return true;
+}
+
+void
+pgstat_index_delete_pending_cb(PgStat_EntryRef *entry_ref)
+{
+	PgStat_IndexStatus *pending = (PgStat_IndexStatus *) entry_ref->pending;
+
+	if (pending->relation)
+		pgstat_unlink_index(pending->relation);
+}
+
+/*
+ * 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;
+}
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_table.c
similarity index 75%
rename from src/backend/utils/activity/pgstat_relation.c
rename to src/backend/utils/activity/pgstat_table.c
index f9788c30ae..7e4e0134df 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_table.c
@@ -1,9 +1,9 @@
 /* -------------------------------------------------------------------------
  *
- * pgstat_relation.c
- *	  Implementation of relation statistics.
+ * pgstat_table.c
+ *	  Implementation of table statistics.
  *
- * This file contains the implementation of function relation. It is kept
+ * This file contains the implementation of function table. It is kept
  * separate from pgstat.c to enforce the line between the statistics access /
  * storage implementation and the details about individual types of
  * statistics.
@@ -11,7 +11,7 @@
  * Copyright (c) 2001-2022, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *	  src/backend/utils/activity/pgstat_relation.c
+ *	  src/backend/utils/activity/pgstat_table.c
  * -------------------------------------------------------------------------
  */
 
@@ -44,74 +44,48 @@ 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 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
- * CONCURRENTLY.
- */
-void
-pgstat_copy_relation_stats(Relation dst, Relation src)
-{
-	PgStat_StatTabEntry *srcstats;
-	PgStatShared_Relation *dstshstats;
-	PgStat_EntryRef *dst_ref;
-
-	srcstats = pgstat_fetch_stat_tabentry_ext(src->rd_rel->relisshared,
-											  RelationGetRelid(src));
-	if (!srcstats)
-		return;
-
-	dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
-										  dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
-										  RelationGetRelid(dst),
-										  false);
-
-	dstshstats = (PgStatShared_Relation *) dst_ref->shared_stats;
-	dstshstats->stats = *srcstats;
-
-	pgstat_unlock_entry(dst_ref);
-}
-
 /*
  * Initialize a relcache entry to count access statistics.  Called whenever a
- * relation is opened.
+ * table is opened.
  *
- * We assume that a relcache entry's pgstat_info field is zeroed by relcache.c
+ * We assume that a relcache entry's pgstattab_info field is 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
- * pgstat_assoc_relation().
+ * pgstat_assoc_table().
  */
 void
-pgstat_init_relation(Relation rel)
+pgstat_init_table(Relation rel)
 {
 	char		relkind = rel->rd_rel->relkind;
 
+	Assert(relkind != RELKIND_INDEX);
+
 	/*
 	 * We only count stats for relations with storage and partitioned tables
 	 */
 	if (!RELKIND_HAS_STORAGE(relkind) && relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		rel->pgstat_enabled = false;
-		rel->pgstat_info = NULL;
+		rel->pgstattab_info = NULL;
 		return;
 	}
 
 	if (!pgstat_track_counts)
 	{
-		if (rel->pgstat_info)
-			pgstat_unlink_relation(rel);
+		if (rel->pgstattab_info != NULL)
+			pgstat_unlink_table(rel);
 
 		/* We're not counting at all */
 		rel->pgstat_enabled = false;
-		rel->pgstat_info = NULL;
+		rel->pgstattab_info = NULL;
 		return;
 	}
 
@@ -119,89 +93,92 @@ 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.
  *
- * This is separate from pgstat_init_relation() as it is not uncommon for
+ * This is separate from pgstat_init_table() as it is not uncommon for
  * 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->pgstat_info->relation == NULL);
+	Assert(rel->pgstattab_info->relation == NULL);
 
 	/* mark this relation as the owner */
-	rel->pgstat_info->relation = rel;
+	rel->pgstattab_info->relation = rel;
 }
 
 /*
- * Break the mutual link between a relcache entry and pending stats entry.
+ * Break the mutual link between a relcache entry and pending table stats entry.
  * This must be called whenever one end of the link is removed.
  */
 void
-pgstat_unlink_relation(Relation rel)
+pgstat_unlink_table(Relation rel)
 {
-	/* remove the link to stats info if any */
-	if (rel->pgstat_info == NULL)
+
+	if (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;
+	}
 }
 
 /*
- * Ensure that stats are dropped if transaction aborts.
+ * Ensure that table stats are dropped if transaction aborts.
  */
 void
-pgstat_create_relation(Relation rel)
+pgstat_create_table(Relation rel)
 {
-	pgstat_create_transactional(PGSTAT_KIND_RELATION,
+	pgstat_create_transactional(PGSTAT_KIND_TABLE,
 								rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
 								RelationGetRelid(rel));
 }
 
 /*
- * Ensure that stats are dropped if transaction commits.
+ * Ensure that table stats are dropped if transaction commits.
  */
 void
-pgstat_drop_relation(Relation rel)
+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;
 	}
 }
 
@@ -213,7 +190,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;
@@ -225,10 +202,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->live_tuples = livetuples;
@@ -272,7 +249,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);
 
@@ -291,31 +268,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->live_tuples = livetuples;
@@ -349,12 +326,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;
 	}
 }
 
@@ -364,16 +341,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++;
 	}
 }
 
@@ -383,12 +360,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++;
 	}
 }
 
@@ -398,15 +375,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;
 	}
 }
 
@@ -421,11 +398,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;
 	}
 }
 
@@ -451,7 +428,7 @@ 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);
 }
 
 /*
@@ -467,9 +444,9 @@ 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;
@@ -477,7 +454,7 @@ find_tabstat_entry(Oid rel_id)
 }
 
 /*
- * Perform relation stats specific end-of-transaction work. Helper for
+ * Perform table stats specific end-of-transaction work. Helper for
  * AtEOXact_PgStat.
  *
  * Transfer transactional insert/update counts into the base tabstat entries.
@@ -485,7 +462,7 @@ find_tabstat_entry(Oid rel_id)
  * TopTransactionContext and will go away anyway.
  */
 void
-AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit)
+AtEOXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit)
 {
 	PgStat_TableXactStatus *trans;
 
@@ -536,14 +513,14 @@ AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit)
 }
 
 /*
- * Perform relation stats specific end-of-sub-transaction work. Helper for
+ * Perform table stats specific end-of-sub-transaction work. Helper for
  * AtEOSubXact_PgStat.
  *
  * Transfer transactional insert/update counts into the next higher
  * subtransaction state.
  */
 void
-AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
+AtEOSubXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
 {
 	PgStat_TableXactStatus *trans;
 	PgStat_TableXactStatus *next_trans;
@@ -620,11 +597,11 @@ AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, in
 }
 
 /*
- * Generate 2PC records for all the pending transaction-dependent relation
+ * Generate 2PC records for all the pending transaction-dependent table
  * stats.
  */
 void
-AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
+AtPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state)
 {
 	PgStat_TableXactStatus *trans;
 
@@ -659,10 +636,10 @@ AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
  * reported to the stats system immediately, while the effects on live and
  * dead tuple counts are preserved in the 2PC state file.
  *
- * Note: AtEOXact_PgStat_Relations is not called during PREPARE.
+ * Note: AtEOXact_PgStat_Tables is not called during PREPARE.
  */
 void
-PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
+PostPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state)
 {
 	PgStat_TableXactStatus *trans;
 
@@ -685,27 +662,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;
 }
@@ -721,10 +698,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)
@@ -733,10 +710,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;
 }
 
@@ -750,22 +727,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)
@@ -783,6 +759,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;
 	}
@@ -831,12 +808,12 @@ pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
 }
 
 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;
 
 	if (pending->relation)
-		pgstat_unlink_relation(pending->relation);
+		pgstat_unlink_table(pending->relation);
 }
 
 /*
@@ -844,12 +821,12 @@ pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref)
  * 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;
@@ -863,7 +840,7 @@ pgstat_prep_relation_pending(Oid rel_id, bool isshared)
  * 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;
@@ -879,24 +856,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/activity/pgstat_xact.c b/src/backend/utils/activity/pgstat_xact.c
index 5a3aca4aef..870f1b1103 100644
--- a/src/backend/utils/activity/pgstat_xact.c
+++ b/src/backend/utils/activity/pgstat_xact.c
@@ -51,7 +51,7 @@ AtEOXact_PgStat(bool isCommit, bool parallel)
 		Assert(xact_state->nest_level == 1);
 		Assert(xact_state->prev == NULL);
 
-		AtEOXact_PgStat_Relations(xact_state, isCommit);
+		AtEOXact_PgStat_Tables(xact_state, isCommit);
 		AtEOXact_PgStat_DroppedStats(xact_state, isCommit);
 	}
 	pgStatXactStack = NULL;
@@ -122,7 +122,7 @@ AtEOSubXact_PgStat(bool isCommit, int nestDepth)
 		/* delink xact_state from stack immediately to simplify reuse case */
 		pgStatXactStack = xact_state->prev;
 
-		AtEOSubXact_PgStat_Relations(xact_state, isCommit, nestDepth);
+		AtEOSubXact_PgStat_Tables(xact_state, isCommit, nestDepth);
 		AtEOSubXact_PgStat_DroppedStats(xact_state, isCommit, nestDepth);
 
 		pfree(xact_state);
@@ -197,7 +197,7 @@ AtPrepare_PgStat(void)
 		Assert(xact_state->nest_level == 1);
 		Assert(xact_state->prev == NULL);
 
-		AtPrepare_PgStat_Relations(xact_state);
+		AtPrepare_PgStat_Tables(xact_state);
 	}
 }
 
@@ -221,7 +221,7 @@ PostPrepare_PgStat(void)
 		Assert(xact_state->nest_level == 1);
 		Assert(xact_state->prev == NULL);
 
-		PostPrepare_PgStat_Relations(xact_state);
+		PostPrepare_PgStat_Tables(xact_state);
 	}
 	pgStatXactStack = NULL;
 
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 04a5a99002..729948378d 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -36,106 +36,159 @@
 
 #define HAS_PGSTAT_PERMISSIONS(role)	 (has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS) || has_privs_of_role(GetUserId(), role))
 
-#define PG_STAT_GET_RELENTRY_INT64(stat)						\
-Datum															\
-CppConcat(pg_stat_get_,stat)(PG_FUNCTION_ARGS)					\
-{																\
-	Oid			relid = PG_GETARG_OID(0);						\
-	int64		result;											\
-	PgStat_StatTabEntry *tabentry;								\
-																\
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)	\
-		result = 0;												\
-	else														\
-		result = (int64) (tabentry->stat);						\
-																\
-	PG_RETURN_INT64(result);									\
-}
-
-/* pg_stat_get_analyze_count */
-PG_STAT_GET_RELENTRY_INT64(analyze_count)
-
-/* pg_stat_get_autoanalyze_count */
-PG_STAT_GET_RELENTRY_INT64(autoanalyze_count)
-
-/* pg_stat_get_autovacuum_count */
-PG_STAT_GET_RELENTRY_INT64(autovacuum_count)
-
-/* pg_stat_get_blocks_fetched */
-PG_STAT_GET_RELENTRY_INT64(blocks_fetched)
-
-/* pg_stat_get_blocks_hit */
-PG_STAT_GET_RELENTRY_INT64(blocks_hit)
+#define PG_STAT_GET_TABENTRY_INT64(stat)							\
+Datum																\
+CppConcat(pg_stat_get_tab_,stat)(PG_FUNCTION_ARGS)					\
+{																	\
+	Oid			relid = PG_GETARG_OID(0);							\
+	int64		result;												\
+	PgStat_StatTabEntry *tabentry;									\
+																	\
+	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)		\
+		result = 0;													\
+	else															\
+		result = (int64) (tabentry->stat);							\
+																	\
+	PG_RETURN_INT64(result);										\
+}
+
+/* pg_stat_get_tab_analyze_count */
+PG_STAT_GET_TABENTRY_INT64(analyze_count)
+
+/* pg_stat_get_tab_autoanalyze_count */
+PG_STAT_GET_TABENTRY_INT64(autoanalyze_count)
+
+/* pg_stat_get_tab_autovacuum_count */
+PG_STAT_GET_TABENTRY_INT64(autovacuum_count)
+
+/* pg_stat_get_tab_blocks_fetched */
+PG_STAT_GET_TABENTRY_INT64(blocks_fetched)
+
+/* pg_stat_get_tab_blocks_hit */
+PG_STAT_GET_TABENTRY_INT64(blocks_hit)
 
-/* pg_stat_get_dead_tuples */
-PG_STAT_GET_RELENTRY_INT64(dead_tuples)
+/* pg_stat_get_tab_dead_tuples */
+PG_STAT_GET_TABENTRY_INT64(dead_tuples)
 
-/* pg_stat_get_ins_since_vacuum */
-PG_STAT_GET_RELENTRY_INT64(ins_since_vacuum)
-
-/* pg_stat_get_live_tuples */
-PG_STAT_GET_RELENTRY_INT64(live_tuples)
-
-/* pg_stat_get_mods_since_analyze */
-PG_STAT_GET_RELENTRY_INT64(mod_since_analyze)
-
-/* pg_stat_get_numscans */
-PG_STAT_GET_RELENTRY_INT64(numscans)
-
-/* pg_stat_get_tuples_deleted */
-PG_STAT_GET_RELENTRY_INT64(tuples_deleted)
-
-/* pg_stat_get_tuples_fetched */
-PG_STAT_GET_RELENTRY_INT64(tuples_fetched)
-
-/* pg_stat_get_tuples_hot_updated */
-PG_STAT_GET_RELENTRY_INT64(tuples_hot_updated)
-
-/* pg_stat_get_tuples_inserted */
-PG_STAT_GET_RELENTRY_INT64(tuples_inserted)
-
-/* pg_stat_get_tuples_returned */
-PG_STAT_GET_RELENTRY_INT64(tuples_returned)
-
-/* pg_stat_get_tuples_updated */
-PG_STAT_GET_RELENTRY_INT64(tuples_updated)
-
-/* pg_stat_get_vacuum_count */
-PG_STAT_GET_RELENTRY_INT64(vacuum_count)
-
-#define PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat)					\
-Datum															\
-CppConcat(pg_stat_get_,stat)(PG_FUNCTION_ARGS)					\
-{																\
-	Oid			relid = PG_GETARG_OID(0);						\
-	TimestampTz result;											\
-	PgStat_StatTabEntry *tabentry;								\
-																\
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)	\
-		result = 0;												\
-	else														\
-		result = tabentry->stat;								\
-																\
-	if (result == 0)											\
-		PG_RETURN_NULL();										\
-	else														\
-		PG_RETURN_TIMESTAMPTZ(result);							\
+/* pg_stat_get_tab_ins_since_vacuum */
+PG_STAT_GET_TABENTRY_INT64(ins_since_vacuum)
+
+/* pg_stat_get_tab_live_tuples */
+PG_STAT_GET_TABENTRY_INT64(live_tuples)
+
+/* pg_stat_get_tab_mods_since_analyze */
+PG_STAT_GET_TABENTRY_INT64(mod_since_analyze)
+
+/* pg_stat_get_tab_numscans */
+PG_STAT_GET_TABENTRY_INT64(numscans)
+
+/* pg_stat_get_tab_tuples_deleted */
+PG_STAT_GET_TABENTRY_INT64(tuples_deleted)
+
+/* pg_stat_get_tab_tuples_fetched */
+PG_STAT_GET_TABENTRY_INT64(tuples_fetched)
+
+/* pg_stat_get_tab_tuples_hot_updated */
+PG_STAT_GET_TABENTRY_INT64(tuples_hot_updated)
+
+/* pg_stat_get_tab_tuples_inserted */
+PG_STAT_GET_TABENTRY_INT64(tuples_inserted)
+
+/* pg_stat_get_tab_tuples_returned */
+PG_STAT_GET_TABENTRY_INT64(tuples_returned)
+
+/* pg_stat_get_tab_tuples_updated */
+PG_STAT_GET_TABENTRY_INT64(tuples_updated)
+
+/* pg_stat_get_tab_vacuum_count */
+PG_STAT_GET_TABENTRY_INT64(vacuum_count)
+
+#define PG_STAT_GET_INDENTRY_INT64(stat)							\
+Datum																\
+CppConcat(pg_stat_get_ind_,stat)(PG_FUNCTION_ARGS)					\
+{																	\
+	Oid			relid = PG_GETARG_OID(0);							\
+	int64		result;												\
+	PgStat_StatIndEntry *indentry;									\
+																	\
+	if ((indentry = pgstat_fetch_stat_indentry(relid)) == NULL)		\
+		result = 0;													\
+	else															\
+		result = (int64) (indentry->stat);							\
+																	\
+	PG_RETURN_INT64(result);										\
+}
+
+/* pg_stat_get_ind_numscans */
+PG_STAT_GET_INDENTRY_INT64(numscans)
+
+/* pg_stat_get_ind_tuples_returned */
+PG_STAT_GET_INDENTRY_INT64(tuples_returned)
+
+/* pg_stat_get_ind_tuples_fetched */
+PG_STAT_GET_INDENTRY_INT64(tuples_fetched)
+
+/* pg_stat_get_ind_blocks_fetched */
+PG_STAT_GET_INDENTRY_INT64(blocks_fetched)
+
+/* pg_stat_get_ind_blocks_hit */
+PG_STAT_GET_INDENTRY_INT64(blocks_hit)
+
+#define PG_STAT_GET_TABENTRY_TIMESTAMPTZ(stat)						\
+Datum																\
+CppConcat(pg_stat_get_tab_,stat)(PG_FUNCTION_ARGS)					\
+{																	\
+	Oid			relid = PG_GETARG_OID(0);							\
+	TimestampTz result;												\
+	PgStat_StatTabEntry *tabentry;									\
+																	\
+	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)		\
+		result = 0;													\
+	else															\
+		result = tabentry->stat;									\
+																	\
+	if (result == 0)												\
+		PG_RETURN_NULL();											\
+	else															\
+		PG_RETURN_TIMESTAMPTZ(result);								\
 }
 
-/* pg_stat_get_last_analyze_time */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_analyze_time)
-
-/* pg_stat_get_last_autoanalyze_time */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_autoanalyze_time)
-
-/* pg_stat_get_last_autovacuum_time */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_autovacuum_time)
+/* pg_stat_get_tab_last_analyze_time */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(last_analyze_time)
 
-/* pg_stat_get_last_vacuum_time */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time)
-
-/* pg_stat_get_lastscan */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan)
+/* pg_stat_get_tab_last_autoanalyze_time */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(last_autoanalyze_time)
+
+/* pg_stat_get_tab_last_autovacuum_time */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(last_autovacuum_time)
+
+/* pg_stat_get_tab_last_vacuum_time */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(last_vacuum_time)
+
+/* pg_stat_get_tab_lastscan */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(lastscan)
+
+#define PG_STAT_GET_INDENTRY_TIMESTAMPTZ(stat)						\
+Datum																\
+CppConcat(pg_stat_get_ind_,stat)(PG_FUNCTION_ARGS)					\
+{																	\
+	Oid			relid = PG_GETARG_OID(0);							\
+	TimestampTz result;												\
+	PgStat_StatIndEntry *indentry;									\
+																	\
+	if ((indentry = pgstat_fetch_stat_indentry(relid)) == NULL)		\
+		result = 0;													\
+	else															\
+		result = indentry->stat;									\
+																	\
+	if (result == 0)												\
+		PG_RETURN_NULL();											\
+	else															\
+		PG_RETURN_TIMESTAMPTZ(result);								\
+}
+
+/* pg_stat_get_ind_lastscan */
+PG_STAT_GET_INDENTRY_TIMESTAMPTZ(lastscan)
 
 Datum
 pg_stat_get_function_calls(PG_FUNCTION_ARGS)
@@ -1308,7 +1361,7 @@ pg_stat_get_slru(PG_FUNCTION_ARGS)
 }
 
 Datum
-pg_stat_get_xact_numscans(PG_FUNCTION_ARGS)
+pg_stat_get_tab_xact_numscans(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
@@ -1322,17 +1375,32 @@ pg_stat_get_xact_numscans(PG_FUNCTION_ARGS)
 	PG_RETURN_INT64(result);
 }
 
+Datum
+pg_stat_get_ind_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);
 }
@@ -1342,12 +1410,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);
 }
@@ -1435,12 +1503,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);
 }
@@ -1450,12 +1518,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);
 }
@@ -1574,7 +1642,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 450e5124a5..f8e47f0f9d 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -2411,7 +2411,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 	RelationCloseSmgr(relation);
 
 	/* break mutual link with stats entry */
-	pgstat_unlink_relation(relation);
+	if (relation->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_unlink_index(relation);
+	else
+		pgstat_unlink_table(relation);
 
 	/*
 	 * Free all the subsidiary data structures of the relcache entry, then the
@@ -2727,8 +2730,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)
@@ -6321,7 +6325,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 719599649a..9b83e14c57 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5274,96 +5274,120 @@
   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_tab_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_tab_numscans' },
+{ oid => '8296', descr => 'statistics: number of scans done for index',
+  proname => 'pg_stat_get_ind_numscans', provolatile => 's', proparallel => 'r',
+  prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_ind_numscans' },
+{ oid => '9976', descr => 'statistics: time of the last scan for table',
+  proname => 'pg_stat_get_tab_lastscan', provolatile => 's', proparallel => 'r',
+  prorettype => 'timestamptz', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tab_lastscan' },
+{ oid => '8626', descr => 'statistics: time of the last scan for index',
+  proname => 'pg_stat_get_ind_lastscan', provolatile => 's', proparallel => 'r',
   prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_lastscan' },
+  prosrc => 'pg_stat_get_ind_lastscan' },
 { oid => '1929', descr => 'statistics: number of tuples read by seqscan',
-  proname => 'pg_stat_get_tuples_returned', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_returned', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_returned' },
+  prosrc => 'pg_stat_get_tab_tuples_returned' },
+{ oid => '9603', descr => 'statistics: number of tuples read by seqscan',
+  proname => 'pg_stat_get_ind_tuples_returned', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_ind_tuples_returned' },
 { oid => '1930', descr => 'statistics: number of tuples fetched by idxscan',
-  proname => 'pg_stat_get_tuples_fetched', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_fetched', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tab_tuples_fetched' },
+{ oid => '8526', descr => 'statistics: number of tuples fetched by idxscan',
+  proname => 'pg_stat_get_ind_tuples_fetched', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_fetched' },
+  prosrc => 'pg_stat_get_ind_tuples_fetched' },
 { oid => '1931', descr => 'statistics: number of tuples inserted',
-  proname => 'pg_stat_get_tuples_inserted', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_inserted', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_inserted' },
+  prosrc => 'pg_stat_get_tab_tuples_inserted' },
 { oid => '1932', descr => 'statistics: number of tuples updated',
-  proname => 'pg_stat_get_tuples_updated', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_updated', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_updated' },
+  prosrc => 'pg_stat_get_tab_tuples_updated' },
 { oid => '1933', descr => 'statistics: number of tuples deleted',
-  proname => 'pg_stat_get_tuples_deleted', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_deleted', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_deleted' },
+  prosrc => 'pg_stat_get_tab_tuples_deleted' },
 { oid => '1972', descr => 'statistics: number of tuples hot updated',
-  proname => 'pg_stat_get_tuples_hot_updated', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_hot_updated', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_hot_updated' },
+  prosrc => 'pg_stat_get_tab_tuples_hot_updated' },
 { oid => '2878', descr => 'statistics: number of live tuples',
-  proname => 'pg_stat_get_live_tuples', provolatile => 's', proparallel => 'r',
+  proname => 'pg_stat_get_tab_live_tuples', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_live_tuples' },
+  prosrc => 'pg_stat_get_tab_live_tuples' },
 { oid => '2879', descr => 'statistics: number of dead tuples',
-  proname => 'pg_stat_get_dead_tuples', provolatile => 's', proparallel => 'r',
+  proname => 'pg_stat_get_tab_dead_tuples', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_dead_tuples' },
+  prosrc => 'pg_stat_get_tab_dead_tuples' },
 { oid => '3177',
   descr => 'statistics: number of tuples changed since last analyze',
-  proname => 'pg_stat_get_mod_since_analyze', provolatile => 's',
+  proname => 'pg_stat_get_tab_mod_since_analyze', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_mod_since_analyze' },
+  prosrc => 'pg_stat_get_tab_mod_since_analyze' },
 { oid => '5053',
   descr => 'statistics: number of tuples inserted since last vacuum',
-  proname => 'pg_stat_get_ins_since_vacuum', provolatile => 's',
+  proname => 'pg_stat_get_tab_ins_since_vacuum', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_ins_since_vacuum' },
+  prosrc => 'pg_stat_get_tab_ins_since_vacuum' },
 { oid => '1934', descr => 'statistics: number of blocks fetched',
-  proname => 'pg_stat_get_blocks_fetched', provolatile => 's',
+  proname => 'pg_stat_get_tab_blocks_fetched', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_blocks_fetched' },
+  prosrc => 'pg_stat_get_tab_blocks_fetched' },
+{ oid => '9432', descr => 'statistics: number of blocks fetched',
+  proname => 'pg_stat_get_ind_blocks_fetched', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_ind_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_tab_blocks_hit', provolatile => 's', proparallel => 'r',
+  prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tab_blocks_hit' },
+{ oid => '8354', descr => 'statistics: number of blocks found in cache',
+  proname => 'pg_stat_get_ind_blocks_hit', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_blocks_hit' },
+  prosrc => 'pg_stat_get_ind_blocks_hit' },
 { oid => '2781', descr => 'statistics: last manual vacuum time for a table',
-  proname => 'pg_stat_get_last_vacuum_time', provolatile => 's',
+  proname => 'pg_stat_get_tab_last_vacuum_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_last_vacuum_time' },
+  prosrc => 'pg_stat_get_tab_last_vacuum_time' },
 { oid => '2782', descr => 'statistics: last auto vacuum time for a table',
-  proname => 'pg_stat_get_last_autovacuum_time', provolatile => 's',
+  proname => 'pg_stat_get_tab_last_autovacuum_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_last_autovacuum_time' },
+  prosrc => 'pg_stat_get_tab_last_autovacuum_time' },
 { oid => '2783', descr => 'statistics: last manual analyze time for a table',
-  proname => 'pg_stat_get_last_analyze_time', provolatile => 's',
+  proname => 'pg_stat_get_tab_last_analyze_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_last_analyze_time' },
+  prosrc => 'pg_stat_get_tab_last_analyze_time' },
 { oid => '2784', descr => 'statistics: last auto analyze time for a table',
-  proname => 'pg_stat_get_last_autoanalyze_time', provolatile => 's',
+  proname => 'pg_stat_get_tab_last_autoanalyze_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_last_autoanalyze_time' },
+  prosrc => 'pg_stat_get_tab_last_autoanalyze_time' },
 { oid => '3054', descr => 'statistics: number of manual vacuums for a table',
-  proname => 'pg_stat_get_vacuum_count', provolatile => 's', proparallel => 'r',
+  proname => 'pg_stat_get_tab_vacuum_count', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_vacuum_count' },
+  prosrc => 'pg_stat_get_tab_vacuum_count' },
 { oid => '3055', descr => 'statistics: number of auto vacuums for a table',
-  proname => 'pg_stat_get_autovacuum_count', provolatile => 's',
+  proname => 'pg_stat_get_tab_autovacuum_count', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_autovacuum_count' },
+  prosrc => 'pg_stat_get_tab_autovacuum_count' },
 { oid => '3056', descr => 'statistics: number of manual analyzes for a table',
-  proname => 'pg_stat_get_analyze_count', provolatile => 's',
+  proname => 'pg_stat_get_tab_analyze_count', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_analyze_count' },
+  prosrc => 'pg_stat_get_tab_analyze_count' },
 { oid => '3057', descr => 'statistics: number of auto analyzes for a table',
-  proname => 'pg_stat_get_autoanalyze_count', provolatile => 's',
+  proname => 'pg_stat_get_tab_autoanalyze_count', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_autoanalyze_count' },
+  prosrc => 'pg_stat_get_tab_autoanalyze_count' },
 { oid => '1936', descr => 'statistics: currently active backend IDs',
   proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't',
   provolatile => 's', proparallel => 'r', prorettype => 'int4',
@@ -5719,10 +5743,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_tab_xact_numscans', provolatile => 'v',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tab_xact_numscans' },
+{ oid => '8892',
+  descr => 'statistics: number of scans done for index in current transaction',
+  proname => 'pg_stat_get_ind_xact_numscans', provolatile => 'v',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_xact_numscans' },
+  prosrc => 'pg_stat_get_ind_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 a3df8d27c3..a35465bc92 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 autoanalyze_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;
@@ -494,16 +541,15 @@ extern PgStat_BackendFunctionEntry *find_funcstat_entry(Oid func_id);
 
 
 /*
- * Functions in pgstat_relation.c
+ * Functions in pgstat_table.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_drop_table(Relation rel);
+extern void pgstat_init_table(Relation rel);
 
-extern void pgstat_init_relation(Relation rel);
-extern void pgstat_assoc_relation(Relation rel);
-extern void pgstat_unlink_relation(Relation rel);
+extern void pgstat_assoc_table(Relation rel);
+extern void pgstat_unlink_table(Relation rel);
 
 extern void pgstat_report_vacuum(Oid tableoid, bool shared,
 								 PgStat_Counter livetuples, PgStat_Counter deadtuples);
@@ -511,53 +557,83 @@ extern void pgstat_report_analyze(Relation rel,
 								  PgStat_Counter livetuples, PgStat_Counter deadtuples,
 								  bool resetcounter);
 
+/*
+ * Functions in pgstat_index.c
+ */
+
+extern void pgstat_create_index(Relation rel);
+extern void pgstat_drop_index(Relation rel);
+extern void pgstat_copy_index_stats(Relation dst, Relation src);
+extern void pgstat_init_index(Relation rel);
+extern void pgstat_assoc_index(Relation rel);
+extern void pgstat_unlink_index(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_table()
+ * and pgstat_init_index().
  */
-#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 +649,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..004e5412ab 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
 {
@@ -550,16 +556,23 @@ extern bool pgstat_function_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
 
 
 /*
- * Functions in pgstat_relation.c
+ * Functions in pgstat_table.c
  */
 
-extern void AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit);
-extern void AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth);
-extern void AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state);
-extern void PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state);
+extern void AtEOXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit);
+extern void AtEOSubXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth);
+extern void AtPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state);
+extern void PostPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state);
+
+extern bool pgstat_table_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+extern void pgstat_table_delete_pending_cb(PgStat_EntryRef *entry_ref);
+
+/*
+ * Functions in pgstat_index.c
+ */
 
-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_index_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+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..494adee88b 100644
--- a/src/test/isolation/expected/stats.out
+++ b/src/test/isolation/expected/stats.out
@@ -2166,14 +2166,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2210,14 +2210,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2236,14 +2236,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2275,14 +2275,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2314,14 +2314,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2369,14 +2369,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2401,14 +2401,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2463,14 +2463,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2495,14 +2495,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2558,14 +2558,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2627,14 +2627,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2688,14 +2688,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2755,14 +2755,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2795,14 +2795,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2841,14 +2841,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2881,14 +2881,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2927,14 +2927,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2968,14 +2968,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -3015,14 +3015,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
diff --git a/src/test/isolation/expected/stats_1.out b/src/test/isolation/expected/stats_1.out
index 3854320106..7981747732 100644
--- a/src/test/isolation/expected/stats_1.out
+++ b/src/test/isolation/expected/stats_1.out
@@ -2174,14 +2174,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2218,14 +2218,14 @@ 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_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2244,14 +2244,14 @@ 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_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2283,14 +2283,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2322,14 +2322,14 @@ 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_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2377,14 +2377,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2409,14 +2409,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2471,14 +2471,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2503,14 +2503,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2566,14 +2566,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2635,14 +2635,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2698,14 +2698,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2767,14 +2767,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2809,14 +2809,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2857,14 +2857,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2899,14 +2899,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2947,14 +2947,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2990,14 +2990,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -3039,14 +3039,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
diff --git a/src/test/isolation/specs/stats.spec b/src/test/isolation/specs/stats.spec
index 5b922d788c..53d78f7db1 100644
--- a/src/test/isolation/specs/stats.spec
+++ b/src/test/isolation/specs/stats.spec
@@ -93,14 +93,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 }
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 fb9f936d43..9c2b813789 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1767,10 +1767,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_ind_numscans(i.oid) AS idx_scan,
+    pg_stat_get_ind_lastscan(i.oid) AS last_idx_scan,
+    pg_stat_get_ind_tuples_returned(i.oid) AS idx_tup_read,
+    pg_stat_get_ind_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)))
@@ -1779,28 +1779,28 @@ 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_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,
-    pg_stat_get_tuples_hot_updated(c.oid) AS n_tup_hot_upd,
-    pg_stat_get_live_tuples(c.oid) AS n_live_tup,
-    pg_stat_get_dead_tuples(c.oid) AS n_dead_tup,
-    pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze,
-    pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum,
-    pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum,
-    pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum,
-    pg_stat_get_last_analyze_time(c.oid) AS last_analyze,
-    pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze,
-    pg_stat_get_vacuum_count(c.oid) AS vacuum_count,
-    pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count,
-    pg_stat_get_analyze_count(c.oid) AS analyze_count,
-    pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count
+    pg_stat_get_tab_numscans(c.oid) AS seq_scan,
+    pg_stat_get_tab_lastscan(c.oid) AS last_seq_scan,
+    pg_stat_get_tab_tuples_returned(c.oid) AS seq_tup_read,
+    (sum(pg_stat_get_ind_numscans(i.indexrelid)))::bigint AS idx_scan,
+    max(pg_stat_get_ind_lastscan(i.indexrelid)) AS last_idx_scan,
+    ((sum(pg_stat_get_ind_tuples_fetched(i.indexrelid)))::bigint + pg_stat_get_tab_tuples_fetched(c.oid)) AS idx_tup_fetch,
+    pg_stat_get_tab_tuples_inserted(c.oid) AS n_tup_ins,
+    pg_stat_get_tab_tuples_updated(c.oid) AS n_tup_upd,
+    pg_stat_get_tab_tuples_deleted(c.oid) AS n_tup_del,
+    pg_stat_get_tab_tuples_hot_updated(c.oid) AS n_tup_hot_upd,
+    pg_stat_get_tab_live_tuples(c.oid) AS n_live_tup,
+    pg_stat_get_tab_dead_tuples(c.oid) AS n_dead_tup,
+    pg_stat_get_tab_mod_since_analyze(c.oid) AS n_mod_since_analyze,
+    pg_stat_get_tab_ins_since_vacuum(c.oid) AS n_ins_since_vacuum,
+    pg_stat_get_tab_last_vacuum_time(c.oid) AS last_vacuum,
+    pg_stat_get_tab_last_autovacuum_time(c.oid) AS last_autovacuum,
+    pg_stat_get_tab_last_analyze_time(c.oid) AS last_analyze,
+    pg_stat_get_tab_last_autoanalyze_time(c.oid) AS last_autoanalyze,
+    pg_stat_get_tab_vacuum_count(c.oid) AS vacuum_count,
+    pg_stat_get_tab_autovacuum_count(c.oid) AS autovacuum_count,
+    pg_stat_get_tab_analyze_count(c.oid) AS analyze_count,
+    pg_stat_get_tab_autoanalyze_count(c.oid) AS autoanalyze_count
    FROM ((pg_class c
      LEFT JOIN pg_index i ON ((c.oid = i.indrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
@@ -2224,9 +2224,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_tab_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_ind_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,
@@ -2277,8 +2277,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_ind_blocks_fetched(i.oid) - pg_stat_get_ind_blocks_hit(i.oid)) AS idx_blks_read,
+    pg_stat_get_ind_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)))
@@ -2287,31 +2287,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_ind_blocks_fetched(c.oid) - pg_stat_get_tab_blocks_hit(c.oid)) AS blks_read,
+    pg_stat_get_tab_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_tab_blocks_fetched(c.oid) - pg_stat_get_tab_blocks_hit(c.oid)) AS heap_blks_read,
+    pg_stat_get_tab_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_tab_blocks_fetched(t.oid) - pg_stat_get_tab_blocks_hit(t.oid)) AS toast_blks_read,
+    pg_stat_get_tab_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_ind_blocks_fetched(pg_index.indexrelid) - pg_stat_get_ind_blocks_hit(pg_index.indexrelid))))::bigint AS idx_blks_read,
+            (sum(pg_stat_get_ind_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_ind_blocks_fetched(pg_index.indexrelid) - pg_stat_get_ind_blocks_hit(pg_index.indexrelid))))::bigint AS idx_blks_read,
+            (sum(pg_stat_get_ind_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 1d84407a03..d5ee0f4845 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;$$;
@@ -357,17 +468,17 @@ SELECT pg_stat_force_next_flush();
  
 (1 row)
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           1
 (1 row)
 
 DROP TABLE drop_stats_test;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       0
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           0
 (1 row)
 
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_oid);
@@ -378,18 +489,18 @@ SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_oid);
 
 -- check that rollback protects against having stats dropped and that local
 -- modifications don't pose a problem
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
-(1 row)
-
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_tuples_inserted 
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_live_tuples 
 -----------------------------
                            1
 (1 row)
 
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_tuples_inserted 
+---------------------------------
+                               1
+(1 row)
+
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
  pg_stat_get_xact_tuples_inserted 
 ----------------------------------
@@ -418,29 +529,29 @@ SELECT pg_stat_force_next_flush();
  
 (1 row)
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           1
 (1 row)
 
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_tuples_inserted 
------------------------------
-                           2
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_tuples_inserted 
+---------------------------------
+                               2
 (1 row)
 
 -- transactional drop
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           1
 (1 row)
 
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_tuples_inserted 
------------------------------
-                           2
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_tuples_inserted 
+---------------------------------
+                               2
 (1 row)
 
 BEGIN;
@@ -465,23 +576,23 @@ SELECT pg_stat_force_next_flush();
  
 (1 row)
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       0
-(1 row)
-
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_tuples_inserted 
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_live_tuples 
 -----------------------------
                            0
 (1 row)
 
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_tuples_inserted 
+---------------------------------
+                               0
+(1 row)
+
 -- savepoint rollback (2 levels)
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           1
 (1 row)
 
 BEGIN;
@@ -510,17 +621,17 @@ SELECT pg_stat_force_next_flush();
  
 (1 row)
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       3
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           3
 (1 row)
 
 -- savepoint rolback (1 level)
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       3
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           3
 (1 row)
 
 BEGIN;
@@ -529,17 +640,17 @@ DROP TABLE drop_stats_test_subxact;
 SAVEPOINT sp2;
 ROLLBACK TO SAVEPOINT sp1;
 COMMIT;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       3
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           3
 (1 row)
 
 -- and now actually drop
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       3
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           3
 (1 row)
 
 BEGIN;
@@ -548,10 +659,10 @@ DROP TABLE drop_stats_test_subxact;
 SAVEPOINT sp2;
 RELEASE SAVEPOINT sp1;
 COMMIT;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       0
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           0
 (1 row)
 
 DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
@@ -1021,21 +1132,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
@@ -1051,14 +1162,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
@@ -1073,7 +1184,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
@@ -1081,7 +1192,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
@@ -1089,7 +1200,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
@@ -1097,7 +1208,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
@@ -1105,7 +1216,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 b4d6753c71..1e9ae74b2a 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
 ---
@@ -218,15 +289,15 @@ SELECT 'drop_stats_test_subxact'::regclass::oid AS drop_stats_test_subxact_oid \
 
 SELECT pg_stat_force_next_flush();
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_oid);
 DROP TABLE drop_stats_test;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_oid);
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_oid);
 
 -- check that rollback protects against having stats dropped and that local
 -- modifications don't pose a problem
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
 BEGIN;
 INSERT INTO drop_stats_test_xact DEFAULT VALUES;
@@ -235,12 +306,12 @@ DROP TABLE drop_stats_test_xact;
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
 ROLLBACK;
 SELECT pg_stat_force_next_flush();
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
 
 -- transactional drop
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
 BEGIN;
 INSERT INTO drop_stats_test_xact DEFAULT VALUES;
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
@@ -248,11 +319,11 @@ DROP TABLE drop_stats_test_xact;
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
 COMMIT;
 SELECT pg_stat_force_next_flush();
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
 
 -- savepoint rollback (2 levels)
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 BEGIN;
 INSERT INTO drop_stats_test_subxact DEFAULT VALUES;
 SAVEPOINT sp1;
@@ -264,27 +335,27 @@ ROLLBACK TO SAVEPOINT sp2;
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_subxact_oid);
 COMMIT;
 SELECT pg_stat_force_next_flush();
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 
 -- savepoint rolback (1 level)
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 BEGIN;
 SAVEPOINT sp1;
 DROP TABLE drop_stats_test_subxact;
 SAVEPOINT sp2;
 ROLLBACK TO SAVEPOINT sp1;
 COMMIT;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 
 -- and now actually drop
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 BEGIN;
 SAVEPOINT sp1;
 DROP TABLE drop_stats_test_subxact;
 SAVEPOINT sp2;
 RELEASE SAVEPOINT sp1;
 COMMIT;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 
 DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
 DROP TABLE prevstats;
@@ -493,40 +564,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 60c71d05fe..3be9c0e1ff 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
@@ -2026,6 +2027,7 @@ PgStat_FetchConsistency
 PgStat_FunctionCallUsage
 PgStat_FunctionCounts
 PgStat_HashKey
+PgStat_IndexStatus
 PgStat_Kind
 PgStat_KindInfo
 PgStat_LocalState
#17Drouvot, Bertrand
bertranddrouvot.pg@gmail.com
In reply to: Drouvot, Bertrand (#16)
1 attachment(s)
Re: Split index and table statistics into different types of stats

Hi,

On 12/10/22 10:54 AM, Drouvot, Bertrand wrote:

Hi,

On 12/7/22 11:11 AM, Drouvot, Bertrand wrote:

Hi,

Hi,

As [1] mentioned above has been committed (83a1a1b566), please find attached V5 related to this thread making use of the new macros (namely PG_STAT_GET_RELENTRY_INT64 and PG_STAT_GET_RELENTRY_TIMESTAMPTZ).

I switched from using "CppConcat" to using "##", as it looks to me it's easier to read now that we are adding another concatenation to the game (due to the table/index split).

The (Tab,tab) or (Ind,ind) passed as arguments to the macros look "weird" (I don't have a better idea yet): purpose is to follow the naming convention for PgStat_StatTabEntry/PgStat_StatIndEntry and pgstat_fetch_stat_tabentry/pgstat_fetch_stat_indentry, thoughts?

Looking forward to your feedback,

Attaching V6 (mandatory rebase due to 8018ffbf58).

While at it, I got rid of the weirdness mentioned above by creating 2 sets of macros (one for the tables and one for the indexes).

Looking forward to your feedback,

Regards,

Attaching V7, mandatory rebase due to 66dcb09246.

Attaching V8, mandatory rebase due to c8e1ba736b.

Regards,

--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

Attachments:

v8-0001-split_tables_indexes_stats.patchtext/plain; charset=UTF-8; name=v8-0001-split_tables_indexes_stats.patchDownload
diff --git a/src/backend/access/common/relation.c b/src/backend/access/common/relation.c
index 4017e175e3..fca166a063 100644
--- a/src/backend/access/common/relation.c
+++ b/src/backend/access/common/relation.c
@@ -73,7 +73,10 @@ relation_open(Oid relationId, LOCKMODE lockmode)
 	if (RelationUsesLocalBuffers(r))
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
-	pgstat_init_relation(r);
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_init_index(r);
+	else
+		pgstat_init_table(r);
 
 	return r;
 }
@@ -123,7 +126,10 @@ try_relation_open(Oid relationId, LOCKMODE lockmode)
 	if (RelationUsesLocalBuffers(r))
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
-	pgstat_init_relation(r);
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_init_index(r);
+	else
+		pgstat_init_table(r);
 
 	return r;
 }
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index dbf147ed22..5b83146eef 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -590,7 +590,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 4f006820b8..4e9569c901 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 e6579f2979..60d36f7f2f 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 447c9b970f..1db3dba77b 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -655,29 +655,29 @@ 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_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,
-            pg_stat_get_tuples_hot_updated(C.oid) AS n_tup_hot_upd,
-            pg_stat_get_live_tuples(C.oid) AS n_live_tup,
-            pg_stat_get_dead_tuples(C.oid) AS n_dead_tup,
-            pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze,
-            pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum,
-            pg_stat_get_last_vacuum_time(C.oid) as last_vacuum,
-            pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum,
-            pg_stat_get_last_analyze_time(C.oid) as last_analyze,
-            pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze,
-            pg_stat_get_vacuum_count(C.oid) AS vacuum_count,
-            pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count,
-            pg_stat_get_analyze_count(C.oid) AS analyze_count,
-            pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count
+            pg_stat_get_tab_numscans(C.oid) AS seq_scan,
+            pg_stat_get_tab_lastscan(C.oid) AS last_seq_scan,
+            pg_stat_get_tab_tuples_returned(C.oid) AS seq_tup_read,
+            sum(pg_stat_get_ind_numscans(I.indexrelid))::bigint AS idx_scan,
+            max(pg_stat_get_ind_lastscan(I.indexrelid)) AS last_idx_scan,
+            sum(pg_stat_get_ind_tuples_fetched(I.indexrelid))::bigint +
+            pg_stat_get_tab_tuples_fetched(C.oid) AS idx_tup_fetch,
+            pg_stat_get_tab_tuples_inserted(C.oid) AS n_tup_ins,
+            pg_stat_get_tab_tuples_updated(C.oid) AS n_tup_upd,
+            pg_stat_get_tab_tuples_deleted(C.oid) AS n_tup_del,
+            pg_stat_get_tab_tuples_hot_updated(C.oid) AS n_tup_hot_upd,
+            pg_stat_get_tab_live_tuples(C.oid) AS n_live_tup,
+            pg_stat_get_tab_dead_tuples(C.oid) AS n_dead_tup,
+            pg_stat_get_tab_mod_since_analyze(C.oid) AS n_mod_since_analyze,
+            pg_stat_get_tab_ins_since_vacuum(C.oid) AS n_ins_since_vacuum,
+            pg_stat_get_tab_last_vacuum_time(C.oid) as last_vacuum,
+            pg_stat_get_tab_last_autovacuum_time(C.oid) as last_autovacuum,
+            pg_stat_get_tab_last_analyze_time(C.oid) as last_analyze,
+            pg_stat_get_tab_last_autoanalyze_time(C.oid) as last_autoanalyze,
+            pg_stat_get_tab_vacuum_count(C.oid) AS vacuum_count,
+            pg_stat_get_tab_autovacuum_count(C.oid) AS autovacuum_count,
+            pg_stat_get_tab_analyze_count(C.oid) AS analyze_count,
+            pg_stat_get_tab_autoanalyze_count(C.oid) AS autoanalyze_count
     FROM pg_class C LEFT JOIN
          pg_index I ON C.oid = I.indrelid
          LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
@@ -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_tab_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_ind_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_tab_blocks_fetched(C.oid) -
+                    pg_stat_get_tab_blocks_hit(C.oid) AS heap_blks_read,
+            pg_stat_get_tab_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_tab_blocks_fetched(T.oid) -
+                    pg_stat_get_tab_blocks_hit(T.oid) AS toast_blks_read,
+            pg_stat_get_tab_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_ind_blocks_fetched(indexrelid) -
+                         pg_stat_get_ind_blocks_hit(indexrelid))::bigint
                      AS idx_blks_read,
-                     sum(pg_stat_get_blocks_hit(indexrelid))::bigint
+                     sum(pg_stat_get_ind_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_ind_blocks_fetched(indexrelid) -
+                         pg_stat_get_ind_blocks_hit(indexrelid))::bigint
                      AS idx_blks_read,
-                     sum(pg_stat_get_blocks_hit(indexrelid))::bigint
+                     sum(pg_stat_get_ind_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_ind_numscans(I.oid) AS idx_scan,
+            pg_stat_get_ind_lastscan(I.oid) AS last_idx_scan,
+            pg_stat_get_ind_tuples_returned(I.oid) AS idx_tup_read,
+            pg_stat_get_ind_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_ind_blocks_fetched(I.oid) -
+                    pg_stat_get_ind_blocks_hit(I.oid) AS idx_blks_read,
+            pg_stat_get_ind_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_ind_blocks_fetched(C.oid) -
+                    pg_stat_get_tab_blocks_hit(C.oid) AS blks_read,
+            pg_stat_get_tab_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 3fb38a25cf..98bb230b95 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/Makefile b/src/backend/utils/activity/Makefile
index a80eda3cf4..89b8447816 100644
--- a/src/backend/utils/activity/Makefile
+++ b/src/backend/utils/activity/Makefile
@@ -22,11 +22,12 @@ OBJS = \
 	pgstat_checkpointer.o \
 	pgstat_database.o \
 	pgstat_function.o \
-	pgstat_relation.o \
+	pgstat_index.o \
 	pgstat_replslot.o \
 	pgstat_shmem.o \
 	pgstat_slru.o \
 	pgstat_subscription.o \
+	pgstat_table.o \
 	pgstat_wal.o \
 	pgstat_xact.o \
 	wait_event.o
diff --git a/src/backend/utils/activity/meson.build b/src/backend/utils/activity/meson.build
index a2b872c24b..617cf909db 100644
--- a/src/backend/utils/activity/meson.build
+++ b/src/backend/utils/activity/meson.build
@@ -9,11 +9,12 @@ backend_sources += files(
   'pgstat_checkpointer.c',
   'pgstat_database.c',
   'pgstat_function.c',
-  'pgstat_relation.c',
+  'pgstat_index.c',
   'pgstat_replslot.c',
   'pgstat_shmem.c',
   'pgstat_slru.c',
   'pgstat_subscription.c',
+  'pgstat_table.c',
   'pgstat_wal.c',
   'pgstat_xact.c',
   'wait_event.c',
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index 7e9dc17e68..6e2690e992 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -72,10 +72,11 @@
  * - pgstat_checkpointer.c
  * - pgstat_database.c
  * - pgstat_function.c
- * - pgstat_relation.c
+ * - pgstat_index.c
  * - pgstat_replslot.c
  * - pgstat_slru.c
  * - pgstat_subscription.c
+ * - pgstat_table.c
  * - pgstat_wal.c
  *
  * Whenever possible infrastructure files should not contain code related to
@@ -269,18 +270,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_index.c b/src/backend/utils/activity/pgstat_index.c
new file mode 100644
index 0000000000..882f6cf30d
--- /dev/null
+++ b/src/backend/utils/activity/pgstat_index.c
@@ -0,0 +1,297 @@
+/* -------------------------------------------------------------------------
+ *
+ * pgstat_index.c
+ *	  Implementation of index statistics.
+ *
+ * This file contains the implementation of function index. It is kept
+ * separate from pgstat.c to enforce the line between the statistics access /
+ * storage implementation and the details about individual types of
+ * statistics.
+ *
+ * Copyright (c) 2001-2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/activity/pgstat_index.c
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/xact.h"
+#include "utils/pgstat_internal.h"
+#include "utils/rel.h"
+#include "catalog/catalog.h"
+
+static PgStat_IndexStatus *pgstat_prep_index_pending(Oid rel_id, bool isshared);
+
+/*
+ * Copy stats between indexes. This is used for things like REINDEX
+ * CONCURRENTLY.
+ */
+void
+pgstat_copy_index_stats(Relation dst, Relation src)
+{
+	PgStat_StatIndEntry *srcstats;
+	PgStatShared_Index *dstshstats;
+	PgStat_EntryRef *dst_ref;
+
+	srcstats = pgstat_fetch_stat_indentry_ext(src->rd_rel->relisshared,
+											  RelationGetRelid(src));
+	if (!srcstats)
+		return;
+
+	dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_INDEX,
+										  dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+										  RelationGetRelid(dst),
+										  false);
+
+	dstshstats = (PgStatShared_Index *) dst_ref->shared_stats;
+	dstshstats->stats = *srcstats;
+
+	pgstat_unlock_entry(dst_ref);
+}
+
+/*
+ * Initialize a relcache entry to count access statistics.  Called whenever an
+ * index is opened.
+ *
+ * We assume that a relcache entry's pgstatind_info field is 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
+ * pgstat_assoc_index().
+ */
+void
+pgstat_init_index(Relation rel)
+{
+	/*
+	 * We only count stats for indexes
+	 */
+	Assert(rel->rd_rel->relkind == RELKIND_INDEX);
+
+	if (!pgstat_track_counts)
+	{
+		if (rel->pgstatind_info != NULL)
+			pgstat_unlink_index(rel);
+
+		/* We're not counting at all */
+		rel->pgstat_enabled = false;
+		rel->pgstatind_info = NULL;
+		return;
+	}
+
+	rel->pgstat_enabled = true;
+}
+
+/*
+ * 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_index() 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->pgstatind_info->relation == NULL);
+
+	/* mark this relation as the owner */
+	rel->pgstatind_info->relation = rel;
+}
+
+/*
+ * Break the mutual link between a relcache entry and pending index stats entry.
+ * This must be called whenever one end of the link is removed.
+ */
+void
+pgstat_unlink_index(Relation rel)
+{
+
+	if (rel->pgstatind_info == NULL)
+		return;
+
+	/* 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 index stats are dropped if transaction aborts.
+ */
+void
+pgstat_create_index(Relation rel)
+{
+	pgstat_create_transactional(PGSTAT_KIND_INDEX,
+								rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+								RelationGetRelid(rel));
+}
+
+/*
+ * Ensure that index stats are dropped if transaction commits.
+ */
+void
+pgstat_drop_index(Relation rel)
+{
+	pgstat_drop_transactional(PGSTAT_KIND_INDEX,
+							  rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+							  RelationGetRelid(rel));
+}
+
+/*
+ * 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)
+{
+	return pgstat_fetch_stat_indentry_ext(IsSharedRelation(relid), relid);
+}
+
+/*
+ * 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);
+}
+
+/*
+ * 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;
+	return NULL;
+}
+
+/*
+ * 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->tuples_returned += lstats->i_counts.i_tuples_returned;
+	dbentry->tuples_fetched += lstats->i_counts.i_tuples_fetched;
+	dbentry->blocks_fetched += lstats->i_counts.i_blocks_fetched;
+	dbentry->blocks_hit += lstats->i_counts.i_blocks_hit;
+
+	return true;
+}
+
+void
+pgstat_index_delete_pending_cb(PgStat_EntryRef *entry_ref)
+{
+	PgStat_IndexStatus *pending = (PgStat_IndexStatus *) entry_ref->pending;
+
+	if (pending->relation)
+		pgstat_unlink_index(pending->relation);
+}
+
+/*
+ * 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;
+}
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_table.c
similarity index 75%
rename from src/backend/utils/activity/pgstat_relation.c
rename to src/backend/utils/activity/pgstat_table.c
index 1730425de1..1b9da8d911 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_table.c
@@ -1,9 +1,9 @@
 /* -------------------------------------------------------------------------
  *
- * pgstat_relation.c
- *	  Implementation of relation statistics.
+ * pgstat_table.c
+ *	  Implementation of table statistics.
  *
- * This file contains the implementation of function relation. It is kept
+ * This file contains the implementation of function table. It is kept
  * separate from pgstat.c to enforce the line between the statistics access /
  * storage implementation and the details about individual types of
  * statistics.
@@ -11,7 +11,7 @@
  * Copyright (c) 2001-2023, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *	  src/backend/utils/activity/pgstat_relation.c
+ *	  src/backend/utils/activity/pgstat_table.c
  * -------------------------------------------------------------------------
  */
 
@@ -44,74 +44,48 @@ 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 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
- * CONCURRENTLY.
- */
-void
-pgstat_copy_relation_stats(Relation dst, Relation src)
-{
-	PgStat_StatTabEntry *srcstats;
-	PgStatShared_Relation *dstshstats;
-	PgStat_EntryRef *dst_ref;
-
-	srcstats = pgstat_fetch_stat_tabentry_ext(src->rd_rel->relisshared,
-											  RelationGetRelid(src));
-	if (!srcstats)
-		return;
-
-	dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
-										  dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
-										  RelationGetRelid(dst),
-										  false);
-
-	dstshstats = (PgStatShared_Relation *) dst_ref->shared_stats;
-	dstshstats->stats = *srcstats;
-
-	pgstat_unlock_entry(dst_ref);
-}
-
 /*
  * Initialize a relcache entry to count access statistics.  Called whenever a
- * relation is opened.
+ * table is opened.
  *
- * We assume that a relcache entry's pgstat_info field is zeroed by relcache.c
+ * We assume that a relcache entry's pgstattab_info field is 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
- * pgstat_assoc_relation().
+ * pgstat_assoc_table().
  */
 void
-pgstat_init_relation(Relation rel)
+pgstat_init_table(Relation rel)
 {
 	char		relkind = rel->rd_rel->relkind;
 
+	Assert(relkind != RELKIND_INDEX);
+
 	/*
 	 * We only count stats for relations with storage and partitioned tables
 	 */
 	if (!RELKIND_HAS_STORAGE(relkind) && relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		rel->pgstat_enabled = false;
-		rel->pgstat_info = NULL;
+		rel->pgstattab_info = NULL;
 		return;
 	}
 
 	if (!pgstat_track_counts)
 	{
-		if (rel->pgstat_info)
-			pgstat_unlink_relation(rel);
+		if (rel->pgstattab_info != NULL)
+			pgstat_unlink_table(rel);
 
 		/* We're not counting at all */
 		rel->pgstat_enabled = false;
-		rel->pgstat_info = NULL;
+		rel->pgstattab_info = NULL;
 		return;
 	}
 
@@ -119,89 +93,92 @@ 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.
  *
- * This is separate from pgstat_init_relation() as it is not uncommon for
+ * This is separate from pgstat_init_table() as it is not uncommon for
  * 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->pgstat_info->relation == NULL);
+	Assert(rel->pgstattab_info->relation == NULL);
 
 	/* mark this relation as the owner */
-	rel->pgstat_info->relation = rel;
+	rel->pgstattab_info->relation = rel;
 }
 
 /*
- * Break the mutual link between a relcache entry and pending stats entry.
+ * Break the mutual link between a relcache entry and pending table stats entry.
  * This must be called whenever one end of the link is removed.
  */
 void
-pgstat_unlink_relation(Relation rel)
+pgstat_unlink_table(Relation rel)
 {
-	/* remove the link to stats info if any */
-	if (rel->pgstat_info == NULL)
+
+	if (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;
+	}
 }
 
 /*
- * Ensure that stats are dropped if transaction aborts.
+ * Ensure that table stats are dropped if transaction aborts.
  */
 void
-pgstat_create_relation(Relation rel)
+pgstat_create_table(Relation rel)
 {
-	pgstat_create_transactional(PGSTAT_KIND_RELATION,
+	pgstat_create_transactional(PGSTAT_KIND_TABLE,
 								rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
 								RelationGetRelid(rel));
 }
 
 /*
- * Ensure that stats are dropped if transaction commits.
+ * Ensure that table stats are dropped if transaction commits.
  */
 void
-pgstat_drop_relation(Relation rel)
+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;
 	}
 }
 
@@ -213,7 +190,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;
@@ -225,10 +202,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->live_tuples = livetuples;
@@ -272,7 +249,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);
 
@@ -291,31 +268,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->live_tuples = livetuples;
@@ -349,12 +326,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;
 	}
 }
 
@@ -364,16 +341,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++;
 	}
 }
 
@@ -383,12 +360,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++;
 	}
 }
 
@@ -398,15 +375,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;
 	}
 }
 
@@ -421,11 +398,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;
 	}
 }
 
@@ -451,7 +428,7 @@ 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);
 }
 
 /*
@@ -467,9 +444,9 @@ 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;
@@ -477,7 +454,7 @@ find_tabstat_entry(Oid rel_id)
 }
 
 /*
- * Perform relation stats specific end-of-transaction work. Helper for
+ * Perform table stats specific end-of-transaction work. Helper for
  * AtEOXact_PgStat.
  *
  * Transfer transactional insert/update counts into the base tabstat entries.
@@ -485,7 +462,7 @@ find_tabstat_entry(Oid rel_id)
  * TopTransactionContext and will go away anyway.
  */
 void
-AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit)
+AtEOXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit)
 {
 	PgStat_TableXactStatus *trans;
 
@@ -536,14 +513,14 @@ AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit)
 }
 
 /*
- * Perform relation stats specific end-of-sub-transaction work. Helper for
+ * Perform table stats specific end-of-sub-transaction work. Helper for
  * AtEOSubXact_PgStat.
  *
  * Transfer transactional insert/update counts into the next higher
  * subtransaction state.
  */
 void
-AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
+AtEOSubXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
 {
 	PgStat_TableXactStatus *trans;
 	PgStat_TableXactStatus *next_trans;
@@ -620,11 +597,11 @@ AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, in
 }
 
 /*
- * Generate 2PC records for all the pending transaction-dependent relation
+ * Generate 2PC records for all the pending transaction-dependent table
  * stats.
  */
 void
-AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
+AtPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state)
 {
 	PgStat_TableXactStatus *trans;
 
@@ -659,10 +636,10 @@ AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
  * reported to the stats system immediately, while the effects on live and
  * dead tuple counts are preserved in the 2PC state file.
  *
- * Note: AtEOXact_PgStat_Relations is not called during PREPARE.
+ * Note: AtEOXact_PgStat_Tables is not called during PREPARE.
  */
 void
-PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
+PostPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state)
 {
 	PgStat_TableXactStatus *trans;
 
@@ -685,27 +662,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;
 }
@@ -721,10 +698,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)
@@ -733,10 +710,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;
 }
 
@@ -750,22 +727,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)
@@ -783,6 +759,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;
 	}
@@ -831,12 +808,12 @@ pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
 }
 
 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;
 
 	if (pending->relation)
-		pgstat_unlink_relation(pending->relation);
+		pgstat_unlink_table(pending->relation);
 }
 
 /*
@@ -844,12 +821,12 @@ pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref)
  * 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;
@@ -863,7 +840,7 @@ pgstat_prep_relation_pending(Oid rel_id, bool isshared)
  * 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;
@@ -879,24 +856,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/activity/pgstat_xact.c b/src/backend/utils/activity/pgstat_xact.c
index 91cdd9222e..ee43362b73 100644
--- a/src/backend/utils/activity/pgstat_xact.c
+++ b/src/backend/utils/activity/pgstat_xact.c
@@ -51,7 +51,7 @@ AtEOXact_PgStat(bool isCommit, bool parallel)
 		Assert(xact_state->nest_level == 1);
 		Assert(xact_state->prev == NULL);
 
-		AtEOXact_PgStat_Relations(xact_state, isCommit);
+		AtEOXact_PgStat_Tables(xact_state, isCommit);
 		AtEOXact_PgStat_DroppedStats(xact_state, isCommit);
 	}
 	pgStatXactStack = NULL;
@@ -122,7 +122,7 @@ AtEOSubXact_PgStat(bool isCommit, int nestDepth)
 		/* delink xact_state from stack immediately to simplify reuse case */
 		pgStatXactStack = xact_state->prev;
 
-		AtEOSubXact_PgStat_Relations(xact_state, isCommit, nestDepth);
+		AtEOSubXact_PgStat_Tables(xact_state, isCommit, nestDepth);
 		AtEOSubXact_PgStat_DroppedStats(xact_state, isCommit, nestDepth);
 
 		pfree(xact_state);
@@ -197,7 +197,7 @@ AtPrepare_PgStat(void)
 		Assert(xact_state->nest_level == 1);
 		Assert(xact_state->prev == NULL);
 
-		AtPrepare_PgStat_Relations(xact_state);
+		AtPrepare_PgStat_Tables(xact_state);
 	}
 }
 
@@ -221,7 +221,7 @@ PostPrepare_PgStat(void)
 		Assert(xact_state->nest_level == 1);
 		Assert(xact_state->prev == NULL);
 
-		PostPrepare_PgStat_Relations(xact_state);
+		PostPrepare_PgStat_Tables(xact_state);
 	}
 	pgStatXactStack = NULL;
 
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 6cddd74aa7..13ee07a38f 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -36,106 +36,159 @@
 
 #define HAS_PGSTAT_PERMISSIONS(role)	 (has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS) || has_privs_of_role(GetUserId(), role))
 
-#define PG_STAT_GET_RELENTRY_INT64(stat)						\
-Datum															\
-CppConcat(pg_stat_get_,stat)(PG_FUNCTION_ARGS)					\
-{																\
-	Oid			relid = PG_GETARG_OID(0);						\
-	int64		result;											\
-	PgStat_StatTabEntry *tabentry;								\
-																\
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)	\
-		result = 0;												\
-	else														\
-		result = (int64) (tabentry->stat);						\
-																\
-	PG_RETURN_INT64(result);									\
-}
-
-/* pg_stat_get_analyze_count */
-PG_STAT_GET_RELENTRY_INT64(analyze_count)
-
-/* pg_stat_get_autoanalyze_count */
-PG_STAT_GET_RELENTRY_INT64(autoanalyze_count)
-
-/* pg_stat_get_autovacuum_count */
-PG_STAT_GET_RELENTRY_INT64(autovacuum_count)
-
-/* pg_stat_get_blocks_fetched */
-PG_STAT_GET_RELENTRY_INT64(blocks_fetched)
-
-/* pg_stat_get_blocks_hit */
-PG_STAT_GET_RELENTRY_INT64(blocks_hit)
+#define PG_STAT_GET_TABENTRY_INT64(stat)							\
+Datum																\
+CppConcat(pg_stat_get_tab_,stat)(PG_FUNCTION_ARGS)					\
+{																	\
+	Oid			relid = PG_GETARG_OID(0);							\
+	int64		result;												\
+	PgStat_StatTabEntry *tabentry;									\
+																	\
+	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)		\
+		result = 0;													\
+	else															\
+		result = (int64) (tabentry->stat);							\
+																	\
+	PG_RETURN_INT64(result);										\
+}
+
+/* pg_stat_get_tab_analyze_count */
+PG_STAT_GET_TABENTRY_INT64(analyze_count)
+
+/* pg_stat_get_tab_autoanalyze_count */
+PG_STAT_GET_TABENTRY_INT64(autoanalyze_count)
+
+/* pg_stat_get_tab_autovacuum_count */
+PG_STAT_GET_TABENTRY_INT64(autovacuum_count)
+
+/* pg_stat_get_tab_blocks_fetched */
+PG_STAT_GET_TABENTRY_INT64(blocks_fetched)
+
+/* pg_stat_get_tab_blocks_hit */
+PG_STAT_GET_TABENTRY_INT64(blocks_hit)
 
-/* pg_stat_get_dead_tuples */
-PG_STAT_GET_RELENTRY_INT64(dead_tuples)
+/* pg_stat_get_tab_dead_tuples */
+PG_STAT_GET_TABENTRY_INT64(dead_tuples)
 
-/* pg_stat_get_ins_since_vacuum */
-PG_STAT_GET_RELENTRY_INT64(ins_since_vacuum)
-
-/* pg_stat_get_live_tuples */
-PG_STAT_GET_RELENTRY_INT64(live_tuples)
-
-/* pg_stat_get_mods_since_analyze */
-PG_STAT_GET_RELENTRY_INT64(mod_since_analyze)
-
-/* pg_stat_get_numscans */
-PG_STAT_GET_RELENTRY_INT64(numscans)
-
-/* pg_stat_get_tuples_deleted */
-PG_STAT_GET_RELENTRY_INT64(tuples_deleted)
-
-/* pg_stat_get_tuples_fetched */
-PG_STAT_GET_RELENTRY_INT64(tuples_fetched)
-
-/* pg_stat_get_tuples_hot_updated */
-PG_STAT_GET_RELENTRY_INT64(tuples_hot_updated)
-
-/* pg_stat_get_tuples_inserted */
-PG_STAT_GET_RELENTRY_INT64(tuples_inserted)
-
-/* pg_stat_get_tuples_returned */
-PG_STAT_GET_RELENTRY_INT64(tuples_returned)
-
-/* pg_stat_get_tuples_updated */
-PG_STAT_GET_RELENTRY_INT64(tuples_updated)
-
-/* pg_stat_get_vacuum_count */
-PG_STAT_GET_RELENTRY_INT64(vacuum_count)
-
-#define PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat)					\
-Datum															\
-CppConcat(pg_stat_get_,stat)(PG_FUNCTION_ARGS)					\
-{																\
-	Oid			relid = PG_GETARG_OID(0);						\
-	TimestampTz result;											\
-	PgStat_StatTabEntry *tabentry;								\
-																\
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)	\
-		result = 0;												\
-	else														\
-		result = tabentry->stat;								\
-																\
-	if (result == 0)											\
-		PG_RETURN_NULL();										\
-	else														\
-		PG_RETURN_TIMESTAMPTZ(result);							\
+/* pg_stat_get_tab_ins_since_vacuum */
+PG_STAT_GET_TABENTRY_INT64(ins_since_vacuum)
+
+/* pg_stat_get_tab_live_tuples */
+PG_STAT_GET_TABENTRY_INT64(live_tuples)
+
+/* pg_stat_get_tab_mods_since_analyze */
+PG_STAT_GET_TABENTRY_INT64(mod_since_analyze)
+
+/* pg_stat_get_tab_numscans */
+PG_STAT_GET_TABENTRY_INT64(numscans)
+
+/* pg_stat_get_tab_tuples_deleted */
+PG_STAT_GET_TABENTRY_INT64(tuples_deleted)
+
+/* pg_stat_get_tab_tuples_fetched */
+PG_STAT_GET_TABENTRY_INT64(tuples_fetched)
+
+/* pg_stat_get_tab_tuples_hot_updated */
+PG_STAT_GET_TABENTRY_INT64(tuples_hot_updated)
+
+/* pg_stat_get_tab_tuples_inserted */
+PG_STAT_GET_TABENTRY_INT64(tuples_inserted)
+
+/* pg_stat_get_tab_tuples_returned */
+PG_STAT_GET_TABENTRY_INT64(tuples_returned)
+
+/* pg_stat_get_tab_tuples_updated */
+PG_STAT_GET_TABENTRY_INT64(tuples_updated)
+
+/* pg_stat_get_tab_vacuum_count */
+PG_STAT_GET_TABENTRY_INT64(vacuum_count)
+
+#define PG_STAT_GET_INDENTRY_INT64(stat)							\
+Datum																\
+CppConcat(pg_stat_get_ind_,stat)(PG_FUNCTION_ARGS)					\
+{																	\
+	Oid			relid = PG_GETARG_OID(0);							\
+	int64		result;												\
+	PgStat_StatIndEntry *indentry;									\
+																	\
+	if ((indentry = pgstat_fetch_stat_indentry(relid)) == NULL)		\
+		result = 0;													\
+	else															\
+		result = (int64) (indentry->stat);							\
+																	\
+	PG_RETURN_INT64(result);										\
+}
+
+/* pg_stat_get_ind_numscans */
+PG_STAT_GET_INDENTRY_INT64(numscans)
+
+/* pg_stat_get_ind_tuples_returned */
+PG_STAT_GET_INDENTRY_INT64(tuples_returned)
+
+/* pg_stat_get_ind_tuples_fetched */
+PG_STAT_GET_INDENTRY_INT64(tuples_fetched)
+
+/* pg_stat_get_ind_blocks_fetched */
+PG_STAT_GET_INDENTRY_INT64(blocks_fetched)
+
+/* pg_stat_get_ind_blocks_hit */
+PG_STAT_GET_INDENTRY_INT64(blocks_hit)
+
+#define PG_STAT_GET_TABENTRY_TIMESTAMPTZ(stat)						\
+Datum																\
+CppConcat(pg_stat_get_tab_,stat)(PG_FUNCTION_ARGS)					\
+{																	\
+	Oid			relid = PG_GETARG_OID(0);							\
+	TimestampTz result;												\
+	PgStat_StatTabEntry *tabentry;									\
+																	\
+	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)		\
+		result = 0;													\
+	else															\
+		result = tabentry->stat;									\
+																	\
+	if (result == 0)												\
+		PG_RETURN_NULL();											\
+	else															\
+		PG_RETURN_TIMESTAMPTZ(result);								\
 }
 
-/* pg_stat_get_last_analyze_time */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_analyze_time)
-
-/* pg_stat_get_last_autoanalyze_time */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_autoanalyze_time)
-
-/* pg_stat_get_last_autovacuum_time */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_autovacuum_time)
+/* pg_stat_get_tab_last_analyze_time */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(last_analyze_time)
 
-/* pg_stat_get_last_vacuum_time */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time)
-
-/* pg_stat_get_lastscan */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan)
+/* pg_stat_get_tab_last_autoanalyze_time */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(last_autoanalyze_time)
+
+/* pg_stat_get_tab_last_autovacuum_time */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(last_autovacuum_time)
+
+/* pg_stat_get_tab_last_vacuum_time */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(last_vacuum_time)
+
+/* pg_stat_get_tab_lastscan */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(lastscan)
+
+#define PG_STAT_GET_INDENTRY_TIMESTAMPTZ(stat)						\
+Datum																\
+CppConcat(pg_stat_get_ind_,stat)(PG_FUNCTION_ARGS)					\
+{																	\
+	Oid			relid = PG_GETARG_OID(0);							\
+	TimestampTz result;												\
+	PgStat_StatIndEntry *indentry;									\
+																	\
+	if ((indentry = pgstat_fetch_stat_indentry(relid)) == NULL)		\
+		result = 0;													\
+	else															\
+		result = indentry->stat;									\
+																	\
+	if (result == 0)												\
+		PG_RETURN_NULL();											\
+	else															\
+		PG_RETURN_TIMESTAMPTZ(result);								\
+}
+
+/* pg_stat_get_ind_lastscan */
+PG_STAT_GET_INDENTRY_TIMESTAMPTZ(lastscan)
 
 Datum
 pg_stat_get_function_calls(PG_FUNCTION_ARGS)
@@ -1346,7 +1399,7 @@ pg_stat_get_slru(PG_FUNCTION_ARGS)
 }
 
 Datum
-pg_stat_get_xact_numscans(PG_FUNCTION_ARGS)
+pg_stat_get_tab_xact_numscans(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
@@ -1360,17 +1413,32 @@ pg_stat_get_xact_numscans(PG_FUNCTION_ARGS)
 	PG_RETURN_INT64(result);
 }
 
+Datum
+pg_stat_get_ind_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);
 }
@@ -1380,12 +1448,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);
 }
@@ -1473,12 +1541,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);
 }
@@ -1488,12 +1556,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);
 }
@@ -1612,7 +1680,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 d171cfcf2f..ff96d0453e 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -2411,7 +2411,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 	RelationCloseSmgr(relation);
 
 	/* break mutual link with stats entry */
-	pgstat_unlink_relation(relation);
+	if (relation->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_unlink_index(relation);
+	else
+		pgstat_unlink_table(relation);
 
 	/*
 	 * Free all the subsidiary data structures of the relcache entry, then the
@@ -2727,8 +2730,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)
@@ -6321,7 +6325,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 7be9a50147..007259ea10 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5274,96 +5274,120 @@
   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_tab_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_tab_numscans' },
+{ oid => '8296', descr => 'statistics: number of scans done for index',
+  proname => 'pg_stat_get_ind_numscans', provolatile => 's', proparallel => 'r',
+  prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_ind_numscans' },
+{ oid => '9976', descr => 'statistics: time of the last scan for table',
+  proname => 'pg_stat_get_tab_lastscan', provolatile => 's', proparallel => 'r',
+  prorettype => 'timestamptz', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tab_lastscan' },
+{ oid => '8626', descr => 'statistics: time of the last scan for index',
+  proname => 'pg_stat_get_ind_lastscan', provolatile => 's', proparallel => 'r',
   prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_lastscan' },
+  prosrc => 'pg_stat_get_ind_lastscan' },
 { oid => '1929', descr => 'statistics: number of tuples read by seqscan',
-  proname => 'pg_stat_get_tuples_returned', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_returned', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_returned' },
+  prosrc => 'pg_stat_get_tab_tuples_returned' },
+{ oid => '9603', descr => 'statistics: number of tuples read by seqscan',
+  proname => 'pg_stat_get_ind_tuples_returned', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_ind_tuples_returned' },
 { oid => '1930', descr => 'statistics: number of tuples fetched by idxscan',
-  proname => 'pg_stat_get_tuples_fetched', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_fetched', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tab_tuples_fetched' },
+{ oid => '8526', descr => 'statistics: number of tuples fetched by idxscan',
+  proname => 'pg_stat_get_ind_tuples_fetched', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_fetched' },
+  prosrc => 'pg_stat_get_ind_tuples_fetched' },
 { oid => '1931', descr => 'statistics: number of tuples inserted',
-  proname => 'pg_stat_get_tuples_inserted', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_inserted', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_inserted' },
+  prosrc => 'pg_stat_get_tab_tuples_inserted' },
 { oid => '1932', descr => 'statistics: number of tuples updated',
-  proname => 'pg_stat_get_tuples_updated', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_updated', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_updated' },
+  prosrc => 'pg_stat_get_tab_tuples_updated' },
 { oid => '1933', descr => 'statistics: number of tuples deleted',
-  proname => 'pg_stat_get_tuples_deleted', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_deleted', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_deleted' },
+  prosrc => 'pg_stat_get_tab_tuples_deleted' },
 { oid => '1972', descr => 'statistics: number of tuples hot updated',
-  proname => 'pg_stat_get_tuples_hot_updated', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_hot_updated', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_hot_updated' },
+  prosrc => 'pg_stat_get_tab_tuples_hot_updated' },
 { oid => '2878', descr => 'statistics: number of live tuples',
-  proname => 'pg_stat_get_live_tuples', provolatile => 's', proparallel => 'r',
+  proname => 'pg_stat_get_tab_live_tuples', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_live_tuples' },
+  prosrc => 'pg_stat_get_tab_live_tuples' },
 { oid => '2879', descr => 'statistics: number of dead tuples',
-  proname => 'pg_stat_get_dead_tuples', provolatile => 's', proparallel => 'r',
+  proname => 'pg_stat_get_tab_dead_tuples', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_dead_tuples' },
+  prosrc => 'pg_stat_get_tab_dead_tuples' },
 { oid => '3177',
   descr => 'statistics: number of tuples changed since last analyze',
-  proname => 'pg_stat_get_mod_since_analyze', provolatile => 's',
+  proname => 'pg_stat_get_tab_mod_since_analyze', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_mod_since_analyze' },
+  prosrc => 'pg_stat_get_tab_mod_since_analyze' },
 { oid => '5053',
   descr => 'statistics: number of tuples inserted since last vacuum',
-  proname => 'pg_stat_get_ins_since_vacuum', provolatile => 's',
+  proname => 'pg_stat_get_tab_ins_since_vacuum', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_ins_since_vacuum' },
+  prosrc => 'pg_stat_get_tab_ins_since_vacuum' },
 { oid => '1934', descr => 'statistics: number of blocks fetched',
-  proname => 'pg_stat_get_blocks_fetched', provolatile => 's',
+  proname => 'pg_stat_get_tab_blocks_fetched', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_blocks_fetched' },
+  prosrc => 'pg_stat_get_tab_blocks_fetched' },
+{ oid => '9432', descr => 'statistics: number of blocks fetched',
+  proname => 'pg_stat_get_ind_blocks_fetched', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_ind_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_tab_blocks_hit', provolatile => 's', proparallel => 'r',
+  prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tab_blocks_hit' },
+{ oid => '8354', descr => 'statistics: number of blocks found in cache',
+  proname => 'pg_stat_get_ind_blocks_hit', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_blocks_hit' },
+  prosrc => 'pg_stat_get_ind_blocks_hit' },
 { oid => '2781', descr => 'statistics: last manual vacuum time for a table',
-  proname => 'pg_stat_get_last_vacuum_time', provolatile => 's',
+  proname => 'pg_stat_get_tab_last_vacuum_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_last_vacuum_time' },
+  prosrc => 'pg_stat_get_tab_last_vacuum_time' },
 { oid => '2782', descr => 'statistics: last auto vacuum time for a table',
-  proname => 'pg_stat_get_last_autovacuum_time', provolatile => 's',
+  proname => 'pg_stat_get_tab_last_autovacuum_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_last_autovacuum_time' },
+  prosrc => 'pg_stat_get_tab_last_autovacuum_time' },
 { oid => '2783', descr => 'statistics: last manual analyze time for a table',
-  proname => 'pg_stat_get_last_analyze_time', provolatile => 's',
+  proname => 'pg_stat_get_tab_last_analyze_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_last_analyze_time' },
+  prosrc => 'pg_stat_get_tab_last_analyze_time' },
 { oid => '2784', descr => 'statistics: last auto analyze time for a table',
-  proname => 'pg_stat_get_last_autoanalyze_time', provolatile => 's',
+  proname => 'pg_stat_get_tab_last_autoanalyze_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_last_autoanalyze_time' },
+  prosrc => 'pg_stat_get_tab_last_autoanalyze_time' },
 { oid => '3054', descr => 'statistics: number of manual vacuums for a table',
-  proname => 'pg_stat_get_vacuum_count', provolatile => 's', proparallel => 'r',
+  proname => 'pg_stat_get_tab_vacuum_count', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_vacuum_count' },
+  prosrc => 'pg_stat_get_tab_vacuum_count' },
 { oid => '3055', descr => 'statistics: number of auto vacuums for a table',
-  proname => 'pg_stat_get_autovacuum_count', provolatile => 's',
+  proname => 'pg_stat_get_tab_autovacuum_count', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_autovacuum_count' },
+  prosrc => 'pg_stat_get_tab_autovacuum_count' },
 { oid => '3056', descr => 'statistics: number of manual analyzes for a table',
-  proname => 'pg_stat_get_analyze_count', provolatile => 's',
+  proname => 'pg_stat_get_tab_analyze_count', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_analyze_count' },
+  prosrc => 'pg_stat_get_tab_analyze_count' },
 { oid => '3057', descr => 'statistics: number of auto analyzes for a table',
-  proname => 'pg_stat_get_autoanalyze_count', provolatile => 's',
+  proname => 'pg_stat_get_tab_autoanalyze_count', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_autoanalyze_count' },
+  prosrc => 'pg_stat_get_tab_autoanalyze_count' },
 { oid => '1936', descr => 'statistics: currently active backend IDs',
   proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't',
   provolatile => 's', proparallel => 'r', prorettype => 'int4',
@@ -5726,10 +5750,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_tab_xact_numscans', provolatile => 'v',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tab_xact_numscans' },
+{ oid => '8892',
+  descr => 'statistics: number of scans done for index in current transaction',
+  proname => 'pg_stat_get_ind_xact_numscans', provolatile => 'v',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_xact_numscans' },
+  prosrc => 'pg_stat_get_ind_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 d3e965d744..9a713ab8e8 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 autoanalyze_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;
@@ -494,16 +541,15 @@ extern PgStat_BackendFunctionEntry *find_funcstat_entry(Oid func_id);
 
 
 /*
- * Functions in pgstat_relation.c
+ * Functions in pgstat_table.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_drop_table(Relation rel);
+extern void pgstat_init_table(Relation rel);
 
-extern void pgstat_init_relation(Relation rel);
-extern void pgstat_assoc_relation(Relation rel);
-extern void pgstat_unlink_relation(Relation rel);
+extern void pgstat_assoc_table(Relation rel);
+extern void pgstat_unlink_table(Relation rel);
 
 extern void pgstat_report_vacuum(Oid tableoid, bool shared,
 								 PgStat_Counter livetuples, PgStat_Counter deadtuples);
@@ -511,53 +557,83 @@ extern void pgstat_report_analyze(Relation rel,
 								  PgStat_Counter livetuples, PgStat_Counter deadtuples,
 								  bool resetcounter);
 
+/*
+ * Functions in pgstat_index.c
+ */
+
+extern void pgstat_create_index(Relation rel);
+extern void pgstat_drop_index(Relation rel);
+extern void pgstat_copy_index_stats(Relation dst, Relation src);
+extern void pgstat_init_index(Relation rel);
+extern void pgstat_assoc_index(Relation rel);
+extern void pgstat_unlink_index(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_table()
+ * and pgstat_init_index().
  */
-#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 +649,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 08412d6404..d48be4c287 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
 {
@@ -550,16 +556,23 @@ extern bool pgstat_function_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
 
 
 /*
- * Functions in pgstat_relation.c
+ * Functions in pgstat_table.c
  */
 
-extern void AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit);
-extern void AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth);
-extern void AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state);
-extern void PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state);
+extern void AtEOXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit);
+extern void AtEOSubXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth);
+extern void AtPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state);
+extern void PostPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state);
+
+extern bool pgstat_table_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+extern void pgstat_table_delete_pending_cb(PgStat_EntryRef *entry_ref);
+
+/*
+ * Functions in pgstat_index.c
+ */
 
-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_index_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+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 af9785038d..de57e5f929 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..494adee88b 100644
--- a/src/test/isolation/expected/stats.out
+++ b/src/test/isolation/expected/stats.out
@@ -2166,14 +2166,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2210,14 +2210,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2236,14 +2236,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2275,14 +2275,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2314,14 +2314,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2369,14 +2369,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2401,14 +2401,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2463,14 +2463,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2495,14 +2495,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2558,14 +2558,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2627,14 +2627,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2688,14 +2688,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2755,14 +2755,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2795,14 +2795,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2841,14 +2841,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2881,14 +2881,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2927,14 +2927,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2968,14 +2968,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -3015,14 +3015,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
diff --git a/src/test/isolation/expected/stats_1.out b/src/test/isolation/expected/stats_1.out
index 3854320106..7981747732 100644
--- a/src/test/isolation/expected/stats_1.out
+++ b/src/test/isolation/expected/stats_1.out
@@ -2174,14 +2174,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2218,14 +2218,14 @@ 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_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2244,14 +2244,14 @@ 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_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2283,14 +2283,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2322,14 +2322,14 @@ 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_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2377,14 +2377,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2409,14 +2409,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2471,14 +2471,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2503,14 +2503,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2566,14 +2566,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2635,14 +2635,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2698,14 +2698,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2767,14 +2767,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2809,14 +2809,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2857,14 +2857,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2899,14 +2899,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2947,14 +2947,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2990,14 +2990,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -3039,14 +3039,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
diff --git a/src/test/isolation/specs/stats.spec b/src/test/isolation/specs/stats.spec
index 5b922d788c..53d78f7db1 100644
--- a/src/test/isolation/specs/stats.spec
+++ b/src/test/isolation/specs/stats.spec
@@ -93,14 +93,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 }
diff --git a/src/test/recovery/t/029_stats_restart.pl b/src/test/recovery/t/029_stats_restart.pl
index 83d6647d32..8b0b597419 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 f1121e4b12..c7f2053f8c 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 fb9f936d43..9c2b813789 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1767,10 +1767,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_ind_numscans(i.oid) AS idx_scan,
+    pg_stat_get_ind_lastscan(i.oid) AS last_idx_scan,
+    pg_stat_get_ind_tuples_returned(i.oid) AS idx_tup_read,
+    pg_stat_get_ind_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)))
@@ -1779,28 +1779,28 @@ 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_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,
-    pg_stat_get_tuples_hot_updated(c.oid) AS n_tup_hot_upd,
-    pg_stat_get_live_tuples(c.oid) AS n_live_tup,
-    pg_stat_get_dead_tuples(c.oid) AS n_dead_tup,
-    pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze,
-    pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum,
-    pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum,
-    pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum,
-    pg_stat_get_last_analyze_time(c.oid) AS last_analyze,
-    pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze,
-    pg_stat_get_vacuum_count(c.oid) AS vacuum_count,
-    pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count,
-    pg_stat_get_analyze_count(c.oid) AS analyze_count,
-    pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count
+    pg_stat_get_tab_numscans(c.oid) AS seq_scan,
+    pg_stat_get_tab_lastscan(c.oid) AS last_seq_scan,
+    pg_stat_get_tab_tuples_returned(c.oid) AS seq_tup_read,
+    (sum(pg_stat_get_ind_numscans(i.indexrelid)))::bigint AS idx_scan,
+    max(pg_stat_get_ind_lastscan(i.indexrelid)) AS last_idx_scan,
+    ((sum(pg_stat_get_ind_tuples_fetched(i.indexrelid)))::bigint + pg_stat_get_tab_tuples_fetched(c.oid)) AS idx_tup_fetch,
+    pg_stat_get_tab_tuples_inserted(c.oid) AS n_tup_ins,
+    pg_stat_get_tab_tuples_updated(c.oid) AS n_tup_upd,
+    pg_stat_get_tab_tuples_deleted(c.oid) AS n_tup_del,
+    pg_stat_get_tab_tuples_hot_updated(c.oid) AS n_tup_hot_upd,
+    pg_stat_get_tab_live_tuples(c.oid) AS n_live_tup,
+    pg_stat_get_tab_dead_tuples(c.oid) AS n_dead_tup,
+    pg_stat_get_tab_mod_since_analyze(c.oid) AS n_mod_since_analyze,
+    pg_stat_get_tab_ins_since_vacuum(c.oid) AS n_ins_since_vacuum,
+    pg_stat_get_tab_last_vacuum_time(c.oid) AS last_vacuum,
+    pg_stat_get_tab_last_autovacuum_time(c.oid) AS last_autovacuum,
+    pg_stat_get_tab_last_analyze_time(c.oid) AS last_analyze,
+    pg_stat_get_tab_last_autoanalyze_time(c.oid) AS last_autoanalyze,
+    pg_stat_get_tab_vacuum_count(c.oid) AS vacuum_count,
+    pg_stat_get_tab_autovacuum_count(c.oid) AS autovacuum_count,
+    pg_stat_get_tab_analyze_count(c.oid) AS analyze_count,
+    pg_stat_get_tab_autoanalyze_count(c.oid) AS autoanalyze_count
    FROM ((pg_class c
      LEFT JOIN pg_index i ON ((c.oid = i.indrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
@@ -2224,9 +2224,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_tab_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_ind_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,
@@ -2277,8 +2277,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_ind_blocks_fetched(i.oid) - pg_stat_get_ind_blocks_hit(i.oid)) AS idx_blks_read,
+    pg_stat_get_ind_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)))
@@ -2287,31 +2287,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_ind_blocks_fetched(c.oid) - pg_stat_get_tab_blocks_hit(c.oid)) AS blks_read,
+    pg_stat_get_tab_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_tab_blocks_fetched(c.oid) - pg_stat_get_tab_blocks_hit(c.oid)) AS heap_blks_read,
+    pg_stat_get_tab_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_tab_blocks_fetched(t.oid) - pg_stat_get_tab_blocks_hit(t.oid)) AS toast_blks_read,
+    pg_stat_get_tab_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_ind_blocks_fetched(pg_index.indexrelid) - pg_stat_get_ind_blocks_hit(pg_index.indexrelid))))::bigint AS idx_blks_read,
+            (sum(pg_stat_get_ind_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_ind_blocks_fetched(pg_index.indexrelid) - pg_stat_get_ind_blocks_hit(pg_index.indexrelid))))::bigint AS idx_blks_read,
+            (sum(pg_stat_get_ind_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 1d84407a03..d5ee0f4845 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;$$;
@@ -357,17 +468,17 @@ SELECT pg_stat_force_next_flush();
  
 (1 row)
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           1
 (1 row)
 
 DROP TABLE drop_stats_test;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       0
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           0
 (1 row)
 
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_oid);
@@ -378,18 +489,18 @@ SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_oid);
 
 -- check that rollback protects against having stats dropped and that local
 -- modifications don't pose a problem
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
-(1 row)
-
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_tuples_inserted 
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_live_tuples 
 -----------------------------
                            1
 (1 row)
 
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_tuples_inserted 
+---------------------------------
+                               1
+(1 row)
+
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
  pg_stat_get_xact_tuples_inserted 
 ----------------------------------
@@ -418,29 +529,29 @@ SELECT pg_stat_force_next_flush();
  
 (1 row)
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           1
 (1 row)
 
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_tuples_inserted 
------------------------------
-                           2
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_tuples_inserted 
+---------------------------------
+                               2
 (1 row)
 
 -- transactional drop
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           1
 (1 row)
 
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_tuples_inserted 
------------------------------
-                           2
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_tuples_inserted 
+---------------------------------
+                               2
 (1 row)
 
 BEGIN;
@@ -465,23 +576,23 @@ SELECT pg_stat_force_next_flush();
  
 (1 row)
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       0
-(1 row)
-
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_tuples_inserted 
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_live_tuples 
 -----------------------------
                            0
 (1 row)
 
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_tuples_inserted 
+---------------------------------
+                               0
+(1 row)
+
 -- savepoint rollback (2 levels)
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           1
 (1 row)
 
 BEGIN;
@@ -510,17 +621,17 @@ SELECT pg_stat_force_next_flush();
  
 (1 row)
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       3
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           3
 (1 row)
 
 -- savepoint rolback (1 level)
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       3
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           3
 (1 row)
 
 BEGIN;
@@ -529,17 +640,17 @@ DROP TABLE drop_stats_test_subxact;
 SAVEPOINT sp2;
 ROLLBACK TO SAVEPOINT sp1;
 COMMIT;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       3
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           3
 (1 row)
 
 -- and now actually drop
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       3
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           3
 (1 row)
 
 BEGIN;
@@ -548,10 +659,10 @@ DROP TABLE drop_stats_test_subxact;
 SAVEPOINT sp2;
 RELEASE SAVEPOINT sp1;
 COMMIT;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       0
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           0
 (1 row)
 
 DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
@@ -1021,21 +1132,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
@@ -1051,14 +1162,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
@@ -1073,7 +1184,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
@@ -1081,7 +1192,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
@@ -1089,7 +1200,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
@@ -1097,7 +1208,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
@@ -1105,7 +1216,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 b4d6753c71..1e9ae74b2a 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
 ---
@@ -218,15 +289,15 @@ SELECT 'drop_stats_test_subxact'::regclass::oid AS drop_stats_test_subxact_oid \
 
 SELECT pg_stat_force_next_flush();
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_oid);
 DROP TABLE drop_stats_test;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_oid);
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_oid);
 
 -- check that rollback protects against having stats dropped and that local
 -- modifications don't pose a problem
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
 BEGIN;
 INSERT INTO drop_stats_test_xact DEFAULT VALUES;
@@ -235,12 +306,12 @@ DROP TABLE drop_stats_test_xact;
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
 ROLLBACK;
 SELECT pg_stat_force_next_flush();
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
 
 -- transactional drop
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
 BEGIN;
 INSERT INTO drop_stats_test_xact DEFAULT VALUES;
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
@@ -248,11 +319,11 @@ DROP TABLE drop_stats_test_xact;
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
 COMMIT;
 SELECT pg_stat_force_next_flush();
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
 
 -- savepoint rollback (2 levels)
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 BEGIN;
 INSERT INTO drop_stats_test_subxact DEFAULT VALUES;
 SAVEPOINT sp1;
@@ -264,27 +335,27 @@ ROLLBACK TO SAVEPOINT sp2;
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_subxact_oid);
 COMMIT;
 SELECT pg_stat_force_next_flush();
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 
 -- savepoint rolback (1 level)
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 BEGIN;
 SAVEPOINT sp1;
 DROP TABLE drop_stats_test_subxact;
 SAVEPOINT sp2;
 ROLLBACK TO SAVEPOINT sp1;
 COMMIT;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 
 -- and now actually drop
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 BEGIN;
 SAVEPOINT sp1;
 DROP TABLE drop_stats_test_subxact;
 SAVEPOINT sp2;
 RELEASE SAVEPOINT sp1;
 COMMIT;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 
 DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
 DROP TABLE prevstats;
@@ -493,40 +564,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 50d86cb01b..ec8258611b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2010,10 +2010,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
@@ -2027,6 +2028,7 @@ PgStat_FetchConsistency
 PgStat_FunctionCallUsage
 PgStat_FunctionCounts
 PgStat_HashKey
+PgStat_IndexStatus
 PgStat_Kind
 PgStat_KindInfo
 PgStat_LocalState
#18Andres Freund
andres@anarazel.de
In reply to: Drouvot, Bertrand (#17)
Re: Split index and table statistics into different types of stats

Hi,

On 2023-01-03 15:19:18 +0100, Drouvot, Bertrand wrote:

diff --git a/src/backend/access/common/relation.c b/src/backend/access/common/relation.c
index 4017e175e3..fca166a063 100644
--- a/src/backend/access/common/relation.c
+++ b/src/backend/access/common/relation.c
@@ -73,7 +73,10 @@ relation_open(Oid relationId, LOCKMODE lockmode)
if (RelationUsesLocalBuffers(r))
MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
-	pgstat_init_relation(r);
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_init_index(r);
+	else
+		pgstat_init_table(r);

return r;
}
@@ -123,7 +126,10 @@ try_relation_open(Oid relationId, LOCKMODE lockmode)
if (RelationUsesLocalBuffers(r))
MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;

-	pgstat_init_relation(r);
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_init_index(r);
+	else
+		pgstat_init_table(r);

return r;
}

Not this patch's fault, but the functions in relation.c have gotten duplicated
to an almost ridiculous degree :(

diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 3fb38a25cf..98bb230b95 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;
}

Not nice to have additional branches here :(.

I think going forward we should move buffer stats to a "per-relfilenode" stats
entry (which would allow to track writes too), then thiw branch would be
removed again.

+/* -------------------------------------------------------------------------
+ *
+ * pgstat_index.c
+ *	  Implementation of index statistics.

This is a fair bit of duplicated code. Perhaps it'd be worth keeping
pgstat_relation with code common to table/index stats?

+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;

What does "com" stand for in 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;
+	}

I really need to propose pg_memiszero().

Datum
-pg_stat_get_xact_numscans(PG_FUNCTION_ARGS)
+pg_stat_get_tab_xact_numscans(PG_FUNCTION_ARGS)
{
Oid			relid = PG_GETARG_OID(0);
int64		result;
@@ -1360,17 +1413,32 @@ pg_stat_get_xact_numscans(PG_FUNCTION_ARGS)
PG_RETURN_INT64(result);
}
+Datum
+pg_stat_get_ind_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);
+}

Why didn't all these get converted to the same macro based approach as the
!xact versions?

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);
}

There's a bunch of changes like this, and I don't understand -
pg_stat_get_xact_tuples_returned() now looks at index stats, even though it
afaics continues to be used in pg_stat_xact_all_tables? Huh?

+/* ----------
+ * 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.
+ * ----------
+ */

Which counters are transactional for indexes? None, no?

diff --git a/src/test/recovery/t/029_stats_restart.pl b/src/test/recovery/t/029_stats_restart.pl
index 83d6647d32..8b0b597419 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");

Think this should grow a test for an index too. There's not that much point in
the isolation case, because we don't have transactional stats, but here it
seems worth testing?

Greetings,

Andres Freund

#19Drouvot, Bertrand
bertranddrouvot.pg@gmail.com
In reply to: Andres Freund (#18)
Re: Split index and table statistics into different types of stats

Hi,

On 1/5/23 1:27 AM, Andres Freund wrote:

Hi,

On 2023-01-03 15:19:18 +0100, Drouvot, Bertrand wrote:

diff --git a/src/backend/access/common/relation.c b/src/backend/access/common/relation.c
index 4017e175e3..fca166a063 100644
--- a/src/backend/access/common/relation.c
+++ b/src/backend/access/common/relation.c
@@ -73,7 +73,10 @@ relation_open(Oid relationId, LOCKMODE lockmode)
if (RelationUsesLocalBuffers(r))
MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
-	pgstat_init_relation(r);
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_init_index(r);
+	else
+		pgstat_init_table(r);

return r;
}
@@ -123,7 +126,10 @@ try_relation_open(Oid relationId, LOCKMODE lockmode)
if (RelationUsesLocalBuffers(r))
MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;

-	pgstat_init_relation(r);
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_init_index(r);
+	else
+		pgstat_init_table(r);

return r;
}

Not this patch's fault, but the functions in relation.c have gotten duplicated
to an almost ridiculous degree :(

Thanks for looking at it!
Right, I'll have a look and will try to submit a dedicated patch for this.

diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 3fb38a25cf..98bb230b95 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;
}

Not nice to have additional branches here :(.

Indeed, but that does look like the price to pay for the moment ;-(

I think going forward we should move buffer stats to a "per-relfilenode" stats
entry (which would allow to track writes too), then thiw branch would be
removed again.

Agree. I think the best approach is to have this patch committed and then resume working on [1]/messages/by-id/20221220181108.e5fddk3g7cive3v6@alap3.anarazel.de (which would most probably introduce
the "per-relfilenode" stats.) Does this approach make sense to you?

+/* -------------------------------------------------------------------------
+ *
+ * pgstat_index.c
+ *	  Implementation of index statistics.

This is a fair bit of duplicated code. Perhaps it'd be worth keeping
pgstat_relation with code common to table/index stats?

Good point, will look at what can be done.

+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;

What does "com" stand for in shrelcomstats?

Oops, thanks!

This naming is coming from my first try while working on this subject (that I did not share).
The idea I had at that time was to create a PGSTAT_KIND_RELATION_COMMON stat type for common stats between tables and indexes
and a dedicated one (PGSTAT_KIND_TABLE) for tables (given that indexes would have been fully part of the common one).
But it did not work well (specially as we want "dedicated" field names), so I preferred to submit the current proposal.

Will fix this bad naming.

+	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;
+	}

I really need to propose pg_memiszero().

Oh yeah, great idea, that would be easier to read.

Datum
-pg_stat_get_xact_numscans(PG_FUNCTION_ARGS)
+pg_stat_get_tab_xact_numscans(PG_FUNCTION_ARGS)
{
Oid			relid = PG_GETARG_OID(0);
int64		result;
@@ -1360,17 +1413,32 @@ pg_stat_get_xact_numscans(PG_FUNCTION_ARGS)
PG_RETURN_INT64(result);
}
+Datum
+pg_stat_get_ind_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);
+}

Why didn't all these get converted to the same macro based approach as the
!xact versions?

I think the "benefits" was not that "big" as compared to the number of non xact ones.
But, good point, now with the tables/indexes split I think it does: I'll submit a dedicated patch for it.

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);
}

There's a bunch of changes like this, and I don't understand -
pg_stat_get_xact_tuples_returned() now looks at index stats, even though it
afaics continues to be used in pg_stat_xact_all_tables? Huh?

Looks like a mistake (I probably messed up while doing all those changes that "look the same"), thanks for pointing out!
I'll go through each one and double check.

+/* ----------
+ * 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.
+ * ----------
+ */

Which counters are transactional for indexes? None, no?

Right, will fix.

diff --git a/src/test/recovery/t/029_stats_restart.pl b/src/test/recovery/t/029_stats_restart.pl
index 83d6647d32..8b0b597419 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");

Think this should grow a test for an index too. There's not that much point in
the isolation case, because we don't have transactional stats, but here it
seems worth testing?

+1, will do.

[1]: /messages/by-id/20221220181108.e5fddk3g7cive3v6@alap3.anarazel.de

Regards,

--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

#20Nitin Jadhav
nitinjadhavpostgres@gmail.com
In reply to: Drouvot, Bertrand (#19)
Re: Split index and table statistics into different types of stats
+/* -------------------------------------------------------------------------
+ *
+ * pgstat_index.c
+ *    Implementation of index statistics.

This is a fair bit of duplicated code. Perhaps it'd be worth keeping
pgstat_relation with code common to table/index stats?

+1 to keep common functions/code between table and index stats. Only
the data structure should be different as the goal is to shrink the
current memory usage.

Thanks & Regards,
Nitin Jadhav

On Thu, Jan 5, 2023 at 3:35 PM Drouvot, Bertrand
<bertranddrouvot.pg@gmail.com> wrote:

Show quoted text

Hi,

On 1/5/23 1:27 AM, Andres Freund wrote:

Hi,

On 2023-01-03 15:19:18 +0100, Drouvot, Bertrand wrote:

diff --git a/src/backend/access/common/relation.c b/src/backend/access/common/relation.c
index 4017e175e3..fca166a063 100644
--- a/src/backend/access/common/relation.c
+++ b/src/backend/access/common/relation.c
@@ -73,7 +73,10 @@ relation_open(Oid relationId, LOCKMODE lockmode)
if (RelationUsesLocalBuffers(r))
MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
-    pgstat_init_relation(r);
+    if (r->rd_rel->relkind == RELKIND_INDEX)
+            pgstat_init_index(r);
+    else
+            pgstat_init_table(r);

return r;
}
@@ -123,7 +126,10 @@ try_relation_open(Oid relationId, LOCKMODE lockmode)
if (RelationUsesLocalBuffers(r))
MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;

-    pgstat_init_relation(r);
+    if (r->rd_rel->relkind == RELKIND_INDEX)
+            pgstat_init_index(r);
+    else
+            pgstat_init_table(r);

return r;
}

Not this patch's fault, but the functions in relation.c have gotten duplicated
to an almost ridiculous degree :(

Thanks for looking at it!
Right, I'll have a look and will try to submit a dedicated patch for this.

diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 3fb38a25cf..98bb230b95 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;
}

Not nice to have additional branches here :(.

Indeed, but that does look like the price to pay for the moment ;-(

I think going forward we should move buffer stats to a "per-relfilenode" stats
entry (which would allow to track writes too), then thiw branch would be
removed again.

Agree. I think the best approach is to have this patch committed and then resume working on [1] (which would most probably introduce
the "per-relfilenode" stats.) Does this approach make sense to you?

+/* -------------------------------------------------------------------------
+ *
+ * pgstat_index.c
+ *    Implementation of index statistics.

This is a fair bit of duplicated code. Perhaps it'd be worth keeping
pgstat_relation with code common to table/index stats?

Good point, will look at what can be done.

+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;

What does "com" stand for in shrelcomstats?

Oops, thanks!

This naming is coming from my first try while working on this subject (that I did not share).
The idea I had at that time was to create a PGSTAT_KIND_RELATION_COMMON stat type for common stats between tables and indexes
and a dedicated one (PGSTAT_KIND_TABLE) for tables (given that indexes would have been fully part of the common one).
But it did not work well (specially as we want "dedicated" field names), so I preferred to submit the current proposal.

Will fix this bad naming.

+    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;
+    }

I really need to propose pg_memiszero().

Oh yeah, great idea, that would be easier to read.

Datum
-pg_stat_get_xact_numscans(PG_FUNCTION_ARGS)
+pg_stat_get_tab_xact_numscans(PG_FUNCTION_ARGS)
{
Oid                     relid = PG_GETARG_OID(0);
int64           result;
@@ -1360,17 +1413,32 @@ pg_stat_get_xact_numscans(PG_FUNCTION_ARGS)
PG_RETURN_INT64(result);
}
+Datum
+pg_stat_get_ind_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);
+}

Why didn't all these get converted to the same macro based approach as the
!xact versions?

I think the "benefits" was not that "big" as compared to the number of non xact ones.
But, good point, now with the tables/indexes split I think it does: I'll submit a dedicated patch for it.

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);
}

There's a bunch of changes like this, and I don't understand -
pg_stat_get_xact_tuples_returned() now looks at index stats, even though it
afaics continues to be used in pg_stat_xact_all_tables? Huh?

Looks like a mistake (I probably messed up while doing all those changes that "look the same"), thanks for pointing out!
I'll go through each one and double check.

+/* ----------
+ * 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.
+ * ----------
+ */

Which counters are transactional for indexes? None, no?

Right, will fix.

diff --git a/src/test/recovery/t/029_stats_restart.pl b/src/test/recovery/t/029_stats_restart.pl
index 83d6647d32..8b0b597419 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");

Think this should grow a test for an index too. There's not that much point in
the isolation case, because we don't have transactional stats, but here it
seems worth testing?

+1, will do.

[1]: /messages/by-id/20221220181108.e5fddk3g7cive3v6@alap3.anarazel.de

Regards,

--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

#21Andres Freund
andres@anarazel.de
In reply to: Nitin Jadhav (#20)
Re: Split index and table statistics into different types of stats

Hi,

On 2023-01-09 17:08:42 +0530, Nitin Jadhav wrote:

+1 to keep common functions/code between table and index stats. Only
the data structure should be different as the goal is to shrink the
current memory usage.

I don't think the goal is solely to shrink memory usage - it's also to make it
possible to add more stats that are specific to just indexes or just
tables. Of course that's related to memory usage...

Greetings,

Andres Freund

#22vignesh C
vignesh21@gmail.com
In reply to: Drouvot, Bertrand (#17)
Re: Split index and table statistics into different types of stats

On Tue, 3 Jan 2023 at 19:49, Drouvot, Bertrand
<bertranddrouvot.pg@gmail.com> wrote:

Hi,

On 12/10/22 10:54 AM, Drouvot, Bertrand wrote:

Hi,

On 12/7/22 11:11 AM, Drouvot, Bertrand wrote:

Hi,

Hi,

As [1] mentioned above has been committed (83a1a1b566), please find attached V5 related to this thread making use of the new macros (namely PG_STAT_GET_RELENTRY_INT64 and PG_STAT_GET_RELENTRY_TIMESTAMPTZ).

I switched from using "CppConcat" to using "##", as it looks to me it's easier to read now that we are adding another concatenation to the game (due to the table/index split).

The (Tab,tab) or (Ind,ind) passed as arguments to the macros look "weird" (I don't have a better idea yet): purpose is to follow the naming convention for PgStat_StatTabEntry/PgStat_StatIndEntry and pgstat_fetch_stat_tabentry/pgstat_fetch_stat_indentry, thoughts?

Looking forward to your feedback,

Attaching V6 (mandatory rebase due to 8018ffbf58).

While at it, I got rid of the weirdness mentioned above by creating 2 sets of macros (one for the tables and one for the indexes).

Looking forward to your feedback,

Regards,

Attaching V7, mandatory rebase due to 66dcb09246.

Attaching V8, mandatory rebase due to c8e1ba736b.

The patch does not apply on top of HEAD as in [1]http://cfbot.cputube.org/patch_41_3984.log, please post a rebased patch:
=== Applying patches on top of PostgreSQL commit ID
d540a02a724b9643205abce8c5644a0f0908f6e3 ===
=== applying patch ./v8-0001-split_tables_indexes_stats.patch
....
patching file src/backend/utils/activity/pgstat_table.c (renamed from
src/backend/utils/activity/pgstat_relation.c)
Hunk #25 FAILED at 759.
....
1 out of 29 hunks FAILED -- saving rejects to file
src/backend/utils/activity/pgstat_table.c.rej

[1]: http://cfbot.cputube.org/patch_41_3984.log

Regards,
Vignesh

#23Drouvot, Bertrand
bertranddrouvot.pg@gmail.com
In reply to: vignesh C (#22)
1 attachment(s)
Re: Split index and table statistics into different types of stats

Hi,

On 1/19/23 12:28 PM, vignesh C wrote:

On Tue, 3 Jan 2023 at 19:49, Drouvot, Bertrand
<bertranddrouvot.pg@gmail.com> wrote:

Attaching V8, mandatory rebase due to c8e1ba736b.

The patch does not apply on top of HEAD as in [1], please post a rebased patch:
=== Applying patches on top of PostgreSQL commit ID
d540a02a724b9643205abce8c5644a0f0908f6e3 ===
=== applying patch ./v8-0001-split_tables_indexes_stats.patch
....
patching file src/backend/utils/activity/pgstat_table.c (renamed from
src/backend/utils/activity/pgstat_relation.c)
Hunk #25 FAILED at 759.
....
1 out of 29 hunks FAILED -- saving rejects to file
src/backend/utils/activity/pgstat_table.c.rej

[1] - http://cfbot.cputube.org/patch_41_3984.log

Thanks for the warning!

Please find attached a rebased version (previous comments
up-thread still need to be addressed though).

Regards,

--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

Attachments:

v9-0001-split_tables_indexes_stats.patchtext/plain; charset=UTF-8; name=v9-0001-split_tables_indexes_stats.patchDownload
diff --git a/src/backend/access/common/relation.c b/src/backend/access/common/relation.c
index 4017e175e3..fca166a063 100644
--- a/src/backend/access/common/relation.c
+++ b/src/backend/access/common/relation.c
@@ -73,7 +73,10 @@ relation_open(Oid relationId, LOCKMODE lockmode)
 	if (RelationUsesLocalBuffers(r))
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
-	pgstat_init_relation(r);
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_init_index(r);
+	else
+		pgstat_init_table(r);
 
 	return r;
 }
@@ -123,7 +126,10 @@ try_relation_open(Oid relationId, LOCKMODE lockmode)
 	if (RelationUsesLocalBuffers(r))
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
-	pgstat_init_relation(r);
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_init_index(r);
+	else
+		pgstat_init_table(r);
 
 	return r;
 }
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index dbf147ed22..5b83146eef 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -590,7 +590,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 4f006820b8..4e9569c901 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 e6579f2979..60d36f7f2f 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 8608e3fa5b..356eb40050 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -654,29 +654,29 @@ 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_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,
-            pg_stat_get_tuples_hot_updated(C.oid) AS n_tup_hot_upd,
-            pg_stat_get_live_tuples(C.oid) AS n_live_tup,
-            pg_stat_get_dead_tuples(C.oid) AS n_dead_tup,
-            pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze,
-            pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum,
-            pg_stat_get_last_vacuum_time(C.oid) as last_vacuum,
-            pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum,
-            pg_stat_get_last_analyze_time(C.oid) as last_analyze,
-            pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze,
-            pg_stat_get_vacuum_count(C.oid) AS vacuum_count,
-            pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count,
-            pg_stat_get_analyze_count(C.oid) AS analyze_count,
-            pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count
+            pg_stat_get_tab_numscans(C.oid) AS seq_scan,
+            pg_stat_get_tab_lastscan(C.oid) AS last_seq_scan,
+            pg_stat_get_tab_tuples_returned(C.oid) AS seq_tup_read,
+            sum(pg_stat_get_ind_numscans(I.indexrelid))::bigint AS idx_scan,
+            max(pg_stat_get_ind_lastscan(I.indexrelid)) AS last_idx_scan,
+            sum(pg_stat_get_ind_tuples_fetched(I.indexrelid))::bigint +
+            pg_stat_get_tab_tuples_fetched(C.oid) AS idx_tup_fetch,
+            pg_stat_get_tab_tuples_inserted(C.oid) AS n_tup_ins,
+            pg_stat_get_tab_tuples_updated(C.oid) AS n_tup_upd,
+            pg_stat_get_tab_tuples_deleted(C.oid) AS n_tup_del,
+            pg_stat_get_tab_tuples_hot_updated(C.oid) AS n_tup_hot_upd,
+            pg_stat_get_tab_live_tuples(C.oid) AS n_live_tup,
+            pg_stat_get_tab_dead_tuples(C.oid) AS n_dead_tup,
+            pg_stat_get_tab_mod_since_analyze(C.oid) AS n_mod_since_analyze,
+            pg_stat_get_tab_ins_since_vacuum(C.oid) AS n_ins_since_vacuum,
+            pg_stat_get_tab_last_vacuum_time(C.oid) as last_vacuum,
+            pg_stat_get_tab_last_autovacuum_time(C.oid) as last_autovacuum,
+            pg_stat_get_tab_last_analyze_time(C.oid) as last_analyze,
+            pg_stat_get_tab_last_autoanalyze_time(C.oid) as last_autoanalyze,
+            pg_stat_get_tab_vacuum_count(C.oid) AS vacuum_count,
+            pg_stat_get_tab_autovacuum_count(C.oid) AS autovacuum_count,
+            pg_stat_get_tab_analyze_count(C.oid) AS analyze_count,
+            pg_stat_get_tab_autoanalyze_count(C.oid) AS autoanalyze_count
     FROM pg_class C LEFT JOIN
          pg_index I ON C.oid = I.indrelid
          LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
@@ -688,9 +688,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_tab_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_ind_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,
@@ -728,31 +728,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_tab_blocks_fetched(C.oid) -
+                    pg_stat_get_tab_blocks_hit(C.oid) AS heap_blks_read,
+            pg_stat_get_tab_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_tab_blocks_fetched(T.oid) -
+                    pg_stat_get_tab_blocks_hit(T.oid) AS toast_blks_read,
+            pg_stat_get_tab_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_ind_blocks_fetched(indexrelid) -
+                         pg_stat_get_ind_blocks_hit(indexrelid))::bigint
                      AS idx_blks_read,
-                     sum(pg_stat_get_blocks_hit(indexrelid))::bigint
+                     sum(pg_stat_get_ind_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_ind_blocks_fetched(indexrelid) -
+                         pg_stat_get_ind_blocks_hit(indexrelid))::bigint
                      AS idx_blks_read,
-                     sum(pg_stat_get_blocks_hit(indexrelid))::bigint
+                     sum(pg_stat_get_ind_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');
@@ -774,10 +774,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_ind_numscans(I.oid) AS idx_scan,
+            pg_stat_get_ind_lastscan(I.oid) AS last_idx_scan,
+            pg_stat_get_ind_tuples_returned(I.oid) AS idx_tup_read,
+            pg_stat_get_ind_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
@@ -801,9 +801,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_ind_blocks_fetched(I.oid) -
+                    pg_stat_get_ind_blocks_hit(I.oid) AS idx_blks_read,
+            pg_stat_get_ind_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
@@ -825,9 +825,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_ind_blocks_fetched(C.oid) -
+                    pg_stat_get_tab_blocks_hit(C.oid) AS blks_read,
+            pg_stat_get_tab_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 8075828e8a..1ab2a0584d 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/Makefile b/src/backend/utils/activity/Makefile
index a80eda3cf4..89b8447816 100644
--- a/src/backend/utils/activity/Makefile
+++ b/src/backend/utils/activity/Makefile
@@ -22,11 +22,12 @@ OBJS = \
 	pgstat_checkpointer.o \
 	pgstat_database.o \
 	pgstat_function.o \
-	pgstat_relation.o \
+	pgstat_index.o \
 	pgstat_replslot.o \
 	pgstat_shmem.o \
 	pgstat_slru.o \
 	pgstat_subscription.o \
+	pgstat_table.o \
 	pgstat_wal.o \
 	pgstat_xact.o \
 	wait_event.o
diff --git a/src/backend/utils/activity/meson.build b/src/backend/utils/activity/meson.build
index a2b872c24b..617cf909db 100644
--- a/src/backend/utils/activity/meson.build
+++ b/src/backend/utils/activity/meson.build
@@ -9,11 +9,12 @@ backend_sources += files(
   'pgstat_checkpointer.c',
   'pgstat_database.c',
   'pgstat_function.c',
-  'pgstat_relation.c',
+  'pgstat_index.c',
   'pgstat_replslot.c',
   'pgstat_shmem.c',
   'pgstat_slru.c',
   'pgstat_subscription.c',
+  'pgstat_table.c',
   'pgstat_wal.c',
   'pgstat_xact.c',
   'wait_event.c',
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index 0fa5370bcd..b289e3611f 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -72,10 +72,11 @@
  * - pgstat_checkpointer.c
  * - pgstat_database.c
  * - pgstat_function.c
- * - pgstat_relation.c
+ * - pgstat_index.c
  * - pgstat_replslot.c
  * - pgstat_slru.c
  * - pgstat_subscription.c
+ * - pgstat_table.c
  * - pgstat_wal.c
  *
  * Whenever possible infrastructure files should not contain code related to
@@ -269,18 +270,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_index.c b/src/backend/utils/activity/pgstat_index.c
new file mode 100644
index 0000000000..882f6cf30d
--- /dev/null
+++ b/src/backend/utils/activity/pgstat_index.c
@@ -0,0 +1,297 @@
+/* -------------------------------------------------------------------------
+ *
+ * pgstat_index.c
+ *	  Implementation of index statistics.
+ *
+ * This file contains the implementation of function index. It is kept
+ * separate from pgstat.c to enforce the line between the statistics access /
+ * storage implementation and the details about individual types of
+ * statistics.
+ *
+ * Copyright (c) 2001-2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/activity/pgstat_index.c
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/xact.h"
+#include "utils/pgstat_internal.h"
+#include "utils/rel.h"
+#include "catalog/catalog.h"
+
+static PgStat_IndexStatus *pgstat_prep_index_pending(Oid rel_id, bool isshared);
+
+/*
+ * Copy stats between indexes. This is used for things like REINDEX
+ * CONCURRENTLY.
+ */
+void
+pgstat_copy_index_stats(Relation dst, Relation src)
+{
+	PgStat_StatIndEntry *srcstats;
+	PgStatShared_Index *dstshstats;
+	PgStat_EntryRef *dst_ref;
+
+	srcstats = pgstat_fetch_stat_indentry_ext(src->rd_rel->relisshared,
+											  RelationGetRelid(src));
+	if (!srcstats)
+		return;
+
+	dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_INDEX,
+										  dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+										  RelationGetRelid(dst),
+										  false);
+
+	dstshstats = (PgStatShared_Index *) dst_ref->shared_stats;
+	dstshstats->stats = *srcstats;
+
+	pgstat_unlock_entry(dst_ref);
+}
+
+/*
+ * Initialize a relcache entry to count access statistics.  Called whenever an
+ * index is opened.
+ *
+ * We assume that a relcache entry's pgstatind_info field is 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
+ * pgstat_assoc_index().
+ */
+void
+pgstat_init_index(Relation rel)
+{
+	/*
+	 * We only count stats for indexes
+	 */
+	Assert(rel->rd_rel->relkind == RELKIND_INDEX);
+
+	if (!pgstat_track_counts)
+	{
+		if (rel->pgstatind_info != NULL)
+			pgstat_unlink_index(rel);
+
+		/* We're not counting at all */
+		rel->pgstat_enabled = false;
+		rel->pgstatind_info = NULL;
+		return;
+	}
+
+	rel->pgstat_enabled = true;
+}
+
+/*
+ * 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_index() 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->pgstatind_info->relation == NULL);
+
+	/* mark this relation as the owner */
+	rel->pgstatind_info->relation = rel;
+}
+
+/*
+ * Break the mutual link between a relcache entry and pending index stats entry.
+ * This must be called whenever one end of the link is removed.
+ */
+void
+pgstat_unlink_index(Relation rel)
+{
+
+	if (rel->pgstatind_info == NULL)
+		return;
+
+	/* 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 index stats are dropped if transaction aborts.
+ */
+void
+pgstat_create_index(Relation rel)
+{
+	pgstat_create_transactional(PGSTAT_KIND_INDEX,
+								rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+								RelationGetRelid(rel));
+}
+
+/*
+ * Ensure that index stats are dropped if transaction commits.
+ */
+void
+pgstat_drop_index(Relation rel)
+{
+	pgstat_drop_transactional(PGSTAT_KIND_INDEX,
+							  rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+							  RelationGetRelid(rel));
+}
+
+/*
+ * 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)
+{
+	return pgstat_fetch_stat_indentry_ext(IsSharedRelation(relid), relid);
+}
+
+/*
+ * 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);
+}
+
+/*
+ * 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;
+	return NULL;
+}
+
+/*
+ * 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->tuples_returned += lstats->i_counts.i_tuples_returned;
+	dbentry->tuples_fetched += lstats->i_counts.i_tuples_fetched;
+	dbentry->blocks_fetched += lstats->i_counts.i_blocks_fetched;
+	dbentry->blocks_hit += lstats->i_counts.i_blocks_hit;
+
+	return true;
+}
+
+void
+pgstat_index_delete_pending_cb(PgStat_EntryRef *entry_ref)
+{
+	PgStat_IndexStatus *pending = (PgStat_IndexStatus *) entry_ref->pending;
+
+	if (pending->relation)
+		pgstat_unlink_index(pending->relation);
+}
+
+/*
+ * 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;
+}
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_table.c
similarity index 75%
rename from src/backend/utils/activity/pgstat_relation.c
rename to src/backend/utils/activity/pgstat_table.c
index 2e20b93c20..1b9da8d911 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_table.c
@@ -1,9 +1,9 @@
 /* -------------------------------------------------------------------------
  *
- * pgstat_relation.c
- *	  Implementation of relation statistics.
+ * pgstat_table.c
+ *	  Implementation of table statistics.
  *
- * This file contains the implementation of function relation. It is kept
+ * This file contains the implementation of function table. It is kept
  * separate from pgstat.c to enforce the line between the statistics access /
  * storage implementation and the details about individual types of
  * statistics.
@@ -11,7 +11,7 @@
  * Copyright (c) 2001-2023, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *	  src/backend/utils/activity/pgstat_relation.c
+ *	  src/backend/utils/activity/pgstat_table.c
  * -------------------------------------------------------------------------
  */
 
@@ -44,74 +44,48 @@ 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 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
- * CONCURRENTLY.
- */
-void
-pgstat_copy_relation_stats(Relation dst, Relation src)
-{
-	PgStat_StatTabEntry *srcstats;
-	PgStatShared_Relation *dstshstats;
-	PgStat_EntryRef *dst_ref;
-
-	srcstats = pgstat_fetch_stat_tabentry_ext(src->rd_rel->relisshared,
-											  RelationGetRelid(src));
-	if (!srcstats)
-		return;
-
-	dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
-										  dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
-										  RelationGetRelid(dst),
-										  false);
-
-	dstshstats = (PgStatShared_Relation *) dst_ref->shared_stats;
-	dstshstats->stats = *srcstats;
-
-	pgstat_unlock_entry(dst_ref);
-}
-
 /*
  * Initialize a relcache entry to count access statistics.  Called whenever a
- * relation is opened.
+ * table is opened.
  *
- * We assume that a relcache entry's pgstat_info field is zeroed by relcache.c
+ * We assume that a relcache entry's pgstattab_info field is 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
- * pgstat_assoc_relation().
+ * pgstat_assoc_table().
  */
 void
-pgstat_init_relation(Relation rel)
+pgstat_init_table(Relation rel)
 {
 	char		relkind = rel->rd_rel->relkind;
 
+	Assert(relkind != RELKIND_INDEX);
+
 	/*
 	 * We only count stats for relations with storage and partitioned tables
 	 */
 	if (!RELKIND_HAS_STORAGE(relkind) && relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		rel->pgstat_enabled = false;
-		rel->pgstat_info = NULL;
+		rel->pgstattab_info = NULL;
 		return;
 	}
 
 	if (!pgstat_track_counts)
 	{
-		if (rel->pgstat_info)
-			pgstat_unlink_relation(rel);
+		if (rel->pgstattab_info != NULL)
+			pgstat_unlink_table(rel);
 
 		/* We're not counting at all */
 		rel->pgstat_enabled = false;
-		rel->pgstat_info = NULL;
+		rel->pgstattab_info = NULL;
 		return;
 	}
 
@@ -119,89 +93,92 @@ 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.
  *
- * This is separate from pgstat_init_relation() as it is not uncommon for
+ * This is separate from pgstat_init_table() as it is not uncommon for
  * 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->pgstat_info->relation == NULL);
+	Assert(rel->pgstattab_info->relation == NULL);
 
 	/* mark this relation as the owner */
-	rel->pgstat_info->relation = rel;
+	rel->pgstattab_info->relation = rel;
 }
 
 /*
- * Break the mutual link between a relcache entry and pending stats entry.
+ * Break the mutual link between a relcache entry and pending table stats entry.
  * This must be called whenever one end of the link is removed.
  */
 void
-pgstat_unlink_relation(Relation rel)
+pgstat_unlink_table(Relation rel)
 {
-	/* remove the link to stats info if any */
-	if (rel->pgstat_info == NULL)
+
+	if (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;
+	}
 }
 
 /*
- * Ensure that stats are dropped if transaction aborts.
+ * Ensure that table stats are dropped if transaction aborts.
  */
 void
-pgstat_create_relation(Relation rel)
+pgstat_create_table(Relation rel)
 {
-	pgstat_create_transactional(PGSTAT_KIND_RELATION,
+	pgstat_create_transactional(PGSTAT_KIND_TABLE,
 								rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
 								RelationGetRelid(rel));
 }
 
 /*
- * Ensure that stats are dropped if transaction commits.
+ * Ensure that table stats are dropped if transaction commits.
  */
 void
-pgstat_drop_relation(Relation rel)
+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;
 	}
 }
 
@@ -213,7 +190,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;
@@ -225,10 +202,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->live_tuples = livetuples;
@@ -272,7 +249,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);
 
@@ -291,31 +268,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->live_tuples = livetuples;
@@ -349,12 +326,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;
 	}
 }
 
@@ -364,16 +341,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++;
 	}
 }
 
@@ -383,12 +360,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++;
 	}
 }
 
@@ -398,15 +375,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;
 	}
 }
 
@@ -421,11 +398,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;
 	}
 }
 
@@ -451,7 +428,7 @@ 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);
 }
 
 /*
@@ -467,9 +444,9 @@ 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;
@@ -477,7 +454,7 @@ find_tabstat_entry(Oid rel_id)
 }
 
 /*
- * Perform relation stats specific end-of-transaction work. Helper for
+ * Perform table stats specific end-of-transaction work. Helper for
  * AtEOXact_PgStat.
  *
  * Transfer transactional insert/update counts into the base tabstat entries.
@@ -485,7 +462,7 @@ find_tabstat_entry(Oid rel_id)
  * TopTransactionContext and will go away anyway.
  */
 void
-AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit)
+AtEOXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit)
 {
 	PgStat_TableXactStatus *trans;
 
@@ -536,14 +513,14 @@ AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit)
 }
 
 /*
- * Perform relation stats specific end-of-sub-transaction work. Helper for
+ * Perform table stats specific end-of-sub-transaction work. Helper for
  * AtEOSubXact_PgStat.
  *
  * Transfer transactional insert/update counts into the next higher
  * subtransaction state.
  */
 void
-AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
+AtEOSubXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
 {
 	PgStat_TableXactStatus *trans;
 	PgStat_TableXactStatus *next_trans;
@@ -620,11 +597,11 @@ AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, in
 }
 
 /*
- * Generate 2PC records for all the pending transaction-dependent relation
+ * Generate 2PC records for all the pending transaction-dependent table
  * stats.
  */
 void
-AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
+AtPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state)
 {
 	PgStat_TableXactStatus *trans;
 
@@ -659,10 +636,10 @@ AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
  * reported to the stats system immediately, while the effects on live and
  * dead tuple counts are preserved in the 2PC state file.
  *
- * Note: AtEOXact_PgStat_Relations is not called during PREPARE.
+ * Note: AtEOXact_PgStat_Tables is not called during PREPARE.
  */
 void
-PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
+PostPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state)
 {
 	PgStat_TableXactStatus *trans;
 
@@ -685,27 +662,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;
 }
@@ -721,10 +698,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)
@@ -733,10 +710,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;
 }
 
@@ -750,22 +727,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)
@@ -832,12 +808,12 @@ pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
 }
 
 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;
 
 	if (pending->relation)
-		pgstat_unlink_relation(pending->relation);
+		pgstat_unlink_table(pending->relation);
 }
 
 /*
@@ -845,12 +821,12 @@ pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref)
  * 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;
@@ -864,7 +840,7 @@ pgstat_prep_relation_pending(Oid rel_id, bool isshared)
  * 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;
@@ -880,24 +856,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/activity/pgstat_xact.c b/src/backend/utils/activity/pgstat_xact.c
index 91cdd9222e..ee43362b73 100644
--- a/src/backend/utils/activity/pgstat_xact.c
+++ b/src/backend/utils/activity/pgstat_xact.c
@@ -51,7 +51,7 @@ AtEOXact_PgStat(bool isCommit, bool parallel)
 		Assert(xact_state->nest_level == 1);
 		Assert(xact_state->prev == NULL);
 
-		AtEOXact_PgStat_Relations(xact_state, isCommit);
+		AtEOXact_PgStat_Tables(xact_state, isCommit);
 		AtEOXact_PgStat_DroppedStats(xact_state, isCommit);
 	}
 	pgStatXactStack = NULL;
@@ -122,7 +122,7 @@ AtEOSubXact_PgStat(bool isCommit, int nestDepth)
 		/* delink xact_state from stack immediately to simplify reuse case */
 		pgStatXactStack = xact_state->prev;
 
-		AtEOSubXact_PgStat_Relations(xact_state, isCommit, nestDepth);
+		AtEOSubXact_PgStat_Tables(xact_state, isCommit, nestDepth);
 		AtEOSubXact_PgStat_DroppedStats(xact_state, isCommit, nestDepth);
 
 		pfree(xact_state);
@@ -197,7 +197,7 @@ AtPrepare_PgStat(void)
 		Assert(xact_state->nest_level == 1);
 		Assert(xact_state->prev == NULL);
 
-		AtPrepare_PgStat_Relations(xact_state);
+		AtPrepare_PgStat_Tables(xact_state);
 	}
 }
 
@@ -221,7 +221,7 @@ PostPrepare_PgStat(void)
 		Assert(xact_state->nest_level == 1);
 		Assert(xact_state->prev == NULL);
 
-		PostPrepare_PgStat_Relations(xact_state);
+		PostPrepare_PgStat_Tables(xact_state);
 	}
 	pgStatXactStack = NULL;
 
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 6737493402..cf44a355d3 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -37,106 +37,159 @@
 
 #define HAS_PGSTAT_PERMISSIONS(role)	 (has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS) || has_privs_of_role(GetUserId(), role))
 
-#define PG_STAT_GET_RELENTRY_INT64(stat)						\
-Datum															\
-CppConcat(pg_stat_get_,stat)(PG_FUNCTION_ARGS)					\
-{																\
-	Oid			relid = PG_GETARG_OID(0);						\
-	int64		result;											\
-	PgStat_StatTabEntry *tabentry;								\
-																\
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)	\
-		result = 0;												\
-	else														\
-		result = (int64) (tabentry->stat);						\
-																\
-	PG_RETURN_INT64(result);									\
-}
-
-/* pg_stat_get_analyze_count */
-PG_STAT_GET_RELENTRY_INT64(analyze_count)
-
-/* pg_stat_get_autoanalyze_count */
-PG_STAT_GET_RELENTRY_INT64(autoanalyze_count)
-
-/* pg_stat_get_autovacuum_count */
-PG_STAT_GET_RELENTRY_INT64(autovacuum_count)
-
-/* pg_stat_get_blocks_fetched */
-PG_STAT_GET_RELENTRY_INT64(blocks_fetched)
-
-/* pg_stat_get_blocks_hit */
-PG_STAT_GET_RELENTRY_INT64(blocks_hit)
+#define PG_STAT_GET_TABENTRY_INT64(stat)							\
+Datum																\
+CppConcat(pg_stat_get_tab_,stat)(PG_FUNCTION_ARGS)					\
+{																	\
+	Oid			relid = PG_GETARG_OID(0);							\
+	int64		result;												\
+	PgStat_StatTabEntry *tabentry;									\
+																	\
+	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)		\
+		result = 0;													\
+	else															\
+		result = (int64) (tabentry->stat);							\
+																	\
+	PG_RETURN_INT64(result);										\
+}
+
+/* pg_stat_get_tab_analyze_count */
+PG_STAT_GET_TABENTRY_INT64(analyze_count)
+
+/* pg_stat_get_tab_autoanalyze_count */
+PG_STAT_GET_TABENTRY_INT64(autoanalyze_count)
+
+/* pg_stat_get_tab_autovacuum_count */
+PG_STAT_GET_TABENTRY_INT64(autovacuum_count)
+
+/* pg_stat_get_tab_blocks_fetched */
+PG_STAT_GET_TABENTRY_INT64(blocks_fetched)
+
+/* pg_stat_get_tab_blocks_hit */
+PG_STAT_GET_TABENTRY_INT64(blocks_hit)
 
-/* pg_stat_get_dead_tuples */
-PG_STAT_GET_RELENTRY_INT64(dead_tuples)
+/* pg_stat_get_tab_dead_tuples */
+PG_STAT_GET_TABENTRY_INT64(dead_tuples)
 
-/* pg_stat_get_ins_since_vacuum */
-PG_STAT_GET_RELENTRY_INT64(ins_since_vacuum)
-
-/* pg_stat_get_live_tuples */
-PG_STAT_GET_RELENTRY_INT64(live_tuples)
-
-/* pg_stat_get_mods_since_analyze */
-PG_STAT_GET_RELENTRY_INT64(mod_since_analyze)
-
-/* pg_stat_get_numscans */
-PG_STAT_GET_RELENTRY_INT64(numscans)
-
-/* pg_stat_get_tuples_deleted */
-PG_STAT_GET_RELENTRY_INT64(tuples_deleted)
-
-/* pg_stat_get_tuples_fetched */
-PG_STAT_GET_RELENTRY_INT64(tuples_fetched)
-
-/* pg_stat_get_tuples_hot_updated */
-PG_STAT_GET_RELENTRY_INT64(tuples_hot_updated)
-
-/* pg_stat_get_tuples_inserted */
-PG_STAT_GET_RELENTRY_INT64(tuples_inserted)
-
-/* pg_stat_get_tuples_returned */
-PG_STAT_GET_RELENTRY_INT64(tuples_returned)
-
-/* pg_stat_get_tuples_updated */
-PG_STAT_GET_RELENTRY_INT64(tuples_updated)
-
-/* pg_stat_get_vacuum_count */
-PG_STAT_GET_RELENTRY_INT64(vacuum_count)
-
-#define PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat)					\
-Datum															\
-CppConcat(pg_stat_get_,stat)(PG_FUNCTION_ARGS)					\
-{																\
-	Oid			relid = PG_GETARG_OID(0);						\
-	TimestampTz result;											\
-	PgStat_StatTabEntry *tabentry;								\
-																\
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)	\
-		result = 0;												\
-	else														\
-		result = tabentry->stat;								\
-																\
-	if (result == 0)											\
-		PG_RETURN_NULL();										\
-	else														\
-		PG_RETURN_TIMESTAMPTZ(result);							\
+/* pg_stat_get_tab_ins_since_vacuum */
+PG_STAT_GET_TABENTRY_INT64(ins_since_vacuum)
+
+/* pg_stat_get_tab_live_tuples */
+PG_STAT_GET_TABENTRY_INT64(live_tuples)
+
+/* pg_stat_get_tab_mods_since_analyze */
+PG_STAT_GET_TABENTRY_INT64(mod_since_analyze)
+
+/* pg_stat_get_tab_numscans */
+PG_STAT_GET_TABENTRY_INT64(numscans)
+
+/* pg_stat_get_tab_tuples_deleted */
+PG_STAT_GET_TABENTRY_INT64(tuples_deleted)
+
+/* pg_stat_get_tab_tuples_fetched */
+PG_STAT_GET_TABENTRY_INT64(tuples_fetched)
+
+/* pg_stat_get_tab_tuples_hot_updated */
+PG_STAT_GET_TABENTRY_INT64(tuples_hot_updated)
+
+/* pg_stat_get_tab_tuples_inserted */
+PG_STAT_GET_TABENTRY_INT64(tuples_inserted)
+
+/* pg_stat_get_tab_tuples_returned */
+PG_STAT_GET_TABENTRY_INT64(tuples_returned)
+
+/* pg_stat_get_tab_tuples_updated */
+PG_STAT_GET_TABENTRY_INT64(tuples_updated)
+
+/* pg_stat_get_tab_vacuum_count */
+PG_STAT_GET_TABENTRY_INT64(vacuum_count)
+
+#define PG_STAT_GET_INDENTRY_INT64(stat)							\
+Datum																\
+CppConcat(pg_stat_get_ind_,stat)(PG_FUNCTION_ARGS)					\
+{																	\
+	Oid			relid = PG_GETARG_OID(0);							\
+	int64		result;												\
+	PgStat_StatIndEntry *indentry;									\
+																	\
+	if ((indentry = pgstat_fetch_stat_indentry(relid)) == NULL)		\
+		result = 0;													\
+	else															\
+		result = (int64) (indentry->stat);							\
+																	\
+	PG_RETURN_INT64(result);										\
+}
+
+/* pg_stat_get_ind_numscans */
+PG_STAT_GET_INDENTRY_INT64(numscans)
+
+/* pg_stat_get_ind_tuples_returned */
+PG_STAT_GET_INDENTRY_INT64(tuples_returned)
+
+/* pg_stat_get_ind_tuples_fetched */
+PG_STAT_GET_INDENTRY_INT64(tuples_fetched)
+
+/* pg_stat_get_ind_blocks_fetched */
+PG_STAT_GET_INDENTRY_INT64(blocks_fetched)
+
+/* pg_stat_get_ind_blocks_hit */
+PG_STAT_GET_INDENTRY_INT64(blocks_hit)
+
+#define PG_STAT_GET_TABENTRY_TIMESTAMPTZ(stat)						\
+Datum																\
+CppConcat(pg_stat_get_tab_,stat)(PG_FUNCTION_ARGS)					\
+{																	\
+	Oid			relid = PG_GETARG_OID(0);							\
+	TimestampTz result;												\
+	PgStat_StatTabEntry *tabentry;									\
+																	\
+	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)		\
+		result = 0;													\
+	else															\
+		result = tabentry->stat;									\
+																	\
+	if (result == 0)												\
+		PG_RETURN_NULL();											\
+	else															\
+		PG_RETURN_TIMESTAMPTZ(result);								\
 }
 
-/* pg_stat_get_last_analyze_time */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_analyze_time)
-
-/* pg_stat_get_last_autoanalyze_time */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_autoanalyze_time)
-
-/* pg_stat_get_last_autovacuum_time */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_autovacuum_time)
+/* pg_stat_get_tab_last_analyze_time */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(last_analyze_time)
 
-/* pg_stat_get_last_vacuum_time */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time)
-
-/* pg_stat_get_lastscan */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan)
+/* pg_stat_get_tab_last_autoanalyze_time */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(last_autoanalyze_time)
+
+/* pg_stat_get_tab_last_autovacuum_time */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(last_autovacuum_time)
+
+/* pg_stat_get_tab_last_vacuum_time */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(last_vacuum_time)
+
+/* pg_stat_get_tab_lastscan */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(lastscan)
+
+#define PG_STAT_GET_INDENTRY_TIMESTAMPTZ(stat)						\
+Datum																\
+CppConcat(pg_stat_get_ind_,stat)(PG_FUNCTION_ARGS)					\
+{																	\
+	Oid			relid = PG_GETARG_OID(0);							\
+	TimestampTz result;												\
+	PgStat_StatIndEntry *indentry;									\
+																	\
+	if ((indentry = pgstat_fetch_stat_indentry(relid)) == NULL)		\
+		result = 0;													\
+	else															\
+		result = indentry->stat;									\
+																	\
+	if (result == 0)												\
+		PG_RETURN_NULL();											\
+	else															\
+		PG_RETURN_TIMESTAMPTZ(result);								\
+}
+
+/* pg_stat_get_ind_lastscan */
+PG_STAT_GET_INDENTRY_TIMESTAMPTZ(lastscan)
 
 Datum
 pg_stat_get_function_calls(PG_FUNCTION_ARGS)
@@ -1357,7 +1410,7 @@ pg_stat_get_slru(PG_FUNCTION_ARGS)
 }
 
 Datum
-pg_stat_get_xact_numscans(PG_FUNCTION_ARGS)
+pg_stat_get_tab_xact_numscans(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
@@ -1371,17 +1424,32 @@ pg_stat_get_xact_numscans(PG_FUNCTION_ARGS)
 	PG_RETURN_INT64(result);
 }
 
+Datum
+pg_stat_get_ind_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);
 }
@@ -1391,12 +1459,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);
 }
@@ -1484,12 +1552,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);
 }
@@ -1499,12 +1567,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);
 }
@@ -1623,7 +1691,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 d171cfcf2f..ff96d0453e 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -2411,7 +2411,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 	RelationCloseSmgr(relation);
 
 	/* break mutual link with stats entry */
-	pgstat_unlink_relation(relation);
+	if (relation->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_unlink_index(relation);
+	else
+		pgstat_unlink_table(relation);
 
 	/*
 	 * Free all the subsidiary data structures of the relcache entry, then the
@@ -2727,8 +2730,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)
@@ -6321,7 +6325,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 86eb8e8c58..44fcdeef81 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5278,96 +5278,120 @@
   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_tab_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_tab_numscans' },
+{ oid => '8296', descr => 'statistics: number of scans done for index',
+  proname => 'pg_stat_get_ind_numscans', provolatile => 's', proparallel => 'r',
+  prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_ind_numscans' },
+{ oid => '9976', descr => 'statistics: time of the last scan for table',
+  proname => 'pg_stat_get_tab_lastscan', provolatile => 's', proparallel => 'r',
+  prorettype => 'timestamptz', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tab_lastscan' },
+{ oid => '8626', descr => 'statistics: time of the last scan for index',
+  proname => 'pg_stat_get_ind_lastscan', provolatile => 's', proparallel => 'r',
   prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_lastscan' },
+  prosrc => 'pg_stat_get_ind_lastscan' },
 { oid => '1929', descr => 'statistics: number of tuples read by seqscan',
-  proname => 'pg_stat_get_tuples_returned', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_returned', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_returned' },
+  prosrc => 'pg_stat_get_tab_tuples_returned' },
+{ oid => '9603', descr => 'statistics: number of tuples read by seqscan',
+  proname => 'pg_stat_get_ind_tuples_returned', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_ind_tuples_returned' },
 { oid => '1930', descr => 'statistics: number of tuples fetched by idxscan',
-  proname => 'pg_stat_get_tuples_fetched', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_fetched', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tab_tuples_fetched' },
+{ oid => '8526', descr => 'statistics: number of tuples fetched by idxscan',
+  proname => 'pg_stat_get_ind_tuples_fetched', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_fetched' },
+  prosrc => 'pg_stat_get_ind_tuples_fetched' },
 { oid => '1931', descr => 'statistics: number of tuples inserted',
-  proname => 'pg_stat_get_tuples_inserted', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_inserted', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_inserted' },
+  prosrc => 'pg_stat_get_tab_tuples_inserted' },
 { oid => '1932', descr => 'statistics: number of tuples updated',
-  proname => 'pg_stat_get_tuples_updated', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_updated', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_updated' },
+  prosrc => 'pg_stat_get_tab_tuples_updated' },
 { oid => '1933', descr => 'statistics: number of tuples deleted',
-  proname => 'pg_stat_get_tuples_deleted', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_deleted', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_deleted' },
+  prosrc => 'pg_stat_get_tab_tuples_deleted' },
 { oid => '1972', descr => 'statistics: number of tuples hot updated',
-  proname => 'pg_stat_get_tuples_hot_updated', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_hot_updated', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_hot_updated' },
+  prosrc => 'pg_stat_get_tab_tuples_hot_updated' },
 { oid => '2878', descr => 'statistics: number of live tuples',
-  proname => 'pg_stat_get_live_tuples', provolatile => 's', proparallel => 'r',
+  proname => 'pg_stat_get_tab_live_tuples', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_live_tuples' },
+  prosrc => 'pg_stat_get_tab_live_tuples' },
 { oid => '2879', descr => 'statistics: number of dead tuples',
-  proname => 'pg_stat_get_dead_tuples', provolatile => 's', proparallel => 'r',
+  proname => 'pg_stat_get_tab_dead_tuples', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_dead_tuples' },
+  prosrc => 'pg_stat_get_tab_dead_tuples' },
 { oid => '3177',
   descr => 'statistics: number of tuples changed since last analyze',
-  proname => 'pg_stat_get_mod_since_analyze', provolatile => 's',
+  proname => 'pg_stat_get_tab_mod_since_analyze', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_mod_since_analyze' },
+  prosrc => 'pg_stat_get_tab_mod_since_analyze' },
 { oid => '5053',
   descr => 'statistics: number of tuples inserted since last vacuum',
-  proname => 'pg_stat_get_ins_since_vacuum', provolatile => 's',
+  proname => 'pg_stat_get_tab_ins_since_vacuum', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_ins_since_vacuum' },
+  prosrc => 'pg_stat_get_tab_ins_since_vacuum' },
 { oid => '1934', descr => 'statistics: number of blocks fetched',
-  proname => 'pg_stat_get_blocks_fetched', provolatile => 's',
+  proname => 'pg_stat_get_tab_blocks_fetched', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_blocks_fetched' },
+  prosrc => 'pg_stat_get_tab_blocks_fetched' },
+{ oid => '9432', descr => 'statistics: number of blocks fetched',
+  proname => 'pg_stat_get_ind_blocks_fetched', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_ind_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_tab_blocks_hit', provolatile => 's', proparallel => 'r',
+  prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tab_blocks_hit' },
+{ oid => '8354', descr => 'statistics: number of blocks found in cache',
+  proname => 'pg_stat_get_ind_blocks_hit', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_blocks_hit' },
+  prosrc => 'pg_stat_get_ind_blocks_hit' },
 { oid => '2781', descr => 'statistics: last manual vacuum time for a table',
-  proname => 'pg_stat_get_last_vacuum_time', provolatile => 's',
+  proname => 'pg_stat_get_tab_last_vacuum_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_last_vacuum_time' },
+  prosrc => 'pg_stat_get_tab_last_vacuum_time' },
 { oid => '2782', descr => 'statistics: last auto vacuum time for a table',
-  proname => 'pg_stat_get_last_autovacuum_time', provolatile => 's',
+  proname => 'pg_stat_get_tab_last_autovacuum_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_last_autovacuum_time' },
+  prosrc => 'pg_stat_get_tab_last_autovacuum_time' },
 { oid => '2783', descr => 'statistics: last manual analyze time for a table',
-  proname => 'pg_stat_get_last_analyze_time', provolatile => 's',
+  proname => 'pg_stat_get_tab_last_analyze_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_last_analyze_time' },
+  prosrc => 'pg_stat_get_tab_last_analyze_time' },
 { oid => '2784', descr => 'statistics: last auto analyze time for a table',
-  proname => 'pg_stat_get_last_autoanalyze_time', provolatile => 's',
+  proname => 'pg_stat_get_tab_last_autoanalyze_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_last_autoanalyze_time' },
+  prosrc => 'pg_stat_get_tab_last_autoanalyze_time' },
 { oid => '3054', descr => 'statistics: number of manual vacuums for a table',
-  proname => 'pg_stat_get_vacuum_count', provolatile => 's', proparallel => 'r',
+  proname => 'pg_stat_get_tab_vacuum_count', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_vacuum_count' },
+  prosrc => 'pg_stat_get_tab_vacuum_count' },
 { oid => '3055', descr => 'statistics: number of auto vacuums for a table',
-  proname => 'pg_stat_get_autovacuum_count', provolatile => 's',
+  proname => 'pg_stat_get_tab_autovacuum_count', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_autovacuum_count' },
+  prosrc => 'pg_stat_get_tab_autovacuum_count' },
 { oid => '3056', descr => 'statistics: number of manual analyzes for a table',
-  proname => 'pg_stat_get_analyze_count', provolatile => 's',
+  proname => 'pg_stat_get_tab_analyze_count', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_analyze_count' },
+  prosrc => 'pg_stat_get_tab_analyze_count' },
 { oid => '3057', descr => 'statistics: number of auto analyzes for a table',
-  proname => 'pg_stat_get_autoanalyze_count', provolatile => 's',
+  proname => 'pg_stat_get_tab_autoanalyze_count', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_autoanalyze_count' },
+  prosrc => 'pg_stat_get_tab_autoanalyze_count' },
 { oid => '1936', descr => 'statistics: currently active backend IDs',
   proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't',
   provolatile => 's', proparallel => 'r', prorettype => 'int4',
@@ -5730,10 +5754,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_tab_xact_numscans', provolatile => 'v',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tab_xact_numscans' },
+{ oid => '8892',
+  descr => 'statistics: number of scans done for index in current transaction',
+  proname => 'pg_stat_get_ind_xact_numscans', provolatile => 'v',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_xact_numscans' },
+  prosrc => 'pg_stat_get_ind_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 5e3326a3b9..d68ca9cf31 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 autoanalyze_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;
@@ -495,16 +542,15 @@ extern PgStat_BackendFunctionEntry *find_funcstat_entry(Oid func_id);
 
 
 /*
- * Functions in pgstat_relation.c
+ * Functions in pgstat_table.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_drop_table(Relation rel);
+extern void pgstat_init_table(Relation rel);
 
-extern void pgstat_init_relation(Relation rel);
-extern void pgstat_assoc_relation(Relation rel);
-extern void pgstat_unlink_relation(Relation rel);
+extern void pgstat_assoc_table(Relation rel);
+extern void pgstat_unlink_table(Relation rel);
 
 extern void pgstat_report_vacuum(Oid tableoid, bool shared,
 								 PgStat_Counter livetuples, PgStat_Counter deadtuples);
@@ -512,53 +558,83 @@ extern void pgstat_report_analyze(Relation rel,
 								  PgStat_Counter livetuples, PgStat_Counter deadtuples,
 								  bool resetcounter);
 
+/*
+ * Functions in pgstat_index.c
+ */
+
+extern void pgstat_create_index(Relation rel);
+extern void pgstat_drop_index(Relation rel);
+extern void pgstat_copy_index_stats(Relation dst, Relation src);
+extern void pgstat_init_index(Relation rel);
+extern void pgstat_assoc_index(Relation rel);
+extern void pgstat_unlink_index(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_table()
+ * and pgstat_init_index().
  */
-#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);
@@ -574,6 +650,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 12fd51f1ae..b4f3b6f330 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
 {
@@ -550,16 +556,23 @@ extern bool pgstat_function_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
 
 
 /*
- * Functions in pgstat_relation.c
+ * Functions in pgstat_table.c
  */
 
-extern void AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit);
-extern void AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth);
-extern void AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state);
-extern void PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state);
+extern void AtEOXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit);
+extern void AtEOSubXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth);
+extern void AtPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state);
+extern void PostPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state);
+
+extern bool pgstat_table_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+extern void pgstat_table_delete_pending_cb(PgStat_EntryRef *entry_ref);
+
+/*
+ * Functions in pgstat_index.c
+ */
 
-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_index_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+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 af9785038d..de57e5f929 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..494adee88b 100644
--- a/src/test/isolation/expected/stats.out
+++ b/src/test/isolation/expected/stats.out
@@ -2166,14 +2166,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2210,14 +2210,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2236,14 +2236,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2275,14 +2275,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2314,14 +2314,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2369,14 +2369,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2401,14 +2401,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2463,14 +2463,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2495,14 +2495,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2558,14 +2558,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2627,14 +2627,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2688,14 +2688,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2755,14 +2755,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2795,14 +2795,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2841,14 +2841,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2881,14 +2881,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2927,14 +2927,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2968,14 +2968,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -3015,14 +3015,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
diff --git a/src/test/isolation/expected/stats_1.out b/src/test/isolation/expected/stats_1.out
index 3854320106..7981747732 100644
--- a/src/test/isolation/expected/stats_1.out
+++ b/src/test/isolation/expected/stats_1.out
@@ -2174,14 +2174,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2218,14 +2218,14 @@ 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_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2244,14 +2244,14 @@ 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_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2283,14 +2283,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2322,14 +2322,14 @@ 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_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2377,14 +2377,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2409,14 +2409,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2471,14 +2471,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2503,14 +2503,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2566,14 +2566,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2635,14 +2635,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2698,14 +2698,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2767,14 +2767,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2809,14 +2809,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2857,14 +2857,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2899,14 +2899,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2947,14 +2947,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2990,14 +2990,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -3039,14 +3039,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
diff --git a/src/test/isolation/specs/stats.spec b/src/test/isolation/specs/stats.spec
index 5b922d788c..53d78f7db1 100644
--- a/src/test/isolation/specs/stats.spec
+++ b/src/test/isolation/specs/stats.spec
@@ -93,14 +93,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 }
diff --git a/src/test/recovery/t/029_stats_restart.pl b/src/test/recovery/t/029_stats_restart.pl
index 83d6647d32..8b0b597419 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 f1121e4b12..c7f2053f8c 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 e7a2f5856a..a49c963a6d 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1767,10 +1767,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_ind_numscans(i.oid) AS idx_scan,
+    pg_stat_get_ind_lastscan(i.oid) AS last_idx_scan,
+    pg_stat_get_ind_tuples_returned(i.oid) AS idx_tup_read,
+    pg_stat_get_ind_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)))
@@ -1779,28 +1779,28 @@ 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_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,
-    pg_stat_get_tuples_hot_updated(c.oid) AS n_tup_hot_upd,
-    pg_stat_get_live_tuples(c.oid) AS n_live_tup,
-    pg_stat_get_dead_tuples(c.oid) AS n_dead_tup,
-    pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze,
-    pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum,
-    pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum,
-    pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum,
-    pg_stat_get_last_analyze_time(c.oid) AS last_analyze,
-    pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze,
-    pg_stat_get_vacuum_count(c.oid) AS vacuum_count,
-    pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count,
-    pg_stat_get_analyze_count(c.oid) AS analyze_count,
-    pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count
+    pg_stat_get_tab_numscans(c.oid) AS seq_scan,
+    pg_stat_get_tab_lastscan(c.oid) AS last_seq_scan,
+    pg_stat_get_tab_tuples_returned(c.oid) AS seq_tup_read,
+    (sum(pg_stat_get_ind_numscans(i.indexrelid)))::bigint AS idx_scan,
+    max(pg_stat_get_ind_lastscan(i.indexrelid)) AS last_idx_scan,
+    ((sum(pg_stat_get_ind_tuples_fetched(i.indexrelid)))::bigint + pg_stat_get_tab_tuples_fetched(c.oid)) AS idx_tup_fetch,
+    pg_stat_get_tab_tuples_inserted(c.oid) AS n_tup_ins,
+    pg_stat_get_tab_tuples_updated(c.oid) AS n_tup_upd,
+    pg_stat_get_tab_tuples_deleted(c.oid) AS n_tup_del,
+    pg_stat_get_tab_tuples_hot_updated(c.oid) AS n_tup_hot_upd,
+    pg_stat_get_tab_live_tuples(c.oid) AS n_live_tup,
+    pg_stat_get_tab_dead_tuples(c.oid) AS n_dead_tup,
+    pg_stat_get_tab_mod_since_analyze(c.oid) AS n_mod_since_analyze,
+    pg_stat_get_tab_ins_since_vacuum(c.oid) AS n_ins_since_vacuum,
+    pg_stat_get_tab_last_vacuum_time(c.oid) AS last_vacuum,
+    pg_stat_get_tab_last_autovacuum_time(c.oid) AS last_autovacuum,
+    pg_stat_get_tab_last_analyze_time(c.oid) AS last_analyze,
+    pg_stat_get_tab_last_autoanalyze_time(c.oid) AS last_autoanalyze,
+    pg_stat_get_tab_vacuum_count(c.oid) AS vacuum_count,
+    pg_stat_get_tab_autovacuum_count(c.oid) AS autovacuum_count,
+    pg_stat_get_tab_analyze_count(c.oid) AS analyze_count,
+    pg_stat_get_tab_autoanalyze_count(c.oid) AS autoanalyze_count
    FROM ((pg_class c
      LEFT JOIN pg_index i ON ((c.oid = i.indrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
@@ -2225,9 +2225,9 @@ pg_stat_wal_receiver| SELECT 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_tab_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_ind_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,
@@ -2278,8 +2278,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_ind_blocks_fetched(i.oid) - pg_stat_get_ind_blocks_hit(i.oid)) AS idx_blks_read,
+    pg_stat_get_ind_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)))
@@ -2288,31 +2288,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_ind_blocks_fetched(c.oid) - pg_stat_get_tab_blocks_hit(c.oid)) AS blks_read,
+    pg_stat_get_tab_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_tab_blocks_fetched(c.oid) - pg_stat_get_tab_blocks_hit(c.oid)) AS heap_blks_read,
+    pg_stat_get_tab_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_tab_blocks_fetched(t.oid) - pg_stat_get_tab_blocks_hit(t.oid)) AS toast_blks_read,
+    pg_stat_get_tab_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_ind_blocks_fetched(pg_index.indexrelid) - pg_stat_get_ind_blocks_hit(pg_index.indexrelid))))::bigint AS idx_blks_read,
+            (sum(pg_stat_get_ind_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_ind_blocks_fetched(pg_index.indexrelid) - pg_stat_get_ind_blocks_hit(pg_index.indexrelid))))::bigint AS idx_blks_read,
+            (sum(pg_stat_get_ind_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 1d84407a03..d5ee0f4845 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;$$;
@@ -357,17 +468,17 @@ SELECT pg_stat_force_next_flush();
  
 (1 row)
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           1
 (1 row)
 
 DROP TABLE drop_stats_test;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       0
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           0
 (1 row)
 
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_oid);
@@ -378,18 +489,18 @@ SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_oid);
 
 -- check that rollback protects against having stats dropped and that local
 -- modifications don't pose a problem
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
-(1 row)
-
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_tuples_inserted 
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_live_tuples 
 -----------------------------
                            1
 (1 row)
 
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_tuples_inserted 
+---------------------------------
+                               1
+(1 row)
+
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
  pg_stat_get_xact_tuples_inserted 
 ----------------------------------
@@ -418,29 +529,29 @@ SELECT pg_stat_force_next_flush();
  
 (1 row)
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           1
 (1 row)
 
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_tuples_inserted 
------------------------------
-                           2
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_tuples_inserted 
+---------------------------------
+                               2
 (1 row)
 
 -- transactional drop
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           1
 (1 row)
 
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_tuples_inserted 
------------------------------
-                           2
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_tuples_inserted 
+---------------------------------
+                               2
 (1 row)
 
 BEGIN;
@@ -465,23 +576,23 @@ SELECT pg_stat_force_next_flush();
  
 (1 row)
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       0
-(1 row)
-
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_tuples_inserted 
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_live_tuples 
 -----------------------------
                            0
 (1 row)
 
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_tuples_inserted 
+---------------------------------
+                               0
+(1 row)
+
 -- savepoint rollback (2 levels)
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           1
 (1 row)
 
 BEGIN;
@@ -510,17 +621,17 @@ SELECT pg_stat_force_next_flush();
  
 (1 row)
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       3
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           3
 (1 row)
 
 -- savepoint rolback (1 level)
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       3
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           3
 (1 row)
 
 BEGIN;
@@ -529,17 +640,17 @@ DROP TABLE drop_stats_test_subxact;
 SAVEPOINT sp2;
 ROLLBACK TO SAVEPOINT sp1;
 COMMIT;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       3
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           3
 (1 row)
 
 -- and now actually drop
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       3
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           3
 (1 row)
 
 BEGIN;
@@ -548,10 +659,10 @@ DROP TABLE drop_stats_test_subxact;
 SAVEPOINT sp2;
 RELEASE SAVEPOINT sp1;
 COMMIT;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       0
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           0
 (1 row)
 
 DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
@@ -1021,21 +1132,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
@@ -1051,14 +1162,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
@@ -1073,7 +1184,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
@@ -1081,7 +1192,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
@@ -1089,7 +1200,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
@@ -1097,7 +1208,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
@@ -1105,7 +1216,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 b4d6753c71..1e9ae74b2a 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
 ---
@@ -218,15 +289,15 @@ SELECT 'drop_stats_test_subxact'::regclass::oid AS drop_stats_test_subxact_oid \
 
 SELECT pg_stat_force_next_flush();
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_oid);
 DROP TABLE drop_stats_test;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_oid);
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_oid);
 
 -- check that rollback protects against having stats dropped and that local
 -- modifications don't pose a problem
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
 BEGIN;
 INSERT INTO drop_stats_test_xact DEFAULT VALUES;
@@ -235,12 +306,12 @@ DROP TABLE drop_stats_test_xact;
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
 ROLLBACK;
 SELECT pg_stat_force_next_flush();
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
 
 -- transactional drop
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
 BEGIN;
 INSERT INTO drop_stats_test_xact DEFAULT VALUES;
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
@@ -248,11 +319,11 @@ DROP TABLE drop_stats_test_xact;
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
 COMMIT;
 SELECT pg_stat_force_next_flush();
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
 
 -- savepoint rollback (2 levels)
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 BEGIN;
 INSERT INTO drop_stats_test_subxact DEFAULT VALUES;
 SAVEPOINT sp1;
@@ -264,27 +335,27 @@ ROLLBACK TO SAVEPOINT sp2;
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_subxact_oid);
 COMMIT;
 SELECT pg_stat_force_next_flush();
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 
 -- savepoint rolback (1 level)
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 BEGIN;
 SAVEPOINT sp1;
 DROP TABLE drop_stats_test_subxact;
 SAVEPOINT sp2;
 ROLLBACK TO SAVEPOINT sp1;
 COMMIT;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 
 -- and now actually drop
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 BEGIN;
 SAVEPOINT sp1;
 DROP TABLE drop_stats_test_subxact;
 SAVEPOINT sp2;
 RELEASE SAVEPOINT sp1;
 COMMIT;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 
 DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
 DROP TABLE prevstats;
@@ -493,40 +564,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 24510ac29e..81e2ca161d 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2015,10 +2015,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
@@ -2032,6 +2033,7 @@ PgStat_FetchConsistency
 PgStat_FunctionCallUsage
 PgStat_FunctionCounts
 PgStat_HashKey
+PgStat_IndexStatus
 PgStat_Kind
 PgStat_KindInfo
 PgStat_LocalState
#24Michael Paquier
michael@paquier.xyz
In reply to: Drouvot, Bertrand (#23)
Re: Split index and table statistics into different types of stats

On Sat, Jan 21, 2023 at 06:42:51AM +0100, Drouvot, Bertrand wrote:

Please find attached a rebased version (previous comments
up-thread still need to be addressed though).

This patch has a lot of conflicts. Could you send a rebased version?
--
Michael

#25Drouvot, Bertrand
bertranddrouvot.pg@gmail.com
In reply to: Michael Paquier (#24)
1 attachment(s)
Re: Split index and table statistics into different types of stats

Hi,

On 3/16/23 7:54 AM, Michael Paquier wrote:

On Sat, Jan 21, 2023 at 06:42:51AM +0100, Drouvot, Bertrand wrote:

Please find attached a rebased version (previous comments
up-thread still need to be addressed though).

This patch has a lot of conflicts. Could you send a rebased version?

Thanks for looking at it!

Please find attached v10.

Please note that the previous comments
up-thread still need to be addressed though, and that this patch is also somehow linked to the:

"Generate pg_stat_get_xact*() functions with Macros" patch (see [1]https://commitfest.postgresql.org/42/4106/) (which itself has some dependencies, see [2]/messages/by-id/11744b0e-5f7f-aba8-7d9c-2ff0a0c6e2b2@gmail.com)

My plan was to get [1]https://commitfest.postgresql.org/42/4106/ done before resuming working on the "Split index and table statistics into different types of stats" one.

Regards,

[1]: https://commitfest.postgresql.org/42/4106/
[2]: /messages/by-id/11744b0e-5f7f-aba8-7d9c-2ff0a0c6e2b2@gmail.com

--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

Attachments:

v10-0001-split_tables_indexes_stats.patchtext/plain; charset=UTF-8; name=v10-0001-split_tables_indexes_stats.patchDownload
diff --git a/src/backend/access/common/relation.c b/src/backend/access/common/relation.c
index 4017e175e3..fca166a063 100644
--- a/src/backend/access/common/relation.c
+++ b/src/backend/access/common/relation.c
@@ -73,7 +73,10 @@ relation_open(Oid relationId, LOCKMODE lockmode)
 	if (RelationUsesLocalBuffers(r))
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
-	pgstat_init_relation(r);
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_init_index(r);
+	else
+		pgstat_init_table(r);
 
 	return r;
 }
@@ -123,7 +126,10 @@ try_relation_open(Oid relationId, LOCKMODE lockmode)
 	if (RelationUsesLocalBuffers(r))
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
-	pgstat_init_relation(r);
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_init_index(r);
+	else
+		pgstat_init_table(r);
 
 	return r;
 }
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index dbf147ed22..5b83146eef 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -590,7 +590,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 4f006820b8..4e9569c901 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 7777e7ec77..cb602484e7 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1765,7 +1765,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);
@@ -2339,7 +2339,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 34ca0e739f..5a0fe0d99a 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -654,29 +654,29 @@ 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_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,
-            pg_stat_get_tuples_hot_updated(C.oid) AS n_tup_hot_upd,
-            pg_stat_get_live_tuples(C.oid) AS n_live_tup,
-            pg_stat_get_dead_tuples(C.oid) AS n_dead_tup,
-            pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze,
-            pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum,
-            pg_stat_get_last_vacuum_time(C.oid) as last_vacuum,
-            pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum,
-            pg_stat_get_last_analyze_time(C.oid) as last_analyze,
-            pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze,
-            pg_stat_get_vacuum_count(C.oid) AS vacuum_count,
-            pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count,
-            pg_stat_get_analyze_count(C.oid) AS analyze_count,
-            pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count
+            pg_stat_get_tab_numscans(C.oid) AS seq_scan,
+            pg_stat_get_tab_lastscan(C.oid) AS last_seq_scan,
+            pg_stat_get_tab_tuples_returned(C.oid) AS seq_tup_read,
+            sum(pg_stat_get_ind_numscans(I.indexrelid))::bigint AS idx_scan,
+            max(pg_stat_get_ind_lastscan(I.indexrelid)) AS last_idx_scan,
+            sum(pg_stat_get_ind_tuples_fetched(I.indexrelid))::bigint +
+            pg_stat_get_tab_tuples_fetched(C.oid) AS idx_tup_fetch,
+            pg_stat_get_tab_tuples_inserted(C.oid) AS n_tup_ins,
+            pg_stat_get_tab_tuples_updated(C.oid) AS n_tup_upd,
+            pg_stat_get_tab_tuples_deleted(C.oid) AS n_tup_del,
+            pg_stat_get_tab_tuples_hot_updated(C.oid) AS n_tup_hot_upd,
+            pg_stat_get_tab_live_tuples(C.oid) AS n_live_tup,
+            pg_stat_get_tab_dead_tuples(C.oid) AS n_dead_tup,
+            pg_stat_get_tab_mod_since_analyze(C.oid) AS n_mod_since_analyze,
+            pg_stat_get_tab_ins_since_vacuum(C.oid) AS n_ins_since_vacuum,
+            pg_stat_get_tab_last_vacuum_time(C.oid) as last_vacuum,
+            pg_stat_get_tab_last_autovacuum_time(C.oid) as last_autovacuum,
+            pg_stat_get_tab_last_analyze_time(C.oid) as last_analyze,
+            pg_stat_get_tab_last_autoanalyze_time(C.oid) as last_autoanalyze,
+            pg_stat_get_tab_vacuum_count(C.oid) AS vacuum_count,
+            pg_stat_get_tab_autovacuum_count(C.oid) AS autovacuum_count,
+            pg_stat_get_tab_analyze_count(C.oid) AS analyze_count,
+            pg_stat_get_tab_autoanalyze_count(C.oid) AS autoanalyze_count
     FROM pg_class C LEFT JOIN
          pg_index I ON C.oid = I.indrelid
          LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
@@ -688,9 +688,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_tab_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_ind_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,
@@ -728,31 +728,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_tab_blocks_fetched(C.oid) -
+                    pg_stat_get_tab_blocks_hit(C.oid) AS heap_blks_read,
+            pg_stat_get_tab_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_tab_blocks_fetched(T.oid) -
+                    pg_stat_get_tab_blocks_hit(T.oid) AS toast_blks_read,
+            pg_stat_get_tab_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_ind_blocks_fetched(indexrelid) -
+                         pg_stat_get_ind_blocks_hit(indexrelid))::bigint
                      AS idx_blks_read,
-                     sum(pg_stat_get_blocks_hit(indexrelid))::bigint
+                     sum(pg_stat_get_ind_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_ind_blocks_fetched(indexrelid) -
+                         pg_stat_get_ind_blocks_hit(indexrelid))::bigint
                      AS idx_blks_read,
-                     sum(pg_stat_get_blocks_hit(indexrelid))::bigint
+                     sum(pg_stat_get_ind_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');
@@ -774,10 +774,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_ind_numscans(I.oid) AS idx_scan,
+            pg_stat_get_ind_lastscan(I.oid) AS last_idx_scan,
+            pg_stat_get_ind_tuples_returned(I.oid) AS idx_tup_read,
+            pg_stat_get_ind_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
@@ -801,9 +801,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_ind_blocks_fetched(I.oid) -
+                    pg_stat_get_ind_blocks_hit(I.oid) AS idx_blks_read,
+            pg_stat_get_ind_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
@@ -825,9 +825,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_ind_blocks_fetched(C.oid) -
+                    pg_stat_get_tab_blocks_hit(C.oid) AS blks_read,
+            pg_stat_get_tab_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 0a05577b68..daa3046bc6 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -768,11 +768,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/Makefile b/src/backend/utils/activity/Makefile
index 7d7482dde0..a73d529894 100644
--- a/src/backend/utils/activity/Makefile
+++ b/src/backend/utils/activity/Makefile
@@ -23,11 +23,12 @@ OBJS = \
 	pgstat_database.o \
 	pgstat_function.o \
 	pgstat_io.o \
-	pgstat_relation.o \
+	pgstat_index.o \
 	pgstat_replslot.o \
 	pgstat_shmem.o \
 	pgstat_slru.o \
 	pgstat_subscription.o \
+	pgstat_table.o \
 	pgstat_wal.o \
 	pgstat_xact.o \
 	wait_event.o
diff --git a/src/backend/utils/activity/meson.build b/src/backend/utils/activity/meson.build
index 518ee3f798..80d611c40d 100644
--- a/src/backend/utils/activity/meson.build
+++ b/src/backend/utils/activity/meson.build
@@ -10,11 +10,12 @@ backend_sources += files(
   'pgstat_database.c',
   'pgstat_function.c',
   'pgstat_io.c',
-  'pgstat_relation.c',
+  'pgstat_index.c',
   'pgstat_replslot.c',
   'pgstat_shmem.c',
   'pgstat_slru.c',
   'pgstat_subscription.c',
+  'pgstat_table.c',
   'pgstat_wal.c',
   'pgstat_xact.c',
   'wait_event.c',
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index b125802b21..03edd1265c 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -73,10 +73,11 @@
  * - pgstat_database.c
  * - pgstat_function.c
  * - pgstat_io.c
- * - pgstat_relation.c
+ * - pgstat_index.c
  * - pgstat_replslot.c
  * - pgstat_slru.c
  * - pgstat_subscription.c
+ * - pgstat_table.c
  * - pgstat_wal.c
  *
  * Whenever possible infrastructure files should not contain code related to
@@ -270,18 +271,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_index.c b/src/backend/utils/activity/pgstat_index.c
new file mode 100644
index 0000000000..882f6cf30d
--- /dev/null
+++ b/src/backend/utils/activity/pgstat_index.c
@@ -0,0 +1,297 @@
+/* -------------------------------------------------------------------------
+ *
+ * pgstat_index.c
+ *	  Implementation of index statistics.
+ *
+ * This file contains the implementation of function index. It is kept
+ * separate from pgstat.c to enforce the line between the statistics access /
+ * storage implementation and the details about individual types of
+ * statistics.
+ *
+ * Copyright (c) 2001-2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/activity/pgstat_index.c
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/xact.h"
+#include "utils/pgstat_internal.h"
+#include "utils/rel.h"
+#include "catalog/catalog.h"
+
+static PgStat_IndexStatus *pgstat_prep_index_pending(Oid rel_id, bool isshared);
+
+/*
+ * Copy stats between indexes. This is used for things like REINDEX
+ * CONCURRENTLY.
+ */
+void
+pgstat_copy_index_stats(Relation dst, Relation src)
+{
+	PgStat_StatIndEntry *srcstats;
+	PgStatShared_Index *dstshstats;
+	PgStat_EntryRef *dst_ref;
+
+	srcstats = pgstat_fetch_stat_indentry_ext(src->rd_rel->relisshared,
+											  RelationGetRelid(src));
+	if (!srcstats)
+		return;
+
+	dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_INDEX,
+										  dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+										  RelationGetRelid(dst),
+										  false);
+
+	dstshstats = (PgStatShared_Index *) dst_ref->shared_stats;
+	dstshstats->stats = *srcstats;
+
+	pgstat_unlock_entry(dst_ref);
+}
+
+/*
+ * Initialize a relcache entry to count access statistics.  Called whenever an
+ * index is opened.
+ *
+ * We assume that a relcache entry's pgstatind_info field is 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
+ * pgstat_assoc_index().
+ */
+void
+pgstat_init_index(Relation rel)
+{
+	/*
+	 * We only count stats for indexes
+	 */
+	Assert(rel->rd_rel->relkind == RELKIND_INDEX);
+
+	if (!pgstat_track_counts)
+	{
+		if (rel->pgstatind_info != NULL)
+			pgstat_unlink_index(rel);
+
+		/* We're not counting at all */
+		rel->pgstat_enabled = false;
+		rel->pgstatind_info = NULL;
+		return;
+	}
+
+	rel->pgstat_enabled = true;
+}
+
+/*
+ * 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_index() 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->pgstatind_info->relation == NULL);
+
+	/* mark this relation as the owner */
+	rel->pgstatind_info->relation = rel;
+}
+
+/*
+ * Break the mutual link between a relcache entry and pending index stats entry.
+ * This must be called whenever one end of the link is removed.
+ */
+void
+pgstat_unlink_index(Relation rel)
+{
+
+	if (rel->pgstatind_info == NULL)
+		return;
+
+	/* 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 index stats are dropped if transaction aborts.
+ */
+void
+pgstat_create_index(Relation rel)
+{
+	pgstat_create_transactional(PGSTAT_KIND_INDEX,
+								rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+								RelationGetRelid(rel));
+}
+
+/*
+ * Ensure that index stats are dropped if transaction commits.
+ */
+void
+pgstat_drop_index(Relation rel)
+{
+	pgstat_drop_transactional(PGSTAT_KIND_INDEX,
+							  rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+							  RelationGetRelid(rel));
+}
+
+/*
+ * 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)
+{
+	return pgstat_fetch_stat_indentry_ext(IsSharedRelation(relid), relid);
+}
+
+/*
+ * 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);
+}
+
+/*
+ * 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;
+	return NULL;
+}
+
+/*
+ * 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->tuples_returned += lstats->i_counts.i_tuples_returned;
+	dbentry->tuples_fetched += lstats->i_counts.i_tuples_fetched;
+	dbentry->blocks_fetched += lstats->i_counts.i_blocks_fetched;
+	dbentry->blocks_hit += lstats->i_counts.i_blocks_hit;
+
+	return true;
+}
+
+void
+pgstat_index_delete_pending_cb(PgStat_EntryRef *entry_ref)
+{
+	PgStat_IndexStatus *pending = (PgStat_IndexStatus *) entry_ref->pending;
+
+	if (pending->relation)
+		pgstat_unlink_index(pending->relation);
+}
+
+/*
+ * 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;
+}
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_table.c
similarity index 76%
rename from src/backend/utils/activity/pgstat_relation.c
rename to src/backend/utils/activity/pgstat_table.c
index f793ac1516..51ac4e41e2 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_table.c
@@ -1,9 +1,9 @@
 /* -------------------------------------------------------------------------
  *
- * pgstat_relation.c
- *	  Implementation of relation statistics.
+ * pgstat_table.c
+ *	  Implementation of table statistics.
  *
- * This file contains the implementation of function relation. It is kept
+ * This file contains the implementation of function table. It is kept
  * separate from pgstat.c to enforce the line between the statistics access /
  * storage implementation and the details about individual types of
  * statistics.
@@ -11,7 +11,7 @@
  * Copyright (c) 2001-2023, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *	  src/backend/utils/activity/pgstat_relation.c
+ *	  src/backend/utils/activity/pgstat_table.c
  * -------------------------------------------------------------------------
  */
 
@@ -44,74 +44,48 @@ 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 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
- * CONCURRENTLY.
- */
-void
-pgstat_copy_relation_stats(Relation dst, Relation src)
-{
-	PgStat_StatTabEntry *srcstats;
-	PgStatShared_Relation *dstshstats;
-	PgStat_EntryRef *dst_ref;
-
-	srcstats = pgstat_fetch_stat_tabentry_ext(src->rd_rel->relisshared,
-											  RelationGetRelid(src));
-	if (!srcstats)
-		return;
-
-	dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
-										  dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
-										  RelationGetRelid(dst),
-										  false);
-
-	dstshstats = (PgStatShared_Relation *) dst_ref->shared_stats;
-	dstshstats->stats = *srcstats;
-
-	pgstat_unlock_entry(dst_ref);
-}
-
 /*
  * Initialize a relcache entry to count access statistics.  Called whenever a
- * relation is opened.
+ * table is opened.
  *
- * We assume that a relcache entry's pgstat_info field is zeroed by relcache.c
+ * We assume that a relcache entry's pgstattab_info field is 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
- * pgstat_assoc_relation().
+ * pgstat_assoc_table().
  */
 void
-pgstat_init_relation(Relation rel)
+pgstat_init_table(Relation rel)
 {
 	char		relkind = rel->rd_rel->relkind;
 
+	Assert(relkind != RELKIND_INDEX);
+
 	/*
 	 * We only count stats for relations with storage and partitioned tables
 	 */
 	if (!RELKIND_HAS_STORAGE(relkind) && relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		rel->pgstat_enabled = false;
-		rel->pgstat_info = NULL;
+		rel->pgstattab_info = NULL;
 		return;
 	}
 
 	if (!pgstat_track_counts)
 	{
-		if (rel->pgstat_info)
-			pgstat_unlink_relation(rel);
+		if (rel->pgstattab_info != NULL)
+			pgstat_unlink_table(rel);
 
 		/* We're not counting at all */
 		rel->pgstat_enabled = false;
-		rel->pgstat_info = NULL;
+		rel->pgstattab_info = NULL;
 		return;
 	}
 
@@ -119,89 +93,92 @@ 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.
  *
- * This is separate from pgstat_init_relation() as it is not uncommon for
+ * This is separate from pgstat_init_table() as it is not uncommon for
  * 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->pgstat_info->relation == NULL);
+	Assert(rel->pgstattab_info->relation == NULL);
 
 	/* mark this relation as the owner */
-	rel->pgstat_info->relation = rel;
+	rel->pgstattab_info->relation = rel;
 }
 
 /*
- * Break the mutual link between a relcache entry and pending stats entry.
+ * Break the mutual link between a relcache entry and pending table stats entry.
  * This must be called whenever one end of the link is removed.
  */
 void
-pgstat_unlink_relation(Relation rel)
+pgstat_unlink_table(Relation rel)
 {
-	/* remove the link to stats info if any */
-	if (rel->pgstat_info == NULL)
+
+	if (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;
+	}
 }
 
 /*
- * Ensure that stats are dropped if transaction aborts.
+ * Ensure that table stats are dropped if transaction aborts.
  */
 void
-pgstat_create_relation(Relation rel)
+pgstat_create_table(Relation rel)
 {
-	pgstat_create_transactional(PGSTAT_KIND_RELATION,
+	pgstat_create_transactional(PGSTAT_KIND_TABLE,
 								rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
 								RelationGetRelid(rel));
 }
 
 /*
- * Ensure that stats are dropped if transaction commits.
+ * Ensure that table stats are dropped if transaction commits.
  */
 void
-pgstat_drop_relation(Relation rel)
+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;
 	}
 }
 
@@ -213,7 +190,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;
@@ -225,10 +202,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->live_tuples = livetuples;
@@ -280,7 +257,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);
 
@@ -299,31 +276,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->live_tuples = livetuples;
@@ -360,12 +337,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;
 	}
 }
 
@@ -375,16 +352,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++;
 	}
 }
 
@@ -394,12 +371,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++;
 	}
 }
 
@@ -409,15 +386,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;
 	}
 }
 
@@ -432,11 +409,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;
 	}
 }
 
@@ -462,7 +439,7 @@ 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);
 }
 
 /*
@@ -478,9 +455,9 @@ 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;
@@ -488,7 +465,7 @@ find_tabstat_entry(Oid rel_id)
 }
 
 /*
- * Perform relation stats specific end-of-transaction work. Helper for
+ * Perform table stats specific end-of-transaction work. Helper for
  * AtEOXact_PgStat.
  *
  * Transfer transactional insert/update counts into the base tabstat entries.
@@ -496,7 +473,7 @@ find_tabstat_entry(Oid rel_id)
  * TopTransactionContext and will go away anyway.
  */
 void
-AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit)
+AtEOXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit)
 {
 	PgStat_TableXactStatus *trans;
 
@@ -547,14 +524,14 @@ AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit)
 }
 
 /*
- * Perform relation stats specific end-of-sub-transaction work. Helper for
+ * Perform table stats specific end-of-sub-transaction work. Helper for
  * AtEOSubXact_PgStat.
  *
  * Transfer transactional insert/update counts into the next higher
  * subtransaction state.
  */
 void
-AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
+AtEOSubXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
 {
 	PgStat_TableXactStatus *trans;
 	PgStat_TableXactStatus *next_trans;
@@ -631,11 +608,11 @@ AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, in
 }
 
 /*
- * Generate 2PC records for all the pending transaction-dependent relation
+ * Generate 2PC records for all the pending transaction-dependent table
  * stats.
  */
 void
-AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
+AtPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state)
 {
 	PgStat_TableXactStatus *trans;
 
@@ -670,10 +647,10 @@ AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
  * reported to the stats system immediately, while the effects on live and
  * dead tuple counts are preserved in the 2PC state file.
  *
- * Note: AtEOXact_PgStat_Relations is not called during PREPARE.
+ * Note: AtEOXact_PgStat_Tables is not called during PREPARE.
  */
 void
-PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
+PostPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state)
 {
 	PgStat_TableXactStatus *trans;
 
@@ -696,27 +673,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;
 }
@@ -732,10 +709,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)
@@ -744,10 +721,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;
 }
 
@@ -761,22 +738,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)
@@ -843,12 +819,12 @@ pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
 }
 
 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;
 
 	if (pending->relation)
-		pgstat_unlink_relation(pending->relation);
+		pgstat_unlink_table(pending->relation);
 }
 
 /*
@@ -856,12 +832,12 @@ pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref)
  * 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;
@@ -875,7 +851,7 @@ pgstat_prep_relation_pending(Oid rel_id, bool isshared)
  * 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;
@@ -891,24 +867,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/activity/pgstat_xact.c b/src/backend/utils/activity/pgstat_xact.c
index 91cdd9222e..ee43362b73 100644
--- a/src/backend/utils/activity/pgstat_xact.c
+++ b/src/backend/utils/activity/pgstat_xact.c
@@ -51,7 +51,7 @@ AtEOXact_PgStat(bool isCommit, bool parallel)
 		Assert(xact_state->nest_level == 1);
 		Assert(xact_state->prev == NULL);
 
-		AtEOXact_PgStat_Relations(xact_state, isCommit);
+		AtEOXact_PgStat_Tables(xact_state, isCommit);
 		AtEOXact_PgStat_DroppedStats(xact_state, isCommit);
 	}
 	pgStatXactStack = NULL;
@@ -122,7 +122,7 @@ AtEOSubXact_PgStat(bool isCommit, int nestDepth)
 		/* delink xact_state from stack immediately to simplify reuse case */
 		pgStatXactStack = xact_state->prev;
 
-		AtEOSubXact_PgStat_Relations(xact_state, isCommit, nestDepth);
+		AtEOSubXact_PgStat_Tables(xact_state, isCommit, nestDepth);
 		AtEOSubXact_PgStat_DroppedStats(xact_state, isCommit, nestDepth);
 
 		pfree(xact_state);
@@ -197,7 +197,7 @@ AtPrepare_PgStat(void)
 		Assert(xact_state->nest_level == 1);
 		Assert(xact_state->prev == NULL);
 
-		AtPrepare_PgStat_Relations(xact_state);
+		AtPrepare_PgStat_Tables(xact_state);
 	}
 }
 
@@ -221,7 +221,7 @@ PostPrepare_PgStat(void)
 		Assert(xact_state->nest_level == 1);
 		Assert(xact_state->prev == NULL);
 
-		PostPrepare_PgStat_Relations(xact_state);
+		PostPrepare_PgStat_Tables(xact_state);
 	}
 	pgStatXactStack = NULL;
 
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 35c6d46555..dc469f1b96 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -37,106 +37,159 @@
 
 #define HAS_PGSTAT_PERMISSIONS(role)	 (has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS) || has_privs_of_role(GetUserId(), role))
 
-#define PG_STAT_GET_RELENTRY_INT64(stat)						\
-Datum															\
-CppConcat(pg_stat_get_,stat)(PG_FUNCTION_ARGS)					\
-{																\
-	Oid			relid = PG_GETARG_OID(0);						\
-	int64		result;											\
-	PgStat_StatTabEntry *tabentry;								\
-																\
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)	\
-		result = 0;												\
-	else														\
-		result = (int64) (tabentry->stat);						\
-																\
-	PG_RETURN_INT64(result);									\
-}
-
-/* pg_stat_get_analyze_count */
-PG_STAT_GET_RELENTRY_INT64(analyze_count)
-
-/* pg_stat_get_autoanalyze_count */
-PG_STAT_GET_RELENTRY_INT64(autoanalyze_count)
-
-/* pg_stat_get_autovacuum_count */
-PG_STAT_GET_RELENTRY_INT64(autovacuum_count)
-
-/* pg_stat_get_blocks_fetched */
-PG_STAT_GET_RELENTRY_INT64(blocks_fetched)
+#define PG_STAT_GET_TABENTRY_INT64(stat)							\
+Datum																\
+CppConcat(pg_stat_get_tab_,stat)(PG_FUNCTION_ARGS)					\
+{																	\
+	Oid			relid = PG_GETARG_OID(0);							\
+	int64		result;												\
+	PgStat_StatTabEntry *tabentry;									\
+																	\
+	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)		\
+		result = 0;													\
+	else															\
+		result = (int64) (tabentry->stat);							\
+																	\
+	PG_RETURN_INT64(result);										\
+}
+
+/* pg_stat_get_tab_analyze_count */
+PG_STAT_GET_TABENTRY_INT64(analyze_count)
+
+/* pg_stat_get_tab_autoanalyze_count */
+PG_STAT_GET_TABENTRY_INT64(autoanalyze_count)
+
+/* pg_stat_get_tab_autovacuum_count */
+PG_STAT_GET_TABENTRY_INT64(autovacuum_count)
+
+/* pg_stat_get_tab_blocks_fetched */
+PG_STAT_GET_TABENTRY_INT64(blocks_fetched)
+
+/* pg_stat_get_tab_blocks_hit */
+PG_STAT_GET_TABENTRY_INT64(blocks_hit)
 
-/* pg_stat_get_blocks_hit */
-PG_STAT_GET_RELENTRY_INT64(blocks_hit)
+/* pg_stat_get_tab_dead_tuples */
+PG_STAT_GET_TABENTRY_INT64(dead_tuples)
 
-/* pg_stat_get_dead_tuples */
-PG_STAT_GET_RELENTRY_INT64(dead_tuples)
-
-/* pg_stat_get_ins_since_vacuum */
-PG_STAT_GET_RELENTRY_INT64(ins_since_vacuum)
-
-/* pg_stat_get_live_tuples */
-PG_STAT_GET_RELENTRY_INT64(live_tuples)
-
-/* pg_stat_get_mods_since_analyze */
-PG_STAT_GET_RELENTRY_INT64(mod_since_analyze)
-
-/* pg_stat_get_numscans */
-PG_STAT_GET_RELENTRY_INT64(numscans)
-
-/* pg_stat_get_tuples_deleted */
-PG_STAT_GET_RELENTRY_INT64(tuples_deleted)
-
-/* pg_stat_get_tuples_fetched */
-PG_STAT_GET_RELENTRY_INT64(tuples_fetched)
-
-/* pg_stat_get_tuples_hot_updated */
-PG_STAT_GET_RELENTRY_INT64(tuples_hot_updated)
-
-/* pg_stat_get_tuples_inserted */
-PG_STAT_GET_RELENTRY_INT64(tuples_inserted)
-
-/* pg_stat_get_tuples_returned */
-PG_STAT_GET_RELENTRY_INT64(tuples_returned)
-
-/* pg_stat_get_tuples_updated */
-PG_STAT_GET_RELENTRY_INT64(tuples_updated)
-
-/* pg_stat_get_vacuum_count */
-PG_STAT_GET_RELENTRY_INT64(vacuum_count)
-
-#define PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat)					\
-Datum															\
-CppConcat(pg_stat_get_,stat)(PG_FUNCTION_ARGS)					\
-{																\
-	Oid			relid = PG_GETARG_OID(0);						\
-	TimestampTz result;											\
-	PgStat_StatTabEntry *tabentry;								\
-																\
-	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)	\
-		result = 0;												\
-	else														\
-		result = tabentry->stat;								\
-																\
-	if (result == 0)											\
-		PG_RETURN_NULL();										\
-	else														\
-		PG_RETURN_TIMESTAMPTZ(result);							\
+/* pg_stat_get_tab_ins_since_vacuum */
+PG_STAT_GET_TABENTRY_INT64(ins_since_vacuum)
+
+/* pg_stat_get_tab_live_tuples */
+PG_STAT_GET_TABENTRY_INT64(live_tuples)
+
+/* pg_stat_get_tab_mods_since_analyze */
+PG_STAT_GET_TABENTRY_INT64(mod_since_analyze)
+
+/* pg_stat_get_tab_numscans */
+PG_STAT_GET_TABENTRY_INT64(numscans)
+
+/* pg_stat_get_tab_tuples_deleted */
+PG_STAT_GET_TABENTRY_INT64(tuples_deleted)
+
+/* pg_stat_get_tab_tuples_fetched */
+PG_STAT_GET_TABENTRY_INT64(tuples_fetched)
+
+/* pg_stat_get_tab_tuples_hot_updated */
+PG_STAT_GET_TABENTRY_INT64(tuples_hot_updated)
+
+/* pg_stat_get_tab_tuples_inserted */
+PG_STAT_GET_TABENTRY_INT64(tuples_inserted)
+
+/* pg_stat_get_tab_tuples_returned */
+PG_STAT_GET_TABENTRY_INT64(tuples_returned)
+
+/* pg_stat_get_tab_tuples_updated */
+PG_STAT_GET_TABENTRY_INT64(tuples_updated)
+
+/* pg_stat_get_tab_vacuum_count */
+PG_STAT_GET_TABENTRY_INT64(vacuum_count)
+
+#define PG_STAT_GET_INDENTRY_INT64(stat)							\
+Datum																\
+CppConcat(pg_stat_get_ind_,stat)(PG_FUNCTION_ARGS)					\
+{																	\
+	Oid			relid = PG_GETARG_OID(0);							\
+	int64		result;												\
+	PgStat_StatIndEntry *indentry;									\
+																	\
+	if ((indentry = pgstat_fetch_stat_indentry(relid)) == NULL)		\
+		result = 0;													\
+	else															\
+		result = (int64) (indentry->stat);							\
+																	\
+	PG_RETURN_INT64(result);										\
+}
+
+/* pg_stat_get_ind_numscans */
+PG_STAT_GET_INDENTRY_INT64(numscans)
+
+/* pg_stat_get_ind_tuples_returned */
+PG_STAT_GET_INDENTRY_INT64(tuples_returned)
+
+/* pg_stat_get_ind_tuples_fetched */
+PG_STAT_GET_INDENTRY_INT64(tuples_fetched)
+
+/* pg_stat_get_ind_blocks_fetched */
+PG_STAT_GET_INDENTRY_INT64(blocks_fetched)
+
+/* pg_stat_get_ind_blocks_hit */
+PG_STAT_GET_INDENTRY_INT64(blocks_hit)
+
+#define PG_STAT_GET_TABENTRY_TIMESTAMPTZ(stat)						\
+Datum																\
+CppConcat(pg_stat_get_tab_,stat)(PG_FUNCTION_ARGS)					\
+{																	\
+	Oid			relid = PG_GETARG_OID(0);							\
+	TimestampTz result;												\
+	PgStat_StatTabEntry *tabentry;									\
+																	\
+	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)		\
+		result = 0;													\
+	else															\
+		result = tabentry->stat;									\
+																	\
+	if (result == 0)												\
+		PG_RETURN_NULL();											\
+	else															\
+		PG_RETURN_TIMESTAMPTZ(result);								\
 }
 
-/* pg_stat_get_last_analyze_time */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_analyze_time)
+/* pg_stat_get_tab_last_analyze_time */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(last_analyze_time)
 
-/* pg_stat_get_last_autoanalyze_time */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_autoanalyze_time)
+/* pg_stat_get_tab_last_autoanalyze_time */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(last_autoanalyze_time)
 
-/* pg_stat_get_last_autovacuum_time */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_autovacuum_time)
+/* pg_stat_get_tab_last_autovacuum_time */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(last_autovacuum_time)
 
-/* pg_stat_get_last_vacuum_time */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time)
+/* pg_stat_get_tab_last_vacuum_time */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(last_vacuum_time)
 
-/* pg_stat_get_lastscan */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan)
+/* pg_stat_get_tab_lastscan */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(lastscan)
+
+#define PG_STAT_GET_INDENTRY_TIMESTAMPTZ(stat)						\
+Datum																\
+CppConcat(pg_stat_get_ind_,stat)(PG_FUNCTION_ARGS)					\
+{																	\
+	Oid			relid = PG_GETARG_OID(0);							\
+	TimestampTz result;												\
+	PgStat_StatIndEntry *indentry;									\
+																	\
+	if ((indentry = pgstat_fetch_stat_indentry(relid)) == NULL)		\
+		result = 0;													\
+	else															\
+		result = indentry->stat;									\
+																	\
+	if (result == 0)												\
+		PG_RETURN_NULL();											\
+	else															\
+		PG_RETURN_TIMESTAMPTZ(result);								\
+}
+
+/* pg_stat_get_ind_lastscan */
+PG_STAT_GET_INDENTRY_TIMESTAMPTZ(lastscan)
 
 Datum
 pg_stat_get_function_calls(PG_FUNCTION_ARGS)
@@ -1496,7 +1549,7 @@ pg_stat_get_slru(PG_FUNCTION_ARGS)
 }
 
 Datum
-pg_stat_get_xact_numscans(PG_FUNCTION_ARGS)
+pg_stat_get_tab_xact_numscans(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 	int64		result;
@@ -1510,17 +1563,32 @@ pg_stat_get_xact_numscans(PG_FUNCTION_ARGS)
 	PG_RETURN_INT64(result);
 }
 
+Datum
+pg_stat_get_ind_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);
 }
@@ -1530,12 +1598,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);
 }
@@ -1623,12 +1691,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);
 }
@@ -1638,12 +1706,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);
 }
@@ -1769,7 +1837,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 13f7987373..d93b872653 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -2411,7 +2411,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 	RelationCloseSmgr(relation);
 
 	/* break mutual link with stats entry */
-	pgstat_unlink_relation(relation);
+	if (relation->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_unlink_index(relation);
+	else
+		pgstat_unlink_table(relation);
 
 	/*
 	 * Free all the subsidiary data structures of the relcache entry, then the
@@ -2727,8 +2730,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)
@@ -6321,7 +6325,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 fbc4aade49..f5b2b46612 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5312,96 +5312,120 @@
   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_tab_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_tab_numscans' },
+{ oid => '8296', descr => 'statistics: number of scans done for index',
+  proname => 'pg_stat_get_ind_numscans', provolatile => 's', proparallel => 'r',
+  prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_ind_numscans' },
+{ oid => '9976', descr => 'statistics: time of the last scan for table',
+  proname => 'pg_stat_get_tab_lastscan', provolatile => 's', proparallel => 'r',
+  prorettype => 'timestamptz', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tab_lastscan' },
+{ oid => '8626', descr => 'statistics: time of the last scan for index',
+  proname => 'pg_stat_get_ind_lastscan', provolatile => 's', proparallel => 'r',
   prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_lastscan' },
+  prosrc => 'pg_stat_get_ind_lastscan' },
 { oid => '1929', descr => 'statistics: number of tuples read by seqscan',
-  proname => 'pg_stat_get_tuples_returned', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_returned', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_returned' },
+  prosrc => 'pg_stat_get_tab_tuples_returned' },
+{ oid => '9603', descr => 'statistics: number of tuples read by seqscan',
+  proname => 'pg_stat_get_ind_tuples_returned', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_ind_tuples_returned' },
 { oid => '1930', descr => 'statistics: number of tuples fetched by idxscan',
-  proname => 'pg_stat_get_tuples_fetched', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_fetched', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tab_tuples_fetched' },
+{ oid => '8526', descr => 'statistics: number of tuples fetched by idxscan',
+  proname => 'pg_stat_get_ind_tuples_fetched', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_fetched' },
+  prosrc => 'pg_stat_get_ind_tuples_fetched' },
 { oid => '1931', descr => 'statistics: number of tuples inserted',
-  proname => 'pg_stat_get_tuples_inserted', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_inserted', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_inserted' },
+  prosrc => 'pg_stat_get_tab_tuples_inserted' },
 { oid => '1932', descr => 'statistics: number of tuples updated',
-  proname => 'pg_stat_get_tuples_updated', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_updated', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_updated' },
+  prosrc => 'pg_stat_get_tab_tuples_updated' },
 { oid => '1933', descr => 'statistics: number of tuples deleted',
-  proname => 'pg_stat_get_tuples_deleted', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_deleted', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_deleted' },
+  prosrc => 'pg_stat_get_tab_tuples_deleted' },
 { oid => '1972', descr => 'statistics: number of tuples hot updated',
-  proname => 'pg_stat_get_tuples_hot_updated', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_hot_updated', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_hot_updated' },
+  prosrc => 'pg_stat_get_tab_tuples_hot_updated' },
 { oid => '2878', descr => 'statistics: number of live tuples',
-  proname => 'pg_stat_get_live_tuples', provolatile => 's', proparallel => 'r',
+  proname => 'pg_stat_get_tab_live_tuples', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_live_tuples' },
+  prosrc => 'pg_stat_get_tab_live_tuples' },
 { oid => '2879', descr => 'statistics: number of dead tuples',
-  proname => 'pg_stat_get_dead_tuples', provolatile => 's', proparallel => 'r',
+  proname => 'pg_stat_get_tab_dead_tuples', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_dead_tuples' },
+  prosrc => 'pg_stat_get_tab_dead_tuples' },
 { oid => '3177',
   descr => 'statistics: number of tuples changed since last analyze',
-  proname => 'pg_stat_get_mod_since_analyze', provolatile => 's',
+  proname => 'pg_stat_get_tab_mod_since_analyze', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_mod_since_analyze' },
+  prosrc => 'pg_stat_get_tab_mod_since_analyze' },
 { oid => '5053',
   descr => 'statistics: number of tuples inserted since last vacuum',
-  proname => 'pg_stat_get_ins_since_vacuum', provolatile => 's',
+  proname => 'pg_stat_get_tab_ins_since_vacuum', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_ins_since_vacuum' },
+  prosrc => 'pg_stat_get_tab_ins_since_vacuum' },
 { oid => '1934', descr => 'statistics: number of blocks fetched',
-  proname => 'pg_stat_get_blocks_fetched', provolatile => 's',
+  proname => 'pg_stat_get_tab_blocks_fetched', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_blocks_fetched' },
+  prosrc => 'pg_stat_get_tab_blocks_fetched' },
+{ oid => '9432', descr => 'statistics: number of blocks fetched',
+  proname => 'pg_stat_get_ind_blocks_fetched', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_ind_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_tab_blocks_hit', provolatile => 's', proparallel => 'r',
+  prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tab_blocks_hit' },
+{ oid => '8354', descr => 'statistics: number of blocks found in cache',
+  proname => 'pg_stat_get_ind_blocks_hit', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_blocks_hit' },
+  prosrc => 'pg_stat_get_ind_blocks_hit' },
 { oid => '2781', descr => 'statistics: last manual vacuum time for a table',
-  proname => 'pg_stat_get_last_vacuum_time', provolatile => 's',
+  proname => 'pg_stat_get_tab_last_vacuum_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_last_vacuum_time' },
+  prosrc => 'pg_stat_get_tab_last_vacuum_time' },
 { oid => '2782', descr => 'statistics: last auto vacuum time for a table',
-  proname => 'pg_stat_get_last_autovacuum_time', provolatile => 's',
+  proname => 'pg_stat_get_tab_last_autovacuum_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_last_autovacuum_time' },
+  prosrc => 'pg_stat_get_tab_last_autovacuum_time' },
 { oid => '2783', descr => 'statistics: last manual analyze time for a table',
-  proname => 'pg_stat_get_last_analyze_time', provolatile => 's',
+  proname => 'pg_stat_get_tab_last_analyze_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_last_analyze_time' },
+  prosrc => 'pg_stat_get_tab_last_analyze_time' },
 { oid => '2784', descr => 'statistics: last auto analyze time for a table',
-  proname => 'pg_stat_get_last_autoanalyze_time', provolatile => 's',
+  proname => 'pg_stat_get_tab_last_autoanalyze_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_last_autoanalyze_time' },
+  prosrc => 'pg_stat_get_tab_last_autoanalyze_time' },
 { oid => '3054', descr => 'statistics: number of manual vacuums for a table',
-  proname => 'pg_stat_get_vacuum_count', provolatile => 's', proparallel => 'r',
+  proname => 'pg_stat_get_tab_vacuum_count', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_vacuum_count' },
+  prosrc => 'pg_stat_get_tab_vacuum_count' },
 { oid => '3055', descr => 'statistics: number of auto vacuums for a table',
-  proname => 'pg_stat_get_autovacuum_count', provolatile => 's',
+  proname => 'pg_stat_get_tab_autovacuum_count', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_autovacuum_count' },
+  prosrc => 'pg_stat_get_tab_autovacuum_count' },
 { oid => '3056', descr => 'statistics: number of manual analyzes for a table',
-  proname => 'pg_stat_get_analyze_count', provolatile => 's',
+  proname => 'pg_stat_get_tab_analyze_count', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_analyze_count' },
+  prosrc => 'pg_stat_get_tab_analyze_count' },
 { oid => '3057', descr => 'statistics: number of auto analyzes for a table',
-  proname => 'pg_stat_get_autoanalyze_count', provolatile => 's',
+  proname => 'pg_stat_get_tab_autoanalyze_count', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_autoanalyze_count' },
+  prosrc => 'pg_stat_get_tab_autoanalyze_count' },
 { oid => '1936', descr => 'statistics: currently active backend IDs',
   proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't',
   provolatile => 's', proparallel => 'r', prorettype => 'int4',
@@ -5773,10 +5797,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_tab_xact_numscans', provolatile => 'v',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tab_xact_numscans' },
+{ oid => '8892',
+  descr => 'statistics: number of scans done for index in current transaction',
+  proname => 'pg_stat_get_ind_xact_numscans', provolatile => 'v',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_xact_numscans' },
+  prosrc => 'pg_stat_get_ind_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 1e418b682b..4d3eb51f80 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 */
@@ -137,6 +138,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
  *
@@ -144,12 +167,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,
@@ -202,6 +222,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
  * ----------
@@ -420,6 +456,17 @@ typedef struct PgStat_StatTabEntry
 	PgStat_Counter autoanalyze_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;
@@ -551,16 +598,15 @@ extern PgStat_FunctionCounts *find_funcstat_entry(Oid func_id);
 
 
 /*
- * Functions in pgstat_relation.c
+ * Functions in pgstat_table.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_drop_table(Relation rel);
+extern void pgstat_init_table(Relation rel);
 
-extern void pgstat_init_relation(Relation rel);
-extern void pgstat_assoc_relation(Relation rel);
-extern void pgstat_unlink_relation(Relation rel);
+extern void pgstat_assoc_table(Relation rel);
+extern void pgstat_unlink_table(Relation rel);
 
 extern void pgstat_report_vacuum(Oid tableoid, bool shared,
 								 PgStat_Counter livetuples, PgStat_Counter deadtuples);
@@ -568,53 +614,83 @@ extern void pgstat_report_analyze(Relation rel,
 								  PgStat_Counter livetuples, PgStat_Counter deadtuples,
 								  bool resetcounter);
 
+/*
+ * Functions in pgstat_index.c
+ */
+
+extern void pgstat_create_index(Relation rel);
+extern void pgstat_drop_index(Relation rel);
+extern void pgstat_copy_index_stats(Relation dst, Relation src);
+extern void pgstat_init_index(Relation rel);
+extern void pgstat_assoc_index(Relation rel);
+extern void pgstat_unlink_index(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_table()
+ * and pgstat_init_index().
  */
-#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);
@@ -630,6 +706,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 6badb2fde4..26a780c76a 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -370,11 +370,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
 {
@@ -573,16 +579,23 @@ extern void pgstat_io_snapshot_cb(void);
 
 
 /*
- * Functions in pgstat_relation.c
+ * Functions in pgstat_table.c
  */
 
-extern void AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit);
-extern void AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth);
-extern void AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state);
-extern void PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state);
+extern void AtEOXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit);
+extern void AtEOSubXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth);
+extern void AtPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state);
+extern void PostPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state);
+
+extern bool pgstat_table_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+extern void pgstat_table_delete_pending_cb(PgStat_EntryRef *entry_ref);
+
+/*
+ * Functions in pgstat_index.c
+ */
 
-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_index_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+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 67f994cb3e..7970011b83 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..494adee88b 100644
--- a/src/test/isolation/expected/stats.out
+++ b/src/test/isolation/expected/stats.out
@@ -2166,14 +2166,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2210,14 +2210,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2236,14 +2236,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2275,14 +2275,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2314,14 +2314,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2369,14 +2369,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2401,14 +2401,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2463,14 +2463,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2495,14 +2495,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2558,14 +2558,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2627,14 +2627,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2688,14 +2688,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2755,14 +2755,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2795,14 +2795,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2841,14 +2841,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2881,14 +2881,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2927,14 +2927,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2968,14 +2968,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -3015,14 +3015,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
diff --git a/src/test/isolation/expected/stats_1.out b/src/test/isolation/expected/stats_1.out
index 3854320106..7981747732 100644
--- a/src/test/isolation/expected/stats_1.out
+++ b/src/test/isolation/expected/stats_1.out
@@ -2174,14 +2174,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2218,14 +2218,14 @@ 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_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2244,14 +2244,14 @@ 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_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2283,14 +2283,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2322,14 +2322,14 @@ 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_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2377,14 +2377,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2409,14 +2409,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2471,14 +2471,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2503,14 +2503,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2566,14 +2566,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2635,14 +2635,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2698,14 +2698,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2767,14 +2767,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2809,14 +2809,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2857,14 +2857,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2899,14 +2899,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2947,14 +2947,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2990,14 +2990,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -3039,14 +3039,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
diff --git a/src/test/isolation/specs/stats.spec b/src/test/isolation/specs/stats.spec
index 5b922d788c..53d78f7db1 100644
--- a/src/test/isolation/specs/stats.spec
+++ b/src/test/isolation/specs/stats.spec
@@ -93,14 +93,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 }
diff --git a/src/test/recovery/t/029_stats_restart.pl b/src/test/recovery/t/029_stats_restart.pl
index 83d6647d32..8b0b597419 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 51495aebcd..042ae55f17 100644
--- a/src/test/recovery/t/030_stats_cleanup_replica.pl
+++ b/src/test/recovery/t/030_stats_cleanup_replica.pl
@@ -179,7 +179,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 e953d1f515..5d6219fb51 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1767,10 +1767,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_ind_numscans(i.oid) AS idx_scan,
+    pg_stat_get_ind_lastscan(i.oid) AS last_idx_scan,
+    pg_stat_get_ind_tuples_returned(i.oid) AS idx_tup_read,
+    pg_stat_get_ind_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)))
@@ -1779,28 +1779,28 @@ 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_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,
-    pg_stat_get_tuples_hot_updated(c.oid) AS n_tup_hot_upd,
-    pg_stat_get_live_tuples(c.oid) AS n_live_tup,
-    pg_stat_get_dead_tuples(c.oid) AS n_dead_tup,
-    pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze,
-    pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum,
-    pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum,
-    pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum,
-    pg_stat_get_last_analyze_time(c.oid) AS last_analyze,
-    pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze,
-    pg_stat_get_vacuum_count(c.oid) AS vacuum_count,
-    pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count,
-    pg_stat_get_analyze_count(c.oid) AS analyze_count,
-    pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count
+    pg_stat_get_tab_numscans(c.oid) AS seq_scan,
+    pg_stat_get_tab_lastscan(c.oid) AS last_seq_scan,
+    pg_stat_get_tab_tuples_returned(c.oid) AS seq_tup_read,
+    (sum(pg_stat_get_ind_numscans(i.indexrelid)))::bigint AS idx_scan,
+    max(pg_stat_get_ind_lastscan(i.indexrelid)) AS last_idx_scan,
+    ((sum(pg_stat_get_ind_tuples_fetched(i.indexrelid)))::bigint + pg_stat_get_tab_tuples_fetched(c.oid)) AS idx_tup_fetch,
+    pg_stat_get_tab_tuples_inserted(c.oid) AS n_tup_ins,
+    pg_stat_get_tab_tuples_updated(c.oid) AS n_tup_upd,
+    pg_stat_get_tab_tuples_deleted(c.oid) AS n_tup_del,
+    pg_stat_get_tab_tuples_hot_updated(c.oid) AS n_tup_hot_upd,
+    pg_stat_get_tab_live_tuples(c.oid) AS n_live_tup,
+    pg_stat_get_tab_dead_tuples(c.oid) AS n_dead_tup,
+    pg_stat_get_tab_mod_since_analyze(c.oid) AS n_mod_since_analyze,
+    pg_stat_get_tab_ins_since_vacuum(c.oid) AS n_ins_since_vacuum,
+    pg_stat_get_tab_last_vacuum_time(c.oid) AS last_vacuum,
+    pg_stat_get_tab_last_autovacuum_time(c.oid) AS last_autovacuum,
+    pg_stat_get_tab_last_analyze_time(c.oid) AS last_analyze,
+    pg_stat_get_tab_last_autoanalyze_time(c.oid) AS last_autoanalyze,
+    pg_stat_get_tab_vacuum_count(c.oid) AS vacuum_count,
+    pg_stat_get_tab_autovacuum_count(c.oid) AS autovacuum_count,
+    pg_stat_get_tab_analyze_count(c.oid) AS analyze_count,
+    pg_stat_get_tab_autoanalyze_count(c.oid) AS autoanalyze_count
    FROM ((pg_class c
      LEFT JOIN pg_index i ON ((c.oid = i.indrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
@@ -2237,9 +2237,9 @@ pg_stat_wal_receiver| SELECT 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_tab_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_ind_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,
@@ -2290,8 +2290,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_ind_blocks_fetched(i.oid) - pg_stat_get_ind_blocks_hit(i.oid)) AS idx_blks_read,
+    pg_stat_get_ind_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)))
@@ -2300,31 +2300,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_ind_blocks_fetched(c.oid) - pg_stat_get_tab_blocks_hit(c.oid)) AS blks_read,
+    pg_stat_get_tab_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_tab_blocks_fetched(c.oid) - pg_stat_get_tab_blocks_hit(c.oid)) AS heap_blks_read,
+    pg_stat_get_tab_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_tab_blocks_fetched(t.oid) - pg_stat_get_tab_blocks_hit(t.oid)) AS toast_blks_read,
+    pg_stat_get_tab_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_ind_blocks_fetched(pg_index.indexrelid) - pg_stat_get_ind_blocks_hit(pg_index.indexrelid))))::bigint AS idx_blks_read,
+            (sum(pg_stat_get_ind_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_ind_blocks_fetched(pg_index.indexrelid) - pg_stat_get_ind_blocks_hit(pg_index.indexrelid))))::bigint AS idx_blks_read,
+            (sum(pg_stat_get_ind_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 186c296299..a821192004 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;$$;
@@ -357,17 +468,17 @@ SELECT pg_stat_force_next_flush();
  
 (1 row)
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           1
 (1 row)
 
 DROP TABLE drop_stats_test;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       0
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           0
 (1 row)
 
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_oid);
@@ -378,18 +489,18 @@ SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_oid);
 
 -- check that rollback protects against having stats dropped and that local
 -- modifications don't pose a problem
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
-(1 row)
-
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_tuples_inserted 
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_live_tuples 
 -----------------------------
                            1
 (1 row)
 
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_tuples_inserted 
+---------------------------------
+                               1
+(1 row)
+
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
  pg_stat_get_xact_tuples_inserted 
 ----------------------------------
@@ -418,29 +529,29 @@ SELECT pg_stat_force_next_flush();
  
 (1 row)
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           1
 (1 row)
 
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_tuples_inserted 
------------------------------
-                           2
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_tuples_inserted 
+---------------------------------
+                               2
 (1 row)
 
 -- transactional drop
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           1
 (1 row)
 
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_tuples_inserted 
------------------------------
-                           2
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_tuples_inserted 
+---------------------------------
+                               2
 (1 row)
 
 BEGIN;
@@ -465,23 +576,23 @@ SELECT pg_stat_force_next_flush();
  
 (1 row)
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       0
-(1 row)
-
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_tuples_inserted 
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_live_tuples 
 -----------------------------
                            0
 (1 row)
 
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_tuples_inserted 
+---------------------------------
+                               0
+(1 row)
+
 -- savepoint rollback (2 levels)
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           1
 (1 row)
 
 BEGIN;
@@ -510,17 +621,17 @@ SELECT pg_stat_force_next_flush();
  
 (1 row)
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       3
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           3
 (1 row)
 
 -- savepoint rolback (1 level)
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       3
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           3
 (1 row)
 
 BEGIN;
@@ -529,17 +640,17 @@ DROP TABLE drop_stats_test_subxact;
 SAVEPOINT sp2;
 ROLLBACK TO SAVEPOINT sp1;
 COMMIT;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       3
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           3
 (1 row)
 
 -- and now actually drop
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       3
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           3
 (1 row)
 
 BEGIN;
@@ -548,10 +659,10 @@ DROP TABLE drop_stats_test_subxact;
 SAVEPOINT sp2;
 RELEASE SAVEPOINT sp1;
 COMMIT;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       0
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           0
 (1 row)
 
 DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
@@ -1021,21 +1132,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
@@ -1051,14 +1162,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
@@ -1073,7 +1184,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
@@ -1081,7 +1192,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
@@ -1089,7 +1200,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
@@ -1097,7 +1208,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
@@ -1105,7 +1216,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 d7f873cfc9..a97bc6fe09 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
 ---
@@ -218,15 +289,15 @@ SELECT 'drop_stats_test_subxact'::regclass::oid AS drop_stats_test_subxact_oid \
 
 SELECT pg_stat_force_next_flush();
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_oid);
 DROP TABLE drop_stats_test;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_oid);
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_oid);
 
 -- check that rollback protects against having stats dropped and that local
 -- modifications don't pose a problem
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
 BEGIN;
 INSERT INTO drop_stats_test_xact DEFAULT VALUES;
@@ -235,12 +306,12 @@ DROP TABLE drop_stats_test_xact;
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
 ROLLBACK;
 SELECT pg_stat_force_next_flush();
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
 
 -- transactional drop
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
 BEGIN;
 INSERT INTO drop_stats_test_xact DEFAULT VALUES;
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
@@ -248,11 +319,11 @@ DROP TABLE drop_stats_test_xact;
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
 COMMIT;
 SELECT pg_stat_force_next_flush();
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
 
 -- savepoint rollback (2 levels)
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 BEGIN;
 INSERT INTO drop_stats_test_subxact DEFAULT VALUES;
 SAVEPOINT sp1;
@@ -264,27 +335,27 @@ ROLLBACK TO SAVEPOINT sp2;
 SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_subxact_oid);
 COMMIT;
 SELECT pg_stat_force_next_flush();
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 
 -- savepoint rolback (1 level)
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 BEGIN;
 SAVEPOINT sp1;
 DROP TABLE drop_stats_test_subxact;
 SAVEPOINT sp2;
 ROLLBACK TO SAVEPOINT sp1;
 COMMIT;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 
 -- and now actually drop
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 BEGIN;
 SAVEPOINT sp1;
 DROP TABLE drop_stats_test_subxact;
 SAVEPOINT sp2;
 RELEASE SAVEPOINT sp1;
 COMMIT;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 
 DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
 DROP TABLE prevstats;
@@ -493,40 +564,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 097f42e1b3..6cebdb026a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2026,10 +2026,11 @@ PgStatShared_Database
 PgStatShared_Function
 PgStatShared_HashEntry
 PgStatShared_IO
-PgStatShared_Relation
+PgStatShared_Index
 PgStatShared_ReplSlot
 PgStatShared_SLRU
 PgStatShared_Subscription
+PgStatShared_Table
 PgStatShared_Wal
 PgStat_ArchiverStats
 PgStat_BackendSubEntry
@@ -2043,6 +2044,7 @@ PgStat_FetchConsistency
 PgStat_FunctionCallUsage
 PgStat_FunctionCounts
 PgStat_HashKey
+PgStat_IndexStatus
 PgStat_IO
 PgStat_Kind
 PgStat_KindInfo
#26Michael Paquier
michael@paquier.xyz
In reply to: Drouvot, Bertrand (#25)
Re: Split index and table statistics into different types of stats

On Thu, Mar 16, 2023 at 10:24:32AM +0100, Drouvot, Bertrand wrote:

My plan was to get [1] done before resuming working on the "Split
index and table statistics into different types of stats" one.

Okay, I was unsure what should be the order here. Let's see about [1]
first, then.
--
Michael

#27Gregory Stark (as CFM)
stark.cfm@gmail.com
In reply to: Drouvot, Bertrand (#25)
Re: Split index and table statistics into different types of stats

On Thu, 16 Mar 2023 at 05:25, Drouvot, Bertrand
<bertranddrouvot.pg@gmail.com> wrote:

My plan was to get [1] done before resuming working on the "Split index and table statistics into different types of stats" one.
[1]: https://commitfest.postgresql.org/42/4106/

Generate pg_stat_get_xact*() functions with Macros ([1]) was committed March 27.

There's only a few days left in this CF. Would you like to leave this
here? Should it be marked Needs Review or Ready for Commit? Or should
we move it to the next CF now?

--
Gregory Stark
As Commitfest Manager

#28Drouvot, Bertrand
bertranddrouvot.pg@gmail.com
In reply to: Gregory Stark (as CFM) (#27)
Re: Split index and table statistics into different types of stats

Hi,

On 4/3/23 11:47 PM, Gregory Stark (as CFM) wrote:

On Thu, 16 Mar 2023 at 05:25, Drouvot, Bertrand
<bertranddrouvot.pg@gmail.com> wrote:

My plan was to get [1] done before resuming working on the "Split index and table statistics into different types of stats" one.
[1]: https://commitfest.postgresql.org/42/4106/

Generate pg_stat_get_xact*() functions with Macros ([1]) was committed March 27.

There's only a few days left in this CF. Would you like to leave this
here? Should it be marked Needs Review or Ready for Commit? Or should
we move it to the next CF now?

I moved it to the next commitfest and marked the target version as v17.

Regards,

--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

#29Michael Paquier
michael@paquier.xyz
In reply to: Drouvot, Bertrand (#28)
Re: Split index and table statistics into different types of stats

On Tue, Apr 04, 2023 at 12:04:35PM +0200, Drouvot, Bertrand wrote:

I moved it to the next commitfest and marked the target version as
v17.

Thanks for moving it. I think that we should be able to do a bit more
work for the switch to macros in pgstatfuncs.c, but this is going to
require more review than the feature freeze date allow, I am afraid.
--
Michael

#30Daniel Gustafsson
daniel@yesql.se
In reply to: Drouvot, Bertrand (#28)
Re: Split index and table statistics into different types of stats

On 4 Apr 2023, at 12:04, Drouvot, Bertrand <bertranddrouvot.pg@gmail.com> wrote:
On 4/3/23 11:47 PM, Gregory Stark (as CFM) wrote:

On Thu, 16 Mar 2023 at 05:25, Drouvot, Bertrand
<bertranddrouvot.pg@gmail.com> wrote:

My plan was to get [1] done before resuming working on the "Split index and table statistics into different types of stats" one.
[1]: https://commitfest.postgresql.org/42/4106/

Generate pg_stat_get_xact*() functions with Macros ([1]) was committed March 27.
There's only a few days left in this CF. Would you like to leave this
here? Should it be marked Needs Review or Ready for Commit? Or should
we move it to the next CF now?

I moved it to the next commitfest and marked the target version as v17.

This patch no longer applies (with tests failing when it did), and the thread
has stalled. I'm marking this returned with feedback for now, please feel free
to resubmit to a future CF with a new version of the patch.

--
Daniel Gustafsson

#31Drouvot, Bertrand
bertranddrouvot.pg@gmail.com
In reply to: Daniel Gustafsson (#30)
Re: Split index and table statistics into different types of stats

Hi,

On 7/10/23 11:14 AM, Daniel Gustafsson wrote:

On 4 Apr 2023, at 12:04, Drouvot, Bertrand <bertranddrouvot.pg@gmail.com> wrote:
On 4/3/23 11:47 PM, Gregory Stark (as CFM) wrote:

On Thu, 16 Mar 2023 at 05:25, Drouvot, Bertrand
<bertranddrouvot.pg@gmail.com> wrote:

My plan was to get [1] done before resuming working on the "Split index and table statistics into different types of stats" one.
[1]: https://commitfest.postgresql.org/42/4106/

Generate pg_stat_get_xact*() functions with Macros ([1]) was committed March 27.
There's only a few days left in this CF. Would you like to leave this
here? Should it be marked Needs Review or Ready for Commit? Or should
we move it to the next CF now?

I moved it to the next commitfest and marked the target version as v17.

This patch no longer applies (with tests failing when it did), and the thread
has stalled. I'm marking this returned with feedback for now, please feel free
to resubmit to a future CF with a new version of the patch.

Thanks for the update.
I'll resume working on it and re-submit once done.

Regards,

--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

#32Drouvot, Bertrand
bertranddrouvot.pg@gmail.com
In reply to: Daniel Gustafsson (#30)
1 attachment(s)
Re: Split index and table statistics into different types of stats

Hi,

On 7/10/23 11:14 AM, Daniel Gustafsson wrote:

On 4 Apr 2023, at 12:04, Drouvot, Bertrand <bertranddrouvot.pg@gmail.com> wrote:
On 4/3/23 11:47 PM, Gregory Stark (as CFM) wrote:

On Thu, 16 Mar 2023 at 05:25, Drouvot, Bertrand
<bertranddrouvot.pg@gmail.com> wrote:

My plan was to get [1] done before resuming working on the "Split index and table statistics into different types of stats" one.
[1]: https://commitfest.postgresql.org/42/4106/

Generate pg_stat_get_xact*() functions with Macros ([1]) was committed March 27.
There's only a few days left in this CF. Would you like to leave this
here? Should it be marked Needs Review or Ready for Commit? Or should
we move it to the next CF now?

I moved it to the next commitfest and marked the target version as v17.

This patch no longer applies (with tests failing when it did), and the thread
has stalled. I'm marking this returned with feedback for now, please feel free
to resubmit to a future CF with a new version of the patch.

FWIW, attached a rebased version as V11.

Will now work on addressing the up-thread remaining comments.

Regards,

--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

Attachments:

v11-0001-split-tables-and-indexes-stats.patchtext/plain; charset=UTF-8; name=v11-0001-split-tables-and-indexes-stats.patchDownload
From 9fd392404a41581a66d600f249b7d523fc75262b Mon Sep 17 00:00:00 2001
From: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Date: Wed, 8 Nov 2023 09:28:01 +0000
Subject: [PATCH v11] split tables and indexes stats

---
 src/backend/access/common/relation.c          |  10 +-
 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          | 112 +++---
 src/backend/storage/buffer/bufmgr.c           |  12 +-
 src/backend/utils/activity/Makefile           |   3 +-
 src/backend/utils/activity/meson.build        |   3 +-
 src/backend/utils/activity/pgstat.c           |  31 +-
 src/backend/utils/activity/pgstat_index.c     | 312 ++++++++++++++++
 .../{pgstat_relation.c => pgstat_table.c}     | 284 +++++++--------
 src/backend/utils/activity/pgstat_xact.c      |   8 +-
 src/backend/utils/adt/pgstatfuncs.c           | 223 ++++++++----
 src/backend/utils/cache/relcache.c            |  13 +-
 src/include/catalog/pg_proc.dat               | 172 +++++----
 src/include/pgstat.h                          | 171 +++++++--
 src/include/utils/pgstat_internal.h           |  31 +-
 src/include/utils/rel.h                       |   5 +-
 src/test/isolation/expected/stats.out         | 304 ++++++++--------
 src/test/isolation/expected/stats_1.out       | 266 +++++++-------
 src/test/isolation/specs/stats.spec           |  16 +-
 src/test/recovery/t/029_stats_restart.pl      |  23 +-
 .../recovery/t/030_stats_cleanup_replica.pl   |   2 +-
 src/test/regress/expected/rules.out           |  96 ++---
 src/test/regress/expected/stats.out           | 333 ++++++++++++------
 src/test/regress/sql/stats.sql                | 143 ++++++--
 src/tools/pgindent/typedefs.list              |   4 +-
 27 files changed, 1666 insertions(+), 924 deletions(-)
   6.3% src/backend/catalog/
  19.1% src/backend/utils/activity/
   6.8% src/backend/utils/adt/
   8.1% src/include/catalog/
   7.0% src/include/
  27.3% src/test/isolation/expected/
  16.4% src/test/regress/expected/
   5.8% src/test/regress/sql/

diff --git a/src/backend/access/common/relation.c b/src/backend/access/common/relation.c
index 4017e175e3..fca166a063 100644
--- a/src/backend/access/common/relation.c
+++ b/src/backend/access/common/relation.c
@@ -73,7 +73,10 @@ relation_open(Oid relationId, LOCKMODE lockmode)
 	if (RelationUsesLocalBuffers(r))
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
-	pgstat_init_relation(r);
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_init_index(r);
+	else
+		pgstat_init_table(r);
 
 	return r;
 }
@@ -123,7 +126,10 @@ try_relation_open(Oid relationId, LOCKMODE lockmode)
 	if (RelationUsesLocalBuffers(r))
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
-	pgstat_init_relation(r);
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_init_index(r);
+	else
+		pgstat_init_table(r);
 
 	return r;
 }
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index b25b03f7ab..5e4722b763 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -590,7 +590,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 d03c961678..e9f8d0fb2f 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;
 }
@@ -1840,7 +1843,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 143fae01eb..7ae2719563 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1757,7 +1757,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);
@@ -2331,7 +2331,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 b65f6b5249..ab36e1adca 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -654,30 +654,30 @@ 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_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,
-            pg_stat_get_tuples_hot_updated(C.oid) AS n_tup_hot_upd,
-            pg_stat_get_tuples_newpage_updated(C.oid) AS n_tup_newpage_upd,
-            pg_stat_get_live_tuples(C.oid) AS n_live_tup,
-            pg_stat_get_dead_tuples(C.oid) AS n_dead_tup,
-            pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze,
-            pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum,
-            pg_stat_get_last_vacuum_time(C.oid) as last_vacuum,
-            pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum,
-            pg_stat_get_last_analyze_time(C.oid) as last_analyze,
-            pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze,
-            pg_stat_get_vacuum_count(C.oid) AS vacuum_count,
-            pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count,
-            pg_stat_get_analyze_count(C.oid) AS analyze_count,
-            pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count
+            pg_stat_get_tab_numscans(C.oid) AS seq_scan,
+            pg_stat_get_tab_lastscan(C.oid) AS last_seq_scan,
+            pg_stat_get_tab_tuples_returned(C.oid) AS seq_tup_read,
+            sum(pg_stat_get_ind_numscans(I.indexrelid))::bigint AS idx_scan,
+            max(pg_stat_get_ind_lastscan(I.indexrelid)) AS last_idx_scan,
+            sum(pg_stat_get_ind_tuples_fetched(I.indexrelid))::bigint +
+            pg_stat_get_tab_tuples_fetched(C.oid) AS idx_tup_fetch,
+            pg_stat_get_tab_tuples_inserted(C.oid) AS n_tup_ins,
+            pg_stat_get_tab_tuples_updated(C.oid) AS n_tup_upd,
+            pg_stat_get_tab_tuples_deleted(C.oid) AS n_tup_del,
+            pg_stat_get_tab_tuples_hot_updated(C.oid) AS n_tup_hot_upd,
+            pg_stat_get_tab_tuples_newpage_updated(C.oid) AS n_tup_newpage_upd,
+            pg_stat_get_tab_live_tuples(C.oid) AS n_live_tup,
+            pg_stat_get_tab_dead_tuples(C.oid) AS n_dead_tup,
+            pg_stat_get_tab_mod_since_analyze(C.oid) AS n_mod_since_analyze,
+            pg_stat_get_tab_ins_since_vacuum(C.oid) AS n_ins_since_vacuum,
+            pg_stat_get_tab_last_vacuum_time(C.oid) as last_vacuum,
+            pg_stat_get_tab_last_autovacuum_time(C.oid) as last_autovacuum,
+            pg_stat_get_tab_last_analyze_time(C.oid) as last_analyze,
+            pg_stat_get_tab_last_autoanalyze_time(C.oid) as last_autoanalyze,
+            pg_stat_get_tab_vacuum_count(C.oid) AS vacuum_count,
+            pg_stat_get_tab_autovacuum_count(C.oid) AS autovacuum_count,
+            pg_stat_get_tab_analyze_count(C.oid) AS analyze_count,
+            pg_stat_get_tab_autoanalyze_count(C.oid) AS autoanalyze_count
     FROM pg_class C LEFT JOIN
          pg_index I ON C.oid = I.indrelid
          LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
@@ -689,16 +689,16 @@ 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_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_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,
-            pg_stat_get_xact_tuples_deleted(C.oid) AS n_tup_del,
-            pg_stat_get_xact_tuples_hot_updated(C.oid) AS n_tup_hot_upd,
-            pg_stat_get_xact_tuples_newpage_updated(C.oid) AS n_tup_newpage_upd
+            pg_stat_get_tab_xact_numscans(C.oid) AS seq_scan,
+            pg_stat_get_tab_xact_tuples_returned(C.oid) AS seq_tup_read,
+            sum(pg_stat_get_ind_xact_numscans(I.indexrelid))::bigint AS idx_scan,
+            sum(pg_stat_get_ind_xact_tuples_fetched(I.indexrelid))::bigint +
+            pg_stat_get_tab_xact_tuples_fetched(C.oid) AS idx_tup_fetch,
+            pg_stat_get_tab_xact_tuples_inserted(C.oid) AS n_tup_ins,
+            pg_stat_get_tab_xact_tuples_updated(C.oid) AS n_tup_upd,
+            pg_stat_get_tab_xact_tuples_deleted(C.oid) AS n_tup_del,
+            pg_stat_get_tab_xact_tuples_hot_updated(C.oid) AS n_tup_hot_upd,
+            pg_stat_get_tab_xact_tuples_newpage_updated(C.oid) AS n_tup_newpage_upd
     FROM pg_class C LEFT JOIN
          pg_index I ON C.oid = I.indrelid
          LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
@@ -730,31 +730,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_tab_blocks_fetched(C.oid) -
+                    pg_stat_get_tab_blocks_hit(C.oid) AS heap_blks_read,
+            pg_stat_get_tab_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_tab_blocks_fetched(T.oid) -
+                    pg_stat_get_tab_blocks_hit(T.oid) AS toast_blks_read,
+            pg_stat_get_tab_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_ind_blocks_fetched(indexrelid) -
+                         pg_stat_get_ind_blocks_hit(indexrelid))::bigint
                      AS idx_blks_read,
-                     sum(pg_stat_get_blocks_hit(indexrelid))::bigint
+                     sum(pg_stat_get_ind_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_ind_blocks_fetched(indexrelid) -
+                         pg_stat_get_ind_blocks_hit(indexrelid))::bigint
                      AS idx_blks_read,
-                     sum(pg_stat_get_blocks_hit(indexrelid))::bigint
+                     sum(pg_stat_get_ind_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');
@@ -776,10 +776,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_ind_numscans(I.oid) AS idx_scan,
+            pg_stat_get_ind_lastscan(I.oid) AS last_idx_scan,
+            pg_stat_get_ind_tuples_returned(I.oid) AS idx_tup_read,
+            pg_stat_get_ind_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
@@ -803,9 +803,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_ind_blocks_fetched(I.oid) -
+                    pg_stat_get_ind_blocks_hit(I.oid) AS idx_blks_read,
+            pg_stat_get_ind_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
@@ -827,9 +827,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_ind_blocks_fetched(C.oid) -
+                    pg_stat_get_tab_blocks_hit(C.oid) AS blks_read,
+            pg_stat_get_tab_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 f7c67d504c..bc2ddee0a3 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -799,11 +799,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/Makefile b/src/backend/utils/activity/Makefile
index a4397ef907..341a41f2b4 100644
--- a/src/backend/utils/activity/Makefile
+++ b/src/backend/utils/activity/Makefile
@@ -25,11 +25,12 @@ OBJS = \
 	pgstat_database.o \
 	pgstat_function.o \
 	pgstat_io.o \
-	pgstat_relation.o \
+	pgstat_index.o \
 	pgstat_replslot.o \
 	pgstat_shmem.o \
 	pgstat_slru.o \
 	pgstat_subscription.o \
+	pgstat_table.o \
 	pgstat_wal.o \
 	pgstat_xact.o \
 	wait_event.o \
diff --git a/src/backend/utils/activity/meson.build b/src/backend/utils/activity/meson.build
index 46a27e7548..9248a79941 100644
--- a/src/backend/utils/activity/meson.build
+++ b/src/backend/utils/activity/meson.build
@@ -10,11 +10,12 @@ backend_sources += files(
   'pgstat_database.c',
   'pgstat_function.c',
   'pgstat_io.c',
-  'pgstat_relation.c',
+  'pgstat_index.c',
   'pgstat_replslot.c',
   'pgstat_shmem.c',
   'pgstat_slru.c',
   'pgstat_subscription.c',
+  'pgstat_table.c',
   'pgstat_wal.c',
   'pgstat_xact.c',
 )
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index d743fc0b28..ebace8c998 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -73,10 +73,11 @@
  * - pgstat_database.c
  * - pgstat_function.c
  * - pgstat_io.c
- * - pgstat_relation.c
+ * - pgstat_index.c
  * - pgstat_replslot.c
  * - pgstat_slru.c
  * - pgstat_subscription.c
+ * - pgstat_table.c
  * - pgstat_wal.c
  *
  * Whenever possible infrastructure files should not contain code related to
@@ -277,18 +278,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_index.c b/src/backend/utils/activity/pgstat_index.c
new file mode 100644
index 0000000000..131d431a30
--- /dev/null
+++ b/src/backend/utils/activity/pgstat_index.c
@@ -0,0 +1,312 @@
+/* -------------------------------------------------------------------------
+ *
+ * pgstat_index.c
+ *	  Implementation of index statistics.
+ *
+ * This file contains the implementation of function index. It is kept
+ * separate from pgstat.c to enforce the line between the statistics access /
+ * storage implementation and the details about individual types of
+ * statistics.
+ *
+ * Copyright (c) 2001-2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/activity/pgstat_index.c
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/xact.h"
+#include "utils/pgstat_internal.h"
+#include "utils/rel.h"
+#include "catalog/catalog.h"
+
+static PgStat_IndexStatus *pgstat_prep_index_pending(Oid rel_id, bool isshared);
+
+/*
+ * Copy stats between indexes. This is used for things like REINDEX
+ * CONCURRENTLY.
+ */
+void
+pgstat_copy_index_stats(Relation dst, Relation src)
+{
+	PgStat_StatIndEntry *srcstats;
+	PgStatShared_Index *dstshstats;
+	PgStat_EntryRef *dst_ref;
+
+	srcstats = pgstat_fetch_stat_indentry_ext(src->rd_rel->relisshared,
+											  RelationGetRelid(src));
+	if (!srcstats)
+		return;
+
+	dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_INDEX,
+										  dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+										  RelationGetRelid(dst),
+										  false);
+
+	dstshstats = (PgStatShared_Index *) dst_ref->shared_stats;
+	dstshstats->stats = *srcstats;
+
+	pgstat_unlock_entry(dst_ref);
+}
+
+/*
+ * Initialize a relcache entry to count access statistics.  Called whenever an
+ * index is opened.
+ *
+ * We assume that a relcache entry's pgstatind_info field is 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
+ * pgstat_assoc_index().
+ */
+void
+pgstat_init_index(Relation rel)
+{
+	/*
+	 * We only count stats for indexes
+	 */
+	Assert(rel->rd_rel->relkind == RELKIND_INDEX);
+
+	if (!pgstat_track_counts)
+	{
+		if (rel->pgstatind_info != NULL)
+			pgstat_unlink_index(rel);
+
+		/* We're not counting at all */
+		rel->pgstat_enabled = false;
+		rel->pgstatind_info = NULL;
+		return;
+	}
+
+	rel->pgstat_enabled = true;
+}
+
+/*
+ * 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_index() 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->pgstatind_info->relation == NULL);
+
+	/* mark this relation as the owner */
+	rel->pgstatind_info->relation = rel;
+}
+
+/*
+ * Break the mutual link between a relcache entry and pending index stats entry.
+ * This must be called whenever one end of the link is removed.
+ */
+void
+pgstat_unlink_index(Relation rel)
+{
+
+	if (rel->pgstatind_info == NULL)
+		return;
+
+	/* 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 index stats are dropped if transaction aborts.
+ */
+void
+pgstat_create_index(Relation rel)
+{
+	pgstat_create_transactional(PGSTAT_KIND_INDEX,
+								rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+								RelationGetRelid(rel));
+}
+
+/*
+ * Ensure that index stats are dropped if transaction commits.
+ */
+void
+pgstat_drop_index(Relation rel)
+{
+	pgstat_drop_transactional(PGSTAT_KIND_INDEX,
+							  rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
+							  RelationGetRelid(rel));
+}
+
+/*
+ * 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)
+{
+	return pgstat_fetch_stat_indentry_ext(IsSharedRelation(relid), relid);
+}
+
+/*
+ * 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);
+}
+
+/*
+ * 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;
+	PgStat_IndexStatus *indentry = NULL;
+	PgStat_IndexStatus *indexstatus = NULL;
+
+	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 indexstatus;
+	}
+
+	indentry = (PgStat_IndexStatus *) entry_ref->pending;
+	indexstatus = palloc(sizeof(PgStat_IndexStatus));
+	*indexstatus = *indentry;
+
+	/*
+	 * Reset indexstatus->trans in the copy of PgStat_IndexStatus as it may
+	 * point to a shared memory area.  Its data is saved below, so removing it
+	 * does not matter.
+	 */
+	indexstatus->trans = NULL;
+
+	return indexstatus;
+}
+
+/*
+ * 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->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->counts.numscans;
+
+	if (lstats->counts.numscans)
+	{
+		TimestampTz t = GetCurrentTransactionStopTimestamp();
+
+		if (t > indentry->lastscan)
+			indentry->lastscan = t;
+	}
+	indentry->tuples_returned += lstats->counts.tuples_returned;
+	indentry->tuples_fetched += lstats->counts.tuples_fetched;
+	indentry->blocks_fetched += lstats->counts.blocks_fetched;
+	indentry->blocks_hit += lstats->counts.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->tuples_returned += lstats->counts.tuples_returned;
+	dbentry->tuples_fetched += lstats->counts.tuples_fetched;
+	dbentry->blocks_fetched += lstats->counts.blocks_fetched;
+	dbentry->blocks_hit += lstats->counts.blocks_hit;
+
+	return true;
+}
+
+void
+pgstat_index_delete_pending_cb(PgStat_EntryRef *entry_ref)
+{
+	PgStat_IndexStatus *pending = (PgStat_IndexStatus *) entry_ref->pending;
+
+	if (pending->relation)
+		pgstat_unlink_index(pending->relation);
+}
+
+/*
+ * 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;
+}
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_table.c
similarity index 76%
rename from src/backend/utils/activity/pgstat_relation.c
rename to src/backend/utils/activity/pgstat_table.c
index f5d726e292..96f6ff67e2 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_table.c
@@ -1,9 +1,9 @@
 /* -------------------------------------------------------------------------
  *
- * pgstat_relation.c
- *	  Implementation of relation statistics.
+ * pgstat_table.c
+ *	  Implementation of table statistics.
  *
- * This file contains the implementation of function relation. It is kept
+ * This file contains the implementation of function table. It is kept
  * separate from pgstat.c to enforce the line between the statistics access /
  * storage implementation and the details about individual types of
  * statistics.
@@ -11,7 +11,7 @@
  * Copyright (c) 2001-2023, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *	  src/backend/utils/activity/pgstat_relation.c
+ *	  src/backend/utils/activity/pgstat_table.c
  * -------------------------------------------------------------------------
  */
 
@@ -44,74 +44,48 @@ 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 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
- * CONCURRENTLY.
- */
-void
-pgstat_copy_relation_stats(Relation dst, Relation src)
-{
-	PgStat_StatTabEntry *srcstats;
-	PgStatShared_Relation *dstshstats;
-	PgStat_EntryRef *dst_ref;
-
-	srcstats = pgstat_fetch_stat_tabentry_ext(src->rd_rel->relisshared,
-											  RelationGetRelid(src));
-	if (!srcstats)
-		return;
-
-	dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
-										  dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
-										  RelationGetRelid(dst),
-										  false);
-
-	dstshstats = (PgStatShared_Relation *) dst_ref->shared_stats;
-	dstshstats->stats = *srcstats;
-
-	pgstat_unlock_entry(dst_ref);
-}
-
 /*
  * Initialize a relcache entry to count access statistics.  Called whenever a
- * relation is opened.
+ * table is opened.
  *
- * We assume that a relcache entry's pgstat_info field is zeroed by relcache.c
+ * We assume that a relcache entry's pgstattab_info field is 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
- * pgstat_assoc_relation().
+ * pgstat_assoc_table().
  */
 void
-pgstat_init_relation(Relation rel)
+pgstat_init_table(Relation rel)
 {
 	char		relkind = rel->rd_rel->relkind;
 
+	Assert(relkind != RELKIND_INDEX);
+
 	/*
 	 * We only count stats for relations with storage and partitioned tables
 	 */
 	if (!RELKIND_HAS_STORAGE(relkind) && relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		rel->pgstat_enabled = false;
-		rel->pgstat_info = NULL;
+		rel->pgstattab_info = NULL;
 		return;
 	}
 
 	if (!pgstat_track_counts)
 	{
-		if (rel->pgstat_info)
-			pgstat_unlink_relation(rel);
+		if (rel->pgstattab_info != NULL)
+			pgstat_unlink_table(rel);
 
 		/* We're not counting at all */
 		rel->pgstat_enabled = false;
-		rel->pgstat_info = NULL;
+		rel->pgstattab_info = NULL;
 		return;
 	}
 
@@ -119,89 +93,92 @@ 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.
  *
- * This is separate from pgstat_init_relation() as it is not uncommon for
+ * This is separate from pgstat_init_table() as it is not uncommon for
  * 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->pgstat_info->relation == NULL);
+	Assert(rel->pgstattab_info->relation == NULL);
 
 	/* mark this relation as the owner */
-	rel->pgstat_info->relation = rel;
+	rel->pgstattab_info->relation = rel;
 }
 
 /*
- * Break the mutual link between a relcache entry and pending stats entry.
+ * Break the mutual link between a relcache entry and pending table stats entry.
  * This must be called whenever one end of the link is removed.
  */
 void
-pgstat_unlink_relation(Relation rel)
+pgstat_unlink_table(Relation rel)
 {
-	/* remove the link to stats info if any */
-	if (rel->pgstat_info == NULL)
+
+	if (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;
+	}
 }
 
 /*
- * Ensure that stats are dropped if transaction aborts.
+ * Ensure that table stats are dropped if transaction aborts.
  */
 void
-pgstat_create_relation(Relation rel)
+pgstat_create_table(Relation rel)
 {
-	pgstat_create_transactional(PGSTAT_KIND_RELATION,
+	pgstat_create_transactional(PGSTAT_KIND_TABLE,
 								rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
 								RelationGetRelid(rel));
 }
 
 /*
- * Ensure that stats are dropped if transaction commits.
+ * Ensure that table stats are dropped if transaction commits.
  */
 void
-pgstat_drop_relation(Relation rel)
+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;
 	}
 }
 
@@ -213,7 +190,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;
@@ -225,10 +202,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->live_tuples = livetuples;
@@ -280,7 +257,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);
 
@@ -299,31 +276,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->counts.delta_dead_tuples;
+		deadtuples -= rel->pgstattab_info->counts.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->live_tuples = livetuples;
@@ -360,12 +337,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;
 	}
 }
 
@@ -377,21 +354,21 @@ pgstat_count_heap_update(Relation rel, bool hot, bool newpage)
 {
 	Assert(!(hot && newpage));
 
-	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++;
 
 		/*
 		 * tuples_hot_updated and tuples_newpage_updated counters are
 		 * nontransactional, so just advance them
 		 */
 		if (hot)
-			pgstat_info->counts.tuples_hot_updated++;
+			pgstattab_info->counts.tuples_hot_updated++;
 		else if (newpage)
-			pgstat_info->counts.tuples_newpage_updated++;
+			pgstattab_info->counts.tuples_newpage_updated++;
 	}
 }
 
@@ -401,12 +378,12 @@ pgstat_count_heap_update(Relation rel, bool hot, bool newpage)
 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++;
 	}
 }
 
@@ -416,15 +393,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;
 	}
 }
 
@@ -439,11 +416,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->counts.delta_dead_tuples -= delta;
+		pgstattab_info->counts.delta_dead_tuples -= delta;
 	}
 }
 
@@ -469,7 +446,7 @@ 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);
 }
 
 /*
@@ -492,10 +469,10 @@ find_tabstat_entry(Oid rel_id)
 	PgStat_TableStatus *tabentry = NULL;
 	PgStat_TableStatus *tablestatus = NULL;
 
-	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 tablestatus;
 	}
@@ -527,7 +504,7 @@ find_tabstat_entry(Oid rel_id)
 }
 
 /*
- * Perform relation stats specific end-of-transaction work. Helper for
+ * Perform table stats specific end-of-transaction work. Helper for
  * AtEOXact_PgStat.
  *
  * Transfer transactional insert/update counts into the base tabstat entries.
@@ -535,7 +512,7 @@ find_tabstat_entry(Oid rel_id)
  * TopTransactionContext and will go away anyway.
  */
 void
-AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit)
+AtEOXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit)
 {
 	PgStat_TableXactStatus *trans;
 
@@ -586,14 +563,14 @@ AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit)
 }
 
 /*
- * Perform relation stats specific end-of-sub-transaction work. Helper for
+ * Perform table stats specific end-of-sub-transaction work. Helper for
  * AtEOSubXact_PgStat.
  *
  * Transfer transactional insert/update counts into the next higher
  * subtransaction state.
  */
 void
-AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
+AtEOSubXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
 {
 	PgStat_TableXactStatus *trans;
 	PgStat_TableXactStatus *next_trans;
@@ -670,11 +647,11 @@ AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, in
 }
 
 /*
- * Generate 2PC records for all the pending transaction-dependent relation
+ * Generate 2PC records for all the pending transaction-dependent table
  * stats.
  */
 void
-AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
+AtPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state)
 {
 	PgStat_TableXactStatus *trans;
 
@@ -709,10 +686,10 @@ AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
  * reported to the stats system immediately, while the effects on live and
  * dead tuple counts are preserved in the 2PC state file.
  *
- * Note: AtEOXact_PgStat_Relations is not called during PREPARE.
+ * Note: AtEOXact_PgStat_Tables is not called during PREPARE.
  */
 void
-PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
+PostPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state)
 {
 	PgStat_TableXactStatus *trans;
 
@@ -735,27 +712,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->id, rec->shared);
+	/* Find or create a tabstat entry for the table */
+	pgstattab_info = pgstat_prep_table_pending(rec->id, rec->shared);
 
 	/* Same math as in AtEOXact_PgStat, commit case */
-	pgstat_info->counts.tuples_inserted += rec->tuples_inserted;
-	pgstat_info->counts.tuples_updated += rec->tuples_updated;
-	pgstat_info->counts.tuples_deleted += rec->tuples_deleted;
-	pgstat_info->counts.truncdropped = rec->truncdropped;
+	pgstattab_info->counts.tuples_inserted += rec->tuples_inserted;
+	pgstattab_info->counts.tuples_updated += rec->tuples_updated;
+	pgstattab_info->counts.tuples_deleted += rec->tuples_deleted;
+	pgstattab_info->counts.truncdropped = rec->truncdropped;
 	if (rec->truncdropped)
 	{
 		/* forget live/dead stats seen by backend thus far */
-		pgstat_info->counts.delta_live_tuples = 0;
-		pgstat_info->counts.delta_dead_tuples = 0;
+		pgstattab_info->counts.delta_live_tuples = 0;
+		pgstattab_info->counts.delta_dead_tuples = 0;
 	}
-	pgstat_info->counts.delta_live_tuples +=
+	pgstattab_info->counts.delta_live_tuples +=
 		rec->tuples_inserted - rec->tuples_deleted;
-	pgstat_info->counts.delta_dead_tuples +=
+	pgstattab_info->counts.delta_dead_tuples +=
 		rec->tuples_updated + rec->tuples_deleted;
-	pgstat_info->counts.changed_tuples +=
+	pgstattab_info->counts.changed_tuples +=
 		rec->tuples_inserted + rec->tuples_updated +
 		rec->tuples_deleted;
 }
@@ -771,10 +748,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->id, rec->shared);
+	/* Find or create a tabstat entry for the table */
+	pgstattab_info = pgstat_prep_table_pending(rec->id, rec->shared);
 
 	/* Same math as in AtEOXact_PgStat, abort case */
 	if (rec->truncdropped)
@@ -783,10 +760,10 @@ pgstat_twophase_postabort(TransactionId xid, uint16 info,
 		rec->tuples_updated = rec->updated_pre_truncdrop;
 		rec->tuples_deleted = rec->deleted_pre_truncdrop;
 	}
-	pgstat_info->counts.tuples_inserted += rec->tuples_inserted;
-	pgstat_info->counts.tuples_updated += rec->tuples_updated;
-	pgstat_info->counts.tuples_deleted += rec->tuples_deleted;
-	pgstat_info->counts.delta_dead_tuples +=
+	pgstattab_info->counts.tuples_inserted += rec->tuples_inserted;
+	pgstattab_info->counts.tuples_updated += rec->tuples_updated;
+	pgstattab_info->counts.tuples_deleted += rec->tuples_deleted;
+	pgstattab_info->counts.delta_dead_tuples +=
 		rec->tuples_inserted + rec->tuples_updated;
 }
 
@@ -800,22 +777,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->counts, &all_zeroes,
 			   sizeof(PgStat_TableCounts)) == 0)
@@ -883,12 +859,12 @@ pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
 }
 
 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;
 
 	if (pending->relation)
-		pgstat_unlink_relation(pending->relation);
+		pgstat_unlink_table(pending->relation);
 }
 
 /*
@@ -896,12 +872,12 @@ pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref)
  * 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;
@@ -915,7 +891,7 @@ pgstat_prep_relation_pending(Oid rel_id, bool isshared)
  * 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;
@@ -931,24 +907,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/activity/pgstat_xact.c b/src/backend/utils/activity/pgstat_xact.c
index 369239d501..cc87f3d015 100644
--- a/src/backend/utils/activity/pgstat_xact.c
+++ b/src/backend/utils/activity/pgstat_xact.c
@@ -51,7 +51,7 @@ AtEOXact_PgStat(bool isCommit, bool parallel)
 		Assert(xact_state->nest_level == 1);
 		Assert(xact_state->prev == NULL);
 
-		AtEOXact_PgStat_Relations(xact_state, isCommit);
+		AtEOXact_PgStat_Tables(xact_state, isCommit);
 		AtEOXact_PgStat_DroppedStats(xact_state, isCommit);
 	}
 	pgStatXactStack = NULL;
@@ -122,7 +122,7 @@ AtEOSubXact_PgStat(bool isCommit, int nestDepth)
 		/* delink xact_state from stack immediately to simplify reuse case */
 		pgStatXactStack = xact_state->prev;
 
-		AtEOSubXact_PgStat_Relations(xact_state, isCommit, nestDepth);
+		AtEOSubXact_PgStat_Tables(xact_state, isCommit, nestDepth);
 		AtEOSubXact_PgStat_DroppedStats(xact_state, isCommit, nestDepth);
 
 		pfree(xact_state);
@@ -197,7 +197,7 @@ AtPrepare_PgStat(void)
 		Assert(xact_state->nest_level == 1);
 		Assert(xact_state->prev == NULL);
 
-		AtPrepare_PgStat_Relations(xact_state);
+		AtPrepare_PgStat_Tables(xact_state);
 	}
 }
 
@@ -221,7 +221,7 @@ PostPrepare_PgStat(void)
 		Assert(xact_state->nest_level == 1);
 		Assert(xact_state->prev == NULL);
 
-		PostPrepare_PgStat_Relations(xact_state);
+		PostPrepare_PgStat_Tables(xact_state);
 	}
 	pgStatXactStack = NULL;
 
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 3a9f9bc4fe..62a49dd686 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -38,9 +38,9 @@
 
 #define HAS_PGSTAT_PERMISSIONS(role)	 (has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS) || has_privs_of_role(GetUserId(), role))
 
-#define PG_STAT_GET_RELENTRY_INT64(stat)						\
+#define PG_STAT_GET_TABENTRY_INT64(stat)						\
 Datum															\
-CppConcat(pg_stat_get_,stat)(PG_FUNCTION_ARGS)					\
+CppConcat(pg_stat_get_tab_,stat)(PG_FUNCTION_ARGS)				\
 {																\
 	Oid			relid = PG_GETARG_OID(0);						\
 	int64		result;											\
@@ -54,63 +54,95 @@ CppConcat(pg_stat_get_,stat)(PG_FUNCTION_ARGS)					\
 	PG_RETURN_INT64(result);									\
 }
 
-/* pg_stat_get_analyze_count */
-PG_STAT_GET_RELENTRY_INT64(analyze_count)
+/* pg_stat_get_tab_analyze_count */
+PG_STAT_GET_TABENTRY_INT64(analyze_count)
 
-/* pg_stat_get_autoanalyze_count */
-PG_STAT_GET_RELENTRY_INT64(autoanalyze_count)
+/* pg_stat_get_tab_autoanalyze_count */
+PG_STAT_GET_TABENTRY_INT64(autoanalyze_count)
 
-/* pg_stat_get_autovacuum_count */
-PG_STAT_GET_RELENTRY_INT64(autovacuum_count)
+/* pg_stat_get_tab_autovacuum_count */
+PG_STAT_GET_TABENTRY_INT64(autovacuum_count)
 
-/* pg_stat_get_blocks_fetched */
-PG_STAT_GET_RELENTRY_INT64(blocks_fetched)
+/* pg_stat_get_tab_blocks_fetched */
+PG_STAT_GET_TABENTRY_INT64(blocks_fetched)
 
-/* pg_stat_get_blocks_hit */
-PG_STAT_GET_RELENTRY_INT64(blocks_hit)
+/* pg_stat_get_tab_blocks_hit */
+PG_STAT_GET_TABENTRY_INT64(blocks_hit)
 
-/* pg_stat_get_dead_tuples */
-PG_STAT_GET_RELENTRY_INT64(dead_tuples)
+/* pg_stat_get_tab_dead_tuples */
+PG_STAT_GET_TABENTRY_INT64(dead_tuples)
 
-/* pg_stat_get_ins_since_vacuum */
-PG_STAT_GET_RELENTRY_INT64(ins_since_vacuum)
+/* pg_stat_get_tab_ins_since_vacuum */
+PG_STAT_GET_TABENTRY_INT64(ins_since_vacuum)
 
-/* pg_stat_get_live_tuples */
-PG_STAT_GET_RELENTRY_INT64(live_tuples)
+/* pg_stat_get_tab_live_tuples */
+PG_STAT_GET_TABENTRY_INT64(live_tuples)
 
-/* pg_stat_get_mod_since_analyze */
-PG_STAT_GET_RELENTRY_INT64(mod_since_analyze)
+/* pg_stat_get_tab_mod_since_analyze */
+PG_STAT_GET_TABENTRY_INT64(mod_since_analyze)
 
-/* pg_stat_get_numscans */
-PG_STAT_GET_RELENTRY_INT64(numscans)
+/* pg_stat_get_tab_numscans */
+PG_STAT_GET_TABENTRY_INT64(numscans)
 
-/* pg_stat_get_tuples_deleted */
-PG_STAT_GET_RELENTRY_INT64(tuples_deleted)
+/* pg_stat_get_tab_tuples_deleted */
+PG_STAT_GET_TABENTRY_INT64(tuples_deleted)
 
-/* pg_stat_get_tuples_fetched */
-PG_STAT_GET_RELENTRY_INT64(tuples_fetched)
+/* pg_stat_get_tab_tuples_fetched */
+PG_STAT_GET_TABENTRY_INT64(tuples_fetched)
 
-/* pg_stat_get_tuples_hot_updated */
-PG_STAT_GET_RELENTRY_INT64(tuples_hot_updated)
+/* pg_stat_get_tab_tuples_hot_updated */
+PG_STAT_GET_TABENTRY_INT64(tuples_hot_updated)
 
-/* pg_stat_get_tuples_newpage_updated */
-PG_STAT_GET_RELENTRY_INT64(tuples_newpage_updated)
+/* pg_stat_get_tab_tuples_newpage_updated */
+PG_STAT_GET_TABENTRY_INT64(tuples_newpage_updated)
 
-/* pg_stat_get_tuples_inserted */
-PG_STAT_GET_RELENTRY_INT64(tuples_inserted)
+/* pg_stat_get_tab_tuples_inserted */
+PG_STAT_GET_TABENTRY_INT64(tuples_inserted)
 
-/* pg_stat_get_tuples_returned */
-PG_STAT_GET_RELENTRY_INT64(tuples_returned)
+/* pg_stat_get_tab_tuples_returned */
+PG_STAT_GET_TABENTRY_INT64(tuples_returned)
 
-/* pg_stat_get_tuples_updated */
-PG_STAT_GET_RELENTRY_INT64(tuples_updated)
+/* pg_stat_get_tab_tuples_updated */
+PG_STAT_GET_TABENTRY_INT64(tuples_updated)
 
-/* pg_stat_get_vacuum_count */
-PG_STAT_GET_RELENTRY_INT64(vacuum_count)
+/* pg_stat_get_tab_vacuum_count */
+PG_STAT_GET_TABENTRY_INT64(vacuum_count)
 
-#define PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat)					\
+
+#define PG_STAT_GET_INDENTRY_INT64(stat)						\
 Datum															\
-CppConcat(pg_stat_get_,stat)(PG_FUNCTION_ARGS)					\
+CppConcat(pg_stat_get_ind_,stat)(PG_FUNCTION_ARGS)				\
+{																\
+	Oid			relid = PG_GETARG_OID(0);						\
+	int64		result;											\
+	PgStat_StatIndEntry *indentry;								\
+																\
+	if ((indentry = pgstat_fetch_stat_indentry(relid)) == NULL)	\
+		result = 0;												\
+	else														\
+		result = (int64) (indentry->stat);						\
+																\
+	PG_RETURN_INT64(result);									\
+}
+
+/* pg_stat_get_ind_numscans */
+PG_STAT_GET_INDENTRY_INT64(numscans)
+
+/* pg_stat_get_ind_tuples_returned */
+PG_STAT_GET_INDENTRY_INT64(tuples_returned)
+
+/* pg_stat_get_ind_tuples_fetched */
+PG_STAT_GET_INDENTRY_INT64(tuples_fetched)
+
+/* pg_stat_get_ind_blocks_fetched */
+PG_STAT_GET_INDENTRY_INT64(blocks_fetched)
+
+/* pg_stat_get_ind_blocks_hit */
+PG_STAT_GET_INDENTRY_INT64(blocks_hit)
+
+#define PG_STAT_GET_TABENTRY_TIMESTAMPTZ(stat)					\
+Datum															\
+CppConcat(pg_stat_get_tab_,stat)(PG_FUNCTION_ARGS)				\
 {																\
 	Oid			relid = PG_GETARG_OID(0);						\
 	TimestampTz result;											\
@@ -127,20 +159,42 @@ CppConcat(pg_stat_get_,stat)(PG_FUNCTION_ARGS)					\
 		PG_RETURN_TIMESTAMPTZ(result);							\
 }
 
-/* pg_stat_get_last_analyze_time */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_analyze_time)
+/* pg_stat_get_tab_last_analyze_time */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(last_analyze_time)
 
-/* pg_stat_get_last_autoanalyze_time */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_autoanalyze_time)
+/* pg_stat_get_tab_last_autoanalyze_time */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(last_autoanalyze_time)
 
-/* pg_stat_get_last_autovacuum_time */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_autovacuum_time)
+/* pg_stat_get_tab_last_autovacuum_time */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(last_autovacuum_time)
 
-/* pg_stat_get_last_vacuum_time */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time)
+/* pg_stat_get_tab_last_vacuum_time */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(last_vacuum_time)
 
-/* pg_stat_get_lastscan */
-PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan)
+/* pg_stat_get_tab_lastscan */
+PG_STAT_GET_TABENTRY_TIMESTAMPTZ(lastscan)
+
+#define PG_STAT_GET_INDENTRY_TIMESTAMPTZ(stat)					\
+Datum															\
+CppConcat(pg_stat_get_ind_,stat)(PG_FUNCTION_ARGS)				\
+{																\
+	Oid			relid = PG_GETARG_OID(0);						\
+	TimestampTz result;											\
+	PgStat_StatIndEntry *indentry;								\
+																\
+	if ((indentry = pgstat_fetch_stat_indentry(relid)) == NULL)	\
+		result = 0;												\
+	else														\
+		result = indentry->stat;								\
+																\
+	if (result == 0)											\
+		PG_RETURN_NULL();										\
+	else														\
+		PG_RETURN_TIMESTAMPTZ(result);							\
+}
+
+/* pg_stat_get_ind_lastscan */
+PG_STAT_GET_INDENTRY_TIMESTAMPTZ(lastscan)
 
 Datum
 pg_stat_get_function_calls(PG_FUNCTION_ARGS)
@@ -1557,9 +1611,9 @@ pg_stat_get_slru(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
-#define PG_STAT_GET_XACT_RELENTRY_INT64(stat)			\
+#define PG_STAT_GET_XACT_TABENTRY_INT64(stat)			\
 Datum													\
-CppConcat(pg_stat_get_xact_,stat)(PG_FUNCTION_ARGS)		\
+CppConcat(pg_stat_get_tab_xact_,stat)(PG_FUNCTION_ARGS)	\
 {														\
 	Oid         relid = PG_GETARG_OID(0);				\
 	int64       result;									\
@@ -1573,35 +1627,57 @@ CppConcat(pg_stat_get_xact_,stat)(PG_FUNCTION_ARGS)		\
 	PG_RETURN_INT64(result);							\
 }
 
-/* pg_stat_get_xact_numscans */
-PG_STAT_GET_XACT_RELENTRY_INT64(numscans)
+/* pg_stat_get_tab_xact_numscans */
+PG_STAT_GET_XACT_TABENTRY_INT64(numscans)
+
+/* pg_stat_get_tab_xact_tuples_returned */
+PG_STAT_GET_XACT_TABENTRY_INT64(tuples_returned)
+
+/* pg_stat_get_tab_xact_tuples_fetched */
+PG_STAT_GET_XACT_TABENTRY_INT64(tuples_fetched)
 
-/* pg_stat_get_xact_tuples_returned */
-PG_STAT_GET_XACT_RELENTRY_INT64(tuples_returned)
+/* pg_stat_get_tab_xact_tuples_hot_updated */
+PG_STAT_GET_XACT_TABENTRY_INT64(tuples_hot_updated)
 
-/* pg_stat_get_xact_tuples_fetched */
-PG_STAT_GET_XACT_RELENTRY_INT64(tuples_fetched)
+/* pg_stat_get_tab_xact_tuples_newpage_updated */
+PG_STAT_GET_XACT_TABENTRY_INT64(tuples_newpage_updated)
 
-/* pg_stat_get_xact_tuples_hot_updated */
-PG_STAT_GET_XACT_RELENTRY_INT64(tuples_hot_updated)
+/* pg_stat_get_tab_xact_blocks_fetched */
+PG_STAT_GET_XACT_TABENTRY_INT64(blocks_fetched)
 
-/* pg_stat_get_xact_tuples_newpage_updated */
-PG_STAT_GET_XACT_RELENTRY_INT64(tuples_newpage_updated)
+/* pg_stat_get_tab_xact_blocks_hit */
+PG_STAT_GET_XACT_TABENTRY_INT64(blocks_hit)
 
-/* pg_stat_get_xact_blocks_fetched */
-PG_STAT_GET_XACT_RELENTRY_INT64(blocks_fetched)
+/* pg_stat_get_tab_xact_tuples_inserted */
+PG_STAT_GET_XACT_TABENTRY_INT64(tuples_inserted)
 
-/* pg_stat_get_xact_blocks_hit */
-PG_STAT_GET_XACT_RELENTRY_INT64(blocks_hit)
+/* pg_stat_get_tab_xact_tuples_updated */
+PG_STAT_GET_XACT_TABENTRY_INT64(tuples_updated)
 
-/* pg_stat_get_xact_tuples_inserted */
-PG_STAT_GET_XACT_RELENTRY_INT64(tuples_inserted)
+/* pg_stat_get_tab_xact_tuples_deleted */
+PG_STAT_GET_XACT_TABENTRY_INT64(tuples_deleted)
+
+#define PG_STAT_GET_XACT_INDENTRY_INT64(stat)			\
+Datum													\
+CppConcat(pg_stat_get_ind_xact_,stat)(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->counts.stat);		\
+														\
+	PG_RETURN_INT64(result);							\
+}
 
-/* pg_stat_get_xact_tuples_updated */
-PG_STAT_GET_XACT_RELENTRY_INT64(tuples_updated)
+/* pg_stat_get_ind_xact_numscans */
+PG_STAT_GET_XACT_INDENTRY_INT64(numscans)
 
-/* pg_stat_get_xact_tuples_deleted */
-PG_STAT_GET_XACT_RELENTRY_INT64(tuples_deleted)
+/* pg_stat_get_ind_xact_tuples_fetched */
+PG_STAT_GET_XACT_INDENTRY_INT64(tuples_fetched)
 
 Datum
 pg_stat_get_xact_function_calls(PG_FUNCTION_ARGS)
@@ -1733,7 +1809,8 @@ pg_stat_reset_single_table_counters(PG_FUNCTION_ARGS)
 	Oid			taboid = PG_GETARG_OID(0);
 	Oid			dboid = (IsSharedRelation(taboid) ? InvalidOid : MyDatabaseId);
 
-	pgstat_reset(PGSTAT_KIND_RELATION, dboid, taboid);
+	pgstat_reset(PGSTAT_KIND_TABLE, dboid, taboid);
+	pgstat_reset(PGSTAT_KIND_INDEX, dboid, taboid);
 
 	PG_RETURN_VOID();
 }
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index b3faccbefe..e35fb1fe3c 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -2445,7 +2445,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 	RelationCloseSmgr(relation);
 
 	/* break mutual link with stats entry */
-	pgstat_unlink_relation(relation);
+	if (relation->rd_rel->relkind == RELKIND_INDEX)
+		pgstat_unlink_index(relation);
+	else
+		pgstat_unlink_table(relation);
 
 	/*
 	 * Free all the subsidiary data structures of the relcache entry, then the
@@ -2762,8 +2765,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)
@@ -6384,7 +6388,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 bd0b8873d3..03b2f67e44 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5324,101 +5324,125 @@
   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_tab_numscans', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_numscans' },
-{ oid => '6310', descr => 'statistics: time of the last scan for table/index',
-  proname => 'pg_stat_get_lastscan', provolatile => 's', proparallel => 'r',
+  prosrc => 'pg_stat_get_tab_numscans' },
+{ oid => '8808', descr => 'statistics: number of scans done for index',
+  proname => 'pg_stat_get_ind_numscans', provolatile => 's', proparallel => 'r',
+  prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_ind_numscans' },
+{ oid => '6310', descr => 'statistics: time of the last scan for table',
+  proname => 'pg_stat_get_tab_lastscan', provolatile => 's', proparallel => 'r',
+  prorettype => 'timestamptz', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tab_lastscan' },
+{ oid => '9623', descr => 'statistics: time of the last scan for index',
+  proname => 'pg_stat_get_ind_lastscan', provolatile => 's', proparallel => 'r',
   prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_lastscan' },
+  prosrc => 'pg_stat_get_ind_lastscan' },
 { oid => '1929', descr => 'statistics: number of tuples read by seqscan',
-  proname => 'pg_stat_get_tuples_returned', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_returned', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tab_tuples_returned' },
+{ oid => '9362', descr => 'statistics: number of tuples read by index scan',
+  proname => 'pg_stat_get_ind_tuples_returned', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_returned' },
+  prosrc => 'pg_stat_get_ind_tuples_returned' },
 { oid => '1930', descr => 'statistics: number of tuples fetched by idxscan',
-  proname => 'pg_stat_get_tuples_fetched', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_fetched', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_fetched' },
+  prosrc => 'pg_stat_get_tab_tuples_fetched' },
+{ oid => '9998', descr => 'statistics: number of tuples fetched by idxscan',
+  proname => 'pg_stat_get_ind_tuples_fetched', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_ind_tuples_fetched' },
 { oid => '1931', descr => 'statistics: number of tuples inserted',
-  proname => 'pg_stat_get_tuples_inserted', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_inserted', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_inserted' },
+  prosrc => 'pg_stat_get_tab_tuples_inserted' },
 { oid => '1932', descr => 'statistics: number of tuples updated',
-  proname => 'pg_stat_get_tuples_updated', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_updated', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_updated' },
+  prosrc => 'pg_stat_get_tab_tuples_updated' },
 { oid => '1933', descr => 'statistics: number of tuples deleted',
-  proname => 'pg_stat_get_tuples_deleted', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_deleted', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_deleted' },
+  prosrc => 'pg_stat_get_tab_tuples_deleted' },
 { oid => '1972', descr => 'statistics: number of tuples hot updated',
-  proname => 'pg_stat_get_tuples_hot_updated', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_hot_updated', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_hot_updated' },
+  prosrc => 'pg_stat_get_tab_tuples_hot_updated' },
 { oid => '6217',
   descr => 'statistics: number of tuples updated onto a new page',
-  proname => 'pg_stat_get_tuples_newpage_updated', provolatile => 's',
+  proname => 'pg_stat_get_tab_tuples_newpage_updated', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_tuples_newpage_updated' },
+  prosrc => 'pg_stat_get_tab_tuples_newpage_updated' },
 { oid => '2878', descr => 'statistics: number of live tuples',
-  proname => 'pg_stat_get_live_tuples', provolatile => 's', proparallel => 'r',
+  proname => 'pg_stat_get_tab_live_tuples', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_live_tuples' },
+  prosrc => 'pg_stat_get_tab_live_tuples' },
 { oid => '2879', descr => 'statistics: number of dead tuples',
-  proname => 'pg_stat_get_dead_tuples', provolatile => 's', proparallel => 'r',
+  proname => 'pg_stat_get_tab_dead_tuples', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_dead_tuples' },
+  prosrc => 'pg_stat_get_tab_dead_tuples' },
 { oid => '3177',
   descr => 'statistics: number of tuples changed since last analyze',
-  proname => 'pg_stat_get_mod_since_analyze', provolatile => 's',
+  proname => 'pg_stat_get_tab_mod_since_analyze', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_mod_since_analyze' },
+  prosrc => 'pg_stat_get_tab_mod_since_analyze' },
 { oid => '5053',
   descr => 'statistics: number of tuples inserted since last vacuum',
-  proname => 'pg_stat_get_ins_since_vacuum', provolatile => 's',
+  proname => 'pg_stat_get_tab_ins_since_vacuum', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_ins_since_vacuum' },
+  prosrc => 'pg_stat_get_tab_ins_since_vacuum' },
 { oid => '1934', descr => 'statistics: number of blocks fetched',
-  proname => 'pg_stat_get_blocks_fetched', provolatile => 's',
+  proname => 'pg_stat_get_tab_blocks_fetched', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tab_blocks_fetched' },
+{ oid => '8120', descr => 'statistics: number of blocks fetched',
+  proname => 'pg_stat_get_ind_blocks_fetched', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_blocks_fetched' },
+  prosrc => 'pg_stat_get_ind_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_tab_blocks_hit', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_blocks_hit' },
+  prosrc => 'pg_stat_get_tab_blocks_hit' },
+{ oid => '9982', descr => 'statistics: number of blocks found in cache',
+  proname => 'pg_stat_get_ind_blocks_hit', provolatile => 's', proparallel => 'r',
+  prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_ind_blocks_hit' },
 { oid => '2781', descr => 'statistics: last manual vacuum time for a table',
-  proname => 'pg_stat_get_last_vacuum_time', provolatile => 's',
+  proname => 'pg_stat_get_tab_last_vacuum_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_last_vacuum_time' },
+  prosrc => 'pg_stat_get_tab_last_vacuum_time' },
 { oid => '2782', descr => 'statistics: last auto vacuum time for a table',
-  proname => 'pg_stat_get_last_autovacuum_time', provolatile => 's',
+  proname => 'pg_stat_get_tab_last_autovacuum_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_last_autovacuum_time' },
+  prosrc => 'pg_stat_get_tab_last_autovacuum_time' },
 { oid => '2783', descr => 'statistics: last manual analyze time for a table',
-  proname => 'pg_stat_get_last_analyze_time', provolatile => 's',
+  proname => 'pg_stat_get_tab_last_analyze_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_last_analyze_time' },
+  prosrc => 'pg_stat_get_tab_last_analyze_time' },
 { oid => '2784', descr => 'statistics: last auto analyze time for a table',
-  proname => 'pg_stat_get_last_autoanalyze_time', provolatile => 's',
+  proname => 'pg_stat_get_tab_last_autoanalyze_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_last_autoanalyze_time' },
+  prosrc => 'pg_stat_get_tab_last_autoanalyze_time' },
 { oid => '3054', descr => 'statistics: number of manual vacuums for a table',
-  proname => 'pg_stat_get_vacuum_count', provolatile => 's', proparallel => 'r',
+  proname => 'pg_stat_get_tab_vacuum_count', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_vacuum_count' },
+  prosrc => 'pg_stat_get_tab_vacuum_count' },
 { oid => '3055', descr => 'statistics: number of auto vacuums for a table',
-  proname => 'pg_stat_get_autovacuum_count', provolatile => 's',
+  proname => 'pg_stat_get_tab_autovacuum_count', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_autovacuum_count' },
+  prosrc => 'pg_stat_get_tab_autovacuum_count' },
 { oid => '3056', descr => 'statistics: number of manual analyzes for a table',
-  proname => 'pg_stat_get_analyze_count', provolatile => 's',
+  proname => 'pg_stat_get_tab_analyze_count', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_analyze_count' },
+  prosrc => 'pg_stat_get_tab_analyze_count' },
 { oid => '3057', descr => 'statistics: number of auto analyzes for a table',
-  proname => 'pg_stat_get_autoanalyze_count', provolatile => 's',
+  proname => 'pg_stat_get_tab_autoanalyze_count', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_autoanalyze_count' },
+  prosrc => 'pg_stat_get_tab_autoanalyze_count' },
 { oid => '1936', descr => 'statistics: currently active backend IDs',
   proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't',
   provolatile => 's', proparallel => 'r', prorettype => 'int4',
@@ -5795,55 +5819,65 @@
   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_tab_xact_numscans', provolatile => 'v',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tab_xact_numscans' },
+{ oid => '8892',
+  descr => 'statistics: number of scans done for index in current transaction',
+  proname => 'pg_stat_get_ind_xact_numscans', provolatile => 'v',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_xact_numscans' },
+  prosrc => 'pg_stat_get_ind_xact_numscans' },
 { oid => '3038',
   descr => 'statistics: number of tuples read by seqscan in current transaction',
-  proname => 'pg_stat_get_xact_tuples_returned', provolatile => 'v',
+  proname => 'pg_stat_get_tab_xact_tuples_returned', provolatile => 'v',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_xact_tuples_returned' },
+  prosrc => 'pg_stat_get_tab_xact_tuples_returned' },
 { oid => '3039',
   descr => 'statistics: number of tuples fetched by idxscan in current transaction',
-  proname => 'pg_stat_get_xact_tuples_fetched', provolatile => 'v',
+  proname => 'pg_stat_get_tab_xact_tuples_fetched', provolatile => 'v',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_tab_xact_tuples_fetched' },
+{ oid => '9700',
+  descr => 'statistics: number of tuples fetched by idxscan in current transaction',
+  proname => 'pg_stat_get_ind_xact_tuples_fetched', provolatile => 'v',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_xact_tuples_fetched' },
+  prosrc => 'pg_stat_get_ind_xact_tuples_fetched' },
 { oid => '3040',
   descr => 'statistics: number of tuples inserted in current transaction',
-  proname => 'pg_stat_get_xact_tuples_inserted', provolatile => 'v',
+  proname => 'pg_stat_get_tab_xact_tuples_inserted', provolatile => 'v',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_xact_tuples_inserted' },
+  prosrc => 'pg_stat_get_tab_xact_tuples_inserted' },
 { oid => '3041',
   descr => 'statistics: number of tuples updated in current transaction',
-  proname => 'pg_stat_get_xact_tuples_updated', provolatile => 'v',
+  proname => 'pg_stat_get_tab_xact_tuples_updated', provolatile => 'v',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_xact_tuples_updated' },
+  prosrc => 'pg_stat_get_tab_xact_tuples_updated' },
 { oid => '3042',
   descr => 'statistics: number of tuples deleted in current transaction',
-  proname => 'pg_stat_get_xact_tuples_deleted', provolatile => 'v',
+  proname => 'pg_stat_get_tab_xact_tuples_deleted', provolatile => 'v',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_xact_tuples_deleted' },
+  prosrc => 'pg_stat_get_tab_xact_tuples_deleted' },
 { oid => '3043',
   descr => 'statistics: number of tuples hot updated in current transaction',
-  proname => 'pg_stat_get_xact_tuples_hot_updated', provolatile => 'v',
+  proname => 'pg_stat_get_tab_xact_tuples_hot_updated', provolatile => 'v',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_xact_tuples_hot_updated' },
+  prosrc => 'pg_stat_get_tab_xact_tuples_hot_updated' },
 { oid => '6218',
   descr => 'statistics: number of tuples updated onto a new page in current transaction',
-  proname => 'pg_stat_get_xact_tuples_newpage_updated', provolatile => 'v',
+  proname => 'pg_stat_get_tab_xact_tuples_newpage_updated', provolatile => 'v',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_xact_tuples_newpage_updated' },
+  prosrc => 'pg_stat_get_tab_xact_tuples_newpage_updated' },
 { oid => '3044',
   descr => 'statistics: number of blocks fetched in current transaction',
-  proname => 'pg_stat_get_xact_blocks_fetched', provolatile => 'v',
+  proname => 'pg_stat_get_tab_xact_blocks_fetched', provolatile => 'v',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_xact_blocks_fetched' },
+  prosrc => 'pg_stat_get_tab_xact_blocks_fetched' },
 { oid => '3045',
   descr => 'statistics: number of blocks found in cache in current transaction',
-  proname => 'pg_stat_get_xact_blocks_hit', provolatile => 'v',
+  proname => 'pg_stat_get_tab_xact_blocks_hit', provolatile => 'v',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
-  prosrc => 'pg_stat_get_xact_blocks_hit' },
+  prosrc => 'pg_stat_get_tab_xact_blocks_hit' },
 { oid => '3046',
   descr => 'statistics: number of function calls in current transaction',
   proname => 'pg_stat_get_xact_function_calls', provolatile => 'v',
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index f95d8db0c4..0b701ee9aa 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 */
@@ -137,6 +138,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 numscans;
+
+	PgStat_Counter tuples_returned;
+	PgStat_Counter tuples_fetched;
+	PgStat_Counter blocks_fetched;
+	PgStat_Counter blocks_hit;
+} PgStat_IndexCounts;
+
 /* ----------
  * PgStat_TableCounts			The actual per-table counts kept by a backend
  *
@@ -144,12 +167,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/newpage_updated count attempted
  * actions, regardless of whether the transaction committed.  delta_live_tuples,
@@ -203,6 +223,23 @@ 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? */
+	struct PgStat_IndexXactStatus *trans;	/* lowest subxact's counts */
+	PgStat_IndexCounts counts;	/* event counts to be sent */
+	Relation	relation;		/* rel that is using this entry */
+} PgStat_IndexStatus;
+
 /* ----------
  * PgStat_TableXactStatus		Per-table, per-subtransaction status
  * ----------
@@ -227,6 +264,29 @@ typedef struct PgStat_TableXactStatus
 } PgStat_TableXactStatus;
 
 
+/* ----------
+ * PgStat_IndexXactStatus		Per-index, per-subtransaction status
+ * ----------
+ */
+typedef struct PgStat_IndexXactStatus
+{
+	PgStat_Counter tuples_inserted; /* tuples inserted in (sub)xact */
+	PgStat_Counter tuples_updated;	/* tuples updated in (sub)xact */
+	PgStat_Counter tuples_deleted;	/* tuples deleted in (sub)xact */
+	bool		truncdropped;	/* relation truncated/dropped in this
+								 * (sub)xact */
+	/* tuples i/u/d prior to truncate/drop */
+	PgStat_Counter inserted_pre_truncdrop;
+	PgStat_Counter updated_pre_truncdrop;
+	PgStat_Counter deleted_pre_truncdrop;
+	int			nest_level;		/* subtransaction nest level */
+	/* links to other structs for same relation: */
+	struct PgStat_IndexXactStatus *upper;	/* next higher subxact if any */
+	PgStat_IndexStatus *parent; /* per-table status */
+	/* structs of same subxact level are linked here: */
+	struct PgStat_IndexXactStatus *next;	/* next of same subxact */
+} PgStat_IndexXactStatus;
+
 /* ------------------------------------------------------------
  * Data structures on disk and in shared memory follow
  *
@@ -425,6 +485,17 @@ typedef struct PgStat_StatTabEntry
 	PgStat_Counter autoanalyze_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;
@@ -576,16 +647,15 @@ extern PgStat_FunctionCounts *find_funcstat_entry(Oid func_id);
 
 
 /*
- * Functions in pgstat_relation.c
+ * Functions in pgstat_table.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_drop_table(Relation rel);
+extern void pgstat_init_table(Relation rel);
 
-extern void pgstat_init_relation(Relation rel);
-extern void pgstat_assoc_relation(Relation rel);
-extern void pgstat_unlink_relation(Relation rel);
+extern void pgstat_assoc_table(Relation rel);
+extern void pgstat_unlink_table(Relation rel);
 
 extern void pgstat_report_vacuum(Oid tableoid, bool shared,
 								 PgStat_Counter livetuples, PgStat_Counter deadtuples);
@@ -593,51 +663,82 @@ extern void pgstat_report_analyze(Relation rel,
 								  PgStat_Counter livetuples, PgStat_Counter deadtuples,
 								  bool resetcounter);
 
+/*
+ * Functions in pgstat_index.c
+ */
+
+extern void pgstat_create_index(Relation rel);
+extern void pgstat_drop_index(Relation rel);
+extern void pgstat_copy_index_stats(Relation dst, Relation src);
+extern void pgstat_init_index(Relation rel);
+extern void pgstat_assoc_index(Relation rel);
+extern void pgstat_unlink_index(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_table()
+ * and pgstat_init_index().
  */
-#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->counts.numscans++;					\
+		if (pgstat_should_count_table(rel))							\
+			(rel)->pgstattab_info->counts.numscans++;				\
 	} while (0)
 #define pgstat_count_heap_getnext(rel)								\
 	do {															\
-		if (pgstat_should_count_relation(rel))						\
-			(rel)->pgstat_info->counts.tuples_returned++;			\
+		if (pgstat_should_count_table(rel))							\
+			(rel)->pgstattab_info->counts.tuples_returned++;		\
 	} while (0)
 #define pgstat_count_heap_fetch(rel)								\
 	do {															\
-		if (pgstat_should_count_relation(rel))						\
-			(rel)->pgstat_info->counts.tuples_fetched++;			\
+		if (pgstat_should_count_table(rel))							\
+			(rel)->pgstattab_info->counts.tuples_fetched++;			\
+	} while (0)
+#define pgstat_count_index_fetch(rel)								\
+	do {															\
+		if (pgstat_should_count_index(rel))							\
+			(rel)->pgstatind_info->counts.tuples_fetched++;			\
 	} while (0)
 #define pgstat_count_index_scan(rel)								\
 	do {															\
-		if (pgstat_should_count_relation(rel))						\
-			(rel)->pgstat_info->counts.numscans++;					\
+		if (pgstat_should_count_index(rel))							\
+			(rel)->pgstatind_info->counts.numscans++;				\
 	} while (0)
 #define pgstat_count_index_tuples(rel, n)							\
 	do {															\
-		if (pgstat_should_count_relation(rel))						\
-			(rel)->pgstat_info->counts.tuples_returned += (n);		\
+		if (pgstat_should_count_index(rel))							\
+			(rel)->pgstatind_info->counts.tuples_returned += (n);	\
+	} while (0)
+#define pgstat_count_table_buffer_read(rel)							\
+	do {															\
+		if (pgstat_should_count_table(rel))							\
+			(rel)->pgstattab_info->counts.blocks_fetched++;			\
+	} while (0)
+#define pgstat_count_index_buffer_read(rel)							\
+	do {															\
+		if (pgstat_should_count_index(rel))							\
+			(rel)->pgstatind_info->counts.blocks_fetched++;			\
 	} while (0)
-#define pgstat_count_buffer_read(rel)								\
+#define pgstat_count_table_buffer_hit(rel)							\
 	do {															\
-		if (pgstat_should_count_relation(rel))						\
-			(rel)->pgstat_info->counts.blocks_fetched++;			\
+		if (pgstat_should_count_table(rel))							\
+			(rel)->pgstattab_info->counts.blocks_hit++;				\
 	} while (0)
-#define pgstat_count_buffer_hit(rel)								\
+#define pgstat_count_index_buffer_hit(rel)							\
 	do {															\
-		if (pgstat_should_count_relation(rel))						\
-			(rel)->pgstat_info->counts.blocks_hit++;				\
+		if (pgstat_should_count_index(rel))							\
+			(rel)->pgstatind_info->counts.blocks_hit++;				\
 	} while (0)
 
 extern void pgstat_count_heap_insert(Relation rel, PgStat_Counter n);
@@ -655,6 +756,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 60fbf9394b..c9336418b4 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -370,11 +370,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
 {
@@ -573,16 +579,23 @@ extern void pgstat_io_snapshot_cb(void);
 
 
 /*
- * Functions in pgstat_relation.c
+ * Functions in pgstat_table.c
  */
 
-extern void AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit);
-extern void AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth);
-extern void AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state);
-extern void PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state);
+extern void AtEOXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit);
+extern void AtEOSubXact_PgStat_Tables(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth);
+extern void AtPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state);
+extern void PostPrepare_PgStat_Tables(PgStat_SubXactStatus *xact_state);
+
+extern bool pgstat_table_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+extern void pgstat_table_delete_pending_cb(PgStat_EntryRef *entry_ref);
+
+/*
+ * Functions in pgstat_index.c
+ */
 
-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_index_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+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 0ad613c4b8..fd56e737c5 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -251,7 +251,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..494adee88b 100644
--- a/src/test/isolation/expected/stats.out
+++ b/src/test/isolation/expected/stats.out
@@ -2166,14 +2166,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2210,14 +2210,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2236,14 +2236,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2275,14 +2275,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2314,14 +2314,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2369,14 +2369,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2401,14 +2401,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2463,14 +2463,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2495,14 +2495,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2558,14 +2558,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2627,14 +2627,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2688,14 +2688,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2755,14 +2755,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2795,14 +2795,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2841,14 +2841,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2881,14 +2881,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2927,14 +2927,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2968,14 +2968,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -3015,14 +3015,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
diff --git a/src/test/isolation/expected/stats_1.out b/src/test/isolation/expected/stats_1.out
index 3854320106..7981747732 100644
--- a/src/test/isolation/expected/stats_1.out
+++ b/src/test/isolation/expected/stats_1.out
@@ -2174,14 +2174,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2218,14 +2218,14 @@ 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_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2244,14 +2244,14 @@ 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_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2283,14 +2283,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2322,14 +2322,14 @@ 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_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2377,14 +2377,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2409,14 +2409,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2471,14 +2471,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2503,14 +2503,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2566,14 +2566,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2635,14 +2635,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2698,14 +2698,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2767,14 +2767,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2809,14 +2809,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2857,14 +2857,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2899,14 +2899,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2947,14 +2947,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -2990,14 +2990,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
@@ -3039,14 +3039,14 @@ pg_stat_force_next_flush
 
 step s1_table_stats: 
     SELECT
-        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
         pg_stat_get_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 
diff --git a/src/test/isolation/specs/stats.spec b/src/test/isolation/specs/stats.spec
index 5b922d788c..53d78f7db1 100644
--- a/src/test/isolation/specs/stats.spec
+++ b/src/test/isolation/specs/stats.spec
@@ -93,14 +93,14 @@ 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_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,
-        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
-        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
-        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+        pg_stat_get_tab_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tab_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tab_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tab_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tab_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_tab_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_tab_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_tab_vacuum_count(tso.oid) AS vacuum_count
     FROM test_stat_oid AS tso
     WHERE tso.name = 'test_stat_tab'
 }
diff --git a/src/test/recovery/t/029_stats_restart.pl b/src/test/recovery/t/029_stats_restart.pl
index f6368ab1d3..ecfd359b74 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 51495aebcd..042ae55f17 100644
--- a/src/test/recovery/t/030_stats_cleanup_replica.pl
+++ b/src/test/recovery/t/030_stats_cleanup_replica.pl
@@ -179,7 +179,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 1442c43d9c..a4cace6ef3 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1768,10 +1768,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_ind_numscans(i.oid) AS idx_scan,
+    pg_stat_get_ind_lastscan(i.oid) AS last_idx_scan,
+    pg_stat_get_ind_tuples_returned(i.oid) AS idx_tup_read,
+    pg_stat_get_ind_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)))
@@ -1780,29 +1780,29 @@ 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_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,
-    pg_stat_get_tuples_hot_updated(c.oid) AS n_tup_hot_upd,
-    pg_stat_get_tuples_newpage_updated(c.oid) AS n_tup_newpage_upd,
-    pg_stat_get_live_tuples(c.oid) AS n_live_tup,
-    pg_stat_get_dead_tuples(c.oid) AS n_dead_tup,
-    pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze,
-    pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum,
-    pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum,
-    pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum,
-    pg_stat_get_last_analyze_time(c.oid) AS last_analyze,
-    pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze,
-    pg_stat_get_vacuum_count(c.oid) AS vacuum_count,
-    pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count,
-    pg_stat_get_analyze_count(c.oid) AS analyze_count,
-    pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count
+    pg_stat_get_tab_numscans(c.oid) AS seq_scan,
+    pg_stat_get_tab_lastscan(c.oid) AS last_seq_scan,
+    pg_stat_get_tab_tuples_returned(c.oid) AS seq_tup_read,
+    (sum(pg_stat_get_ind_numscans(i.indexrelid)))::bigint AS idx_scan,
+    max(pg_stat_get_ind_lastscan(i.indexrelid)) AS last_idx_scan,
+    ((sum(pg_stat_get_ind_tuples_fetched(i.indexrelid)))::bigint + pg_stat_get_tab_tuples_fetched(c.oid)) AS idx_tup_fetch,
+    pg_stat_get_tab_tuples_inserted(c.oid) AS n_tup_ins,
+    pg_stat_get_tab_tuples_updated(c.oid) AS n_tup_upd,
+    pg_stat_get_tab_tuples_deleted(c.oid) AS n_tup_del,
+    pg_stat_get_tab_tuples_hot_updated(c.oid) AS n_tup_hot_upd,
+    pg_stat_get_tab_tuples_newpage_updated(c.oid) AS n_tup_newpage_upd,
+    pg_stat_get_tab_live_tuples(c.oid) AS n_live_tup,
+    pg_stat_get_tab_dead_tuples(c.oid) AS n_dead_tup,
+    pg_stat_get_tab_mod_since_analyze(c.oid) AS n_mod_since_analyze,
+    pg_stat_get_tab_ins_since_vacuum(c.oid) AS n_ins_since_vacuum,
+    pg_stat_get_tab_last_vacuum_time(c.oid) AS last_vacuum,
+    pg_stat_get_tab_last_autovacuum_time(c.oid) AS last_autovacuum,
+    pg_stat_get_tab_last_analyze_time(c.oid) AS last_analyze,
+    pg_stat_get_tab_last_autoanalyze_time(c.oid) AS last_autoanalyze,
+    pg_stat_get_tab_vacuum_count(c.oid) AS vacuum_count,
+    pg_stat_get_tab_autovacuum_count(c.oid) AS autovacuum_count,
+    pg_stat_get_tab_analyze_count(c.oid) AS analyze_count,
+    pg_stat_get_tab_autoanalyze_count(c.oid) AS autoanalyze_count
    FROM ((pg_class c
      LEFT JOIN pg_index i ON ((c.oid = i.indrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
@@ -2252,15 +2252,15 @@ pg_stat_wal_receiver| SELECT 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_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_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,
-    pg_stat_get_xact_tuples_deleted(c.oid) AS n_tup_del,
-    pg_stat_get_xact_tuples_hot_updated(c.oid) AS n_tup_hot_upd,
-    pg_stat_get_xact_tuples_newpage_updated(c.oid) AS n_tup_newpage_upd
+    pg_stat_get_tab_xact_numscans(c.oid) AS seq_scan,
+    pg_stat_get_tab_xact_tuples_returned(c.oid) AS seq_tup_read,
+    (sum(pg_stat_get_ind_xact_numscans(i.indexrelid)))::bigint AS idx_scan,
+    ((sum(pg_stat_get_ind_xact_tuples_fetched(i.indexrelid)))::bigint + pg_stat_get_tab_xact_tuples_fetched(c.oid)) AS idx_tup_fetch,
+    pg_stat_get_tab_xact_tuples_inserted(c.oid) AS n_tup_ins,
+    pg_stat_get_tab_xact_tuples_updated(c.oid) AS n_tup_upd,
+    pg_stat_get_tab_xact_tuples_deleted(c.oid) AS n_tup_del,
+    pg_stat_get_tab_xact_tuples_hot_updated(c.oid) AS n_tup_hot_upd,
+    pg_stat_get_tab_xact_tuples_newpage_updated(c.oid) AS n_tup_newpage_upd
    FROM ((pg_class c
      LEFT JOIN pg_index i ON ((c.oid = i.indrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
@@ -2308,8 +2308,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_ind_blocks_fetched(i.oid) - pg_stat_get_ind_blocks_hit(i.oid)) AS idx_blks_read,
+    pg_stat_get_ind_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)))
@@ -2318,31 +2318,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_ind_blocks_fetched(c.oid) - pg_stat_get_tab_blocks_hit(c.oid)) AS blks_read,
+    pg_stat_get_tab_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_tab_blocks_fetched(c.oid) - pg_stat_get_tab_blocks_hit(c.oid)) AS heap_blks_read,
+    pg_stat_get_tab_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_tab_blocks_fetched(t.oid) - pg_stat_get_tab_blocks_hit(t.oid)) AS toast_blks_read,
+    pg_stat_get_tab_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_ind_blocks_fetched(pg_index.indexrelid) - pg_stat_get_ind_blocks_hit(pg_index.indexrelid))))::bigint AS idx_blks_read,
+            (sum(pg_stat_get_ind_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_ind_blocks_fetched(pg_index.indexrelid) - pg_stat_get_ind_blocks_hit(pg_index.indexrelid))))::bigint AS idx_blks_read,
+            (sum(pg_stat_get_ind_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 0694de736a..20ff012fd2 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;$$;
@@ -357,58 +468,58 @@ SELECT pg_stat_force_next_flush();
  
 (1 row)
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           1
 (1 row)
 
 DROP TABLE drop_stats_test;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       0
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           0
 (1 row)
 
-SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_oid);
- pg_stat_get_xact_tuples_inserted 
-----------------------------------
-                                0
+SELECT pg_stat_get_tab_xact_tuples_inserted(:drop_stats_test_oid);
+ pg_stat_get_tab_xact_tuples_inserted 
+--------------------------------------
+                                    0
 (1 row)
 
 -- check that rollback protects against having stats dropped and that local
 -- modifications don't pose a problem
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
-(1 row)
-
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_tuples_inserted 
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_live_tuples 
 -----------------------------
                            1
 (1 row)
 
-SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_xact_tuples_inserted 
-----------------------------------
-                                0
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_tuples_inserted 
+---------------------------------
+                               1
+(1 row)
+
+SELECT pg_stat_get_tab_xact_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_xact_tuples_inserted 
+--------------------------------------
+                                    0
 (1 row)
 
 BEGIN;
 INSERT INTO drop_stats_test_xact DEFAULT VALUES;
-SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_xact_tuples_inserted 
-----------------------------------
-                                1
+SELECT pg_stat_get_tab_xact_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_xact_tuples_inserted 
+--------------------------------------
+                                    1
 (1 row)
 
 DROP TABLE drop_stats_test_xact;
-SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_xact_tuples_inserted 
-----------------------------------
-                                0
+SELECT pg_stat_get_tab_xact_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_xact_tuples_inserted 
+--------------------------------------
+                                    0
 (1 row)
 
 ROLLBACK;
@@ -418,44 +529,44 @@ SELECT pg_stat_force_next_flush();
  
 (1 row)
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           1
 (1 row)
 
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_tuples_inserted 
------------------------------
-                           2
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_tuples_inserted 
+---------------------------------
+                               2
 (1 row)
 
 -- transactional drop
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           1
 (1 row)
 
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_tuples_inserted 
------------------------------
-                           2
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_tuples_inserted 
+---------------------------------
+                               2
 (1 row)
 
 BEGIN;
 INSERT INTO drop_stats_test_xact DEFAULT VALUES;
-SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_xact_tuples_inserted 
-----------------------------------
-                                1
+SELECT pg_stat_get_tab_xact_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_xact_tuples_inserted 
+--------------------------------------
+                                    1
 (1 row)
 
 DROP TABLE drop_stats_test_xact;
-SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_xact_tuples_inserted 
-----------------------------------
-                                0
+SELECT pg_stat_get_tab_xact_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_xact_tuples_inserted 
+--------------------------------------
+                                    0
 (1 row)
 
 COMMIT;
@@ -465,42 +576,42 @@ SELECT pg_stat_force_next_flush();
  
 (1 row)
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       0
-(1 row)
-
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
- pg_stat_get_tuples_inserted 
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_live_tuples 
 -----------------------------
                            0
 (1 row)
 
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tab_tuples_inserted 
+---------------------------------
+                               0
+(1 row)
+
 -- savepoint rollback (2 levels)
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       1
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           1
 (1 row)
 
 BEGIN;
 INSERT INTO drop_stats_test_subxact DEFAULT VALUES;
 SAVEPOINT sp1;
 INSERT INTO drop_stats_test_subxact DEFAULT VALUES;
-SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_subxact_oid);
- pg_stat_get_xact_tuples_inserted 
-----------------------------------
-                                2
+SELECT pg_stat_get_tab_xact_tuples_inserted(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_xact_tuples_inserted 
+--------------------------------------
+                                    2
 (1 row)
 
 SAVEPOINT sp2;
 DROP TABLE drop_stats_test_subxact;
 ROLLBACK TO SAVEPOINT sp2;
-SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_subxact_oid);
- pg_stat_get_xact_tuples_inserted 
-----------------------------------
-                                2
+SELECT pg_stat_get_tab_xact_tuples_inserted(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_xact_tuples_inserted 
+--------------------------------------
+                                    2
 (1 row)
 
 COMMIT;
@@ -510,17 +621,17 @@ SELECT pg_stat_force_next_flush();
  
 (1 row)
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       3
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           3
 (1 row)
 
 -- savepoint rolback (1 level)
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       3
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           3
 (1 row)
 
 BEGIN;
@@ -529,17 +640,17 @@ DROP TABLE drop_stats_test_subxact;
 SAVEPOINT sp2;
 ROLLBACK TO SAVEPOINT sp1;
 COMMIT;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       3
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           3
 (1 row)
 
 -- and now actually drop
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       3
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           3
 (1 row)
 
 BEGIN;
@@ -548,10 +659,10 @@ DROP TABLE drop_stats_test_subxact;
 SAVEPOINT sp2;
 RELEASE SAVEPOINT sp1;
 COMMIT;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
- pg_stat_get_live_tuples 
--------------------------
-                       0
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_tab_live_tuples 
+-----------------------------
+                           0
 (1 row)
 
 DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
@@ -1173,21 +1284,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
@@ -1203,14 +1314,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
@@ -1225,7 +1336,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
@@ -1233,7 +1344,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
@@ -1241,7 +1352,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
@@ -1249,7 +1360,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
@@ -1257,7 +1368,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
@@ -1578,7 +1689,7 @@ DECLARE
 BEGIN
   -- we don't want to wait forever; loop will exit after 30 seconds
   FOR i IN 1 .. 300 LOOP
-    SELECT (pg_stat_get_tuples_hot_updated('brin_hot'::regclass::oid) > 0) INTO updated;
+    SELECT (pg_stat_get_tab_tuples_hot_updated('brin_hot'::regclass::oid) > 0) INTO updated;
     EXIT WHEN updated;
 
     -- wait a little
@@ -1605,10 +1716,10 @@ SELECT wait_for_hot_stats();
  
 (1 row)
 
-SELECT pg_stat_get_tuples_hot_updated('brin_hot'::regclass::oid);
- pg_stat_get_tuples_hot_updated 
---------------------------------
-                              1
+SELECT pg_stat_get_tab_tuples_hot_updated('brin_hot'::regclass::oid);
+ pg_stat_get_tab_tuples_hot_updated 
+------------------------------------
+                                  1
 (1 row)
 
 DROP TABLE brin_hot;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 13db45d4dc..34a9a9378e 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
 ---
@@ -218,73 +289,73 @@ SELECT 'drop_stats_test_subxact'::regclass::oid AS drop_stats_test_subxact_oid \
 
 SELECT pg_stat_force_next_flush();
 
-SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_oid);
 DROP TABLE drop_stats_test;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
-SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_oid);
+SELECT pg_stat_get_tab_xact_tuples_inserted(:drop_stats_test_oid);
 
 -- check that rollback protects against having stats dropped and that local
 -- modifications don't pose a problem
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
-SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_xact_tuples_inserted(:drop_stats_test_xact_oid);
 BEGIN;
 INSERT INTO drop_stats_test_xact DEFAULT VALUES;
-SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_xact_tuples_inserted(:drop_stats_test_xact_oid);
 DROP TABLE drop_stats_test_xact;
-SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_xact_tuples_inserted(:drop_stats_test_xact_oid);
 ROLLBACK;
 SELECT pg_stat_force_next_flush();
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
 
 -- transactional drop
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
 BEGIN;
 INSERT INTO drop_stats_test_xact DEFAULT VALUES;
-SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_xact_tuples_inserted(:drop_stats_test_xact_oid);
 DROP TABLE drop_stats_test_xact;
-SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_xact_tuples_inserted(:drop_stats_test_xact_oid);
 COMMIT;
 SELECT pg_stat_force_next_flush();
-SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
-SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tab_tuples_inserted(:drop_stats_test_xact_oid);
 
 -- savepoint rollback (2 levels)
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 BEGIN;
 INSERT INTO drop_stats_test_subxact DEFAULT VALUES;
 SAVEPOINT sp1;
 INSERT INTO drop_stats_test_subxact DEFAULT VALUES;
-SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_xact_tuples_inserted(:drop_stats_test_subxact_oid);
 SAVEPOINT sp2;
 DROP TABLE drop_stats_test_subxact;
 ROLLBACK TO SAVEPOINT sp2;
-SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_xact_tuples_inserted(:drop_stats_test_subxact_oid);
 COMMIT;
 SELECT pg_stat_force_next_flush();
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 
 -- savepoint rolback (1 level)
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 BEGIN;
 SAVEPOINT sp1;
 DROP TABLE drop_stats_test_subxact;
 SAVEPOINT sp2;
 ROLLBACK TO SAVEPOINT sp1;
 COMMIT;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 
 -- and now actually drop
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 BEGIN;
 SAVEPOINT sp1;
 DROP TABLE drop_stats_test_subxact;
 SAVEPOINT sp2;
 RELEASE SAVEPOINT sp1;
 COMMIT;
-SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+SELECT pg_stat_get_tab_live_tuples(:drop_stats_test_subxact_oid);
 
 DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
 DROP TABLE prevstats;
@@ -561,40 +632,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;
@@ -793,7 +864,7 @@ DECLARE
 BEGIN
   -- we don't want to wait forever; loop will exit after 30 seconds
   FOR i IN 1 .. 300 LOOP
-    SELECT (pg_stat_get_tuples_hot_updated('brin_hot'::regclass::oid) > 0) INTO updated;
+    SELECT (pg_stat_get_tab_tuples_hot_updated('brin_hot'::regclass::oid) > 0) INTO updated;
     EXIT WHEN updated;
 
     -- wait a little
@@ -818,7 +889,7 @@ UPDATE brin_hot SET val = -3 WHERE id = 42;
 \c -
 
 SELECT wait_for_hot_stats();
-SELECT pg_stat_get_tuples_hot_updated('brin_hot'::regclass::oid);
+SELECT pg_stat_get_tab_tuples_hot_updated('brin_hot'::regclass::oid);
 
 DROP TABLE brin_hot;
 DROP FUNCTION wait_for_hot_stats();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index bf50a32119..10caf1706a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2042,10 +2042,11 @@ PgStatShared_Database
 PgStatShared_Function
 PgStatShared_HashEntry
 PgStatShared_IO
-PgStatShared_Relation
+PgStatShared_Index
 PgStatShared_ReplSlot
 PgStatShared_SLRU
 PgStatShared_Subscription
+PgStatShared_Table
 PgStatShared_Wal
 PgStat_ArchiverStats
 PgStat_BackendSubEntry
@@ -2059,6 +2060,7 @@ PgStat_FetchConsistency
 PgStat_FunctionCallUsage
 PgStat_FunctionCounts
 PgStat_HashKey
+PgStat_IndexStatus
 PgStat_IO
 PgStat_Kind
 PgStat_KindInfo
-- 
2.34.1

#33Andres Freund
andres@anarazel.de
In reply to: Drouvot, Bertrand (#32)
Re: Split index and table statistics into different types of stats

Hi,

On 2023-11-13 09:26:56 +0100, Drouvot, Bertrand wrote:

--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -799,11 +799,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);

It's not nice from a layering POV that we need this level of awareness in
bufmgr.c. I wonder if this is an argument for first splitting out stats like
blocks_hit, blocks_fetched into something like "relfilenode stats" - they're
agnostic of the relkind. There aren't that many such stats right now,
admittedly, but I think we'll want to also track dirtied, written blocks on a
per relation basis once we can (i.e. we key the relevant stats by relfilenode
instead of oid, so we can associate stats when writing out buffers).

+/*
+ * Initialize a relcache entry to count access statistics.  Called whenever an
+ * index is opened.
+ *
+ * We assume that a relcache entry's pgstatind_info field is 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
+ * pgstat_assoc_index().
+ */
+void
+pgstat_init_index(Relation rel)
+{
+	/*
+	 * We only count stats for indexes
+	 */
+	Assert(rel->rd_rel->relkind == RELKIND_INDEX);
+
+	if (!pgstat_track_counts)
+	{
+		if (rel->pgstatind_info != NULL)
+			pgstat_unlink_index(rel);
+
+		/* We're not counting at all */
+		rel->pgstat_enabled = false;
+		rel->pgstatind_info = NULL;
+		return;
+	}
+
+	rel->pgstat_enabled = true;
+}
+
+/*
+ * 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_index() 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->pgstatind_info->relation == NULL);
+
+	/* mark this relation as the owner */
+	rel->pgstatind_info->relation = rel;
+}
+
+/*
+ * Break the mutual link between a relcache entry and pending index stats entry.
+ * This must be called whenever one end of the link is removed.
+ */
+void
+pgstat_unlink_index(Relation rel)
+{
+
+	if (rel->pgstatind_info == NULL)
+		return;
+
+	/* 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;
+	}
+}
...

This is a fair bit of duplicated code - perhaps we could have shared helpers?

+/* ----------
+ * 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? */
+	struct PgStat_IndexXactStatus *trans;	/* lowest subxact's counts */
+	PgStat_IndexCounts counts;	/* event counts to be sent */
+	Relation	relation;		/* rel that is using this entry */
+} PgStat_IndexStatus;
+
/* ----------
* PgStat_TableXactStatus		Per-table, per-subtransaction status
* ----------
@@ -227,6 +264,29 @@ typedef struct PgStat_TableXactStatus
} PgStat_TableXactStatus;
+/* ----------
+ * PgStat_IndexXactStatus		Per-index, per-subtransaction status
+ * ----------
+ */
+typedef struct PgStat_IndexXactStatus
+{
+	PgStat_Counter tuples_inserted; /* tuples inserted in (sub)xact */
+	PgStat_Counter tuples_updated;	/* tuples updated in (sub)xact */
+	PgStat_Counter tuples_deleted;	/* tuples deleted in (sub)xact */
+	bool		truncdropped;	/* relation truncated/dropped in this
+								 * (sub)xact */
+	/* tuples i/u/d prior to truncate/drop */
+	PgStat_Counter inserted_pre_truncdrop;
+	PgStat_Counter updated_pre_truncdrop;
+	PgStat_Counter deleted_pre_truncdrop;
+	int			nest_level;		/* subtransaction nest level */
+	/* links to other structs for same relation: */
+	struct PgStat_IndexXactStatus *upper;	/* next higher subxact if any */
+	PgStat_IndexStatus *parent; /* per-table status */
+	/* structs of same subxact level are linked here: */
+	struct PgStat_IndexXactStatus *next;	/* next of same subxact */
+} PgStat_IndexXactStatus;

I don't think much of this is used? It doesn't look like you're using most of
the fields. Which makes sense - there's not really the same transactional
behaviour for indexes as there is for tables.

Greetings,

Andres Freund

#34Drouvot, Bertrand
bertranddrouvot.pg@gmail.com
In reply to: Andres Freund (#33)
Re: Split index and table statistics into different types of stats

Hi,

On 11/13/23 9:44 PM, Andres Freund wrote:

Hi,

On 2023-11-13 09:26:56 +0100, Drouvot, Bertrand wrote:

--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -799,11 +799,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);

It's not nice from a layering POV that we need this level of awareness in
bufmgr.c. I wonder if this is an argument for first splitting out stats like
blocks_hit, blocks_fetched into something like "relfilenode stats" - they're
agnostic of the relkind.

Thanks for looking at it! Yeah I think that would make a lot of sense
to track some stats per relfilenode.

There aren't that many such stats right now,
admittedly, but I think we'll want to also track dirtied, written blocks on a
per relation basis once we can (i.e. we key the relevant stats by relfilenode
instead of oid, so we can associate stats when writing out buffers).

Agree. Then, I think that would make sense to start this effort before the
split index/table one. I can work on a per relfilenode stat patch first.

Does this patch ordering make sense to you?

1) Introduce per relfilenode stats
2) Split index and table stats

+/*
+ * Initialize a relcache entry to count access statistics.  Called whenever an
+ * index is opened.
+ *
+ * We assume that a relcache entry's pgstatind_info field is 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
+ * pgstat_assoc_index().
+ */
+void
+pgstat_init_index(Relation rel)
+{
+	/*
+	 * We only count stats for indexes
+	 */
+	Assert(rel->rd_rel->relkind == RELKIND_INDEX);
+
+	if (!pgstat_track_counts)
+	{
+		if (rel->pgstatind_info != NULL)
+			pgstat_unlink_index(rel);
+
+		/* We're not counting at all */
+		rel->pgstat_enabled = false;
+		rel->pgstatind_info = NULL;
+		return;
+	}
+
+	rel->pgstat_enabled = true;
+}
+
+/*
+ * 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_index() 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->pgstatind_info->relation == NULL);
+
+	/* mark this relation as the owner */
+	rel->pgstatind_info->relation = rel;
+}
+
+/*
+ * Break the mutual link between a relcache entry and pending index stats entry.
+ * This must be called whenever one end of the link is removed.
+ */
+void
+pgstat_unlink_index(Relation rel)
+{
+
+	if (rel->pgstatind_info == NULL)
+		return;
+
+	/* 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;
+	}
+}
...

This is a fair bit of duplicated code - perhaps we could have shared helpers?

Yeah, I had it in mind and that was part of the "Will now work on addressing the
up-thread remaining comments" remark I made up-thread.

+/* ----------
+ * 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? */
+	struct PgStat_IndexXactStatus *trans;	/* lowest subxact's counts */
+	PgStat_IndexCounts counts;	/* event counts to be sent */
+	Relation	relation;		/* rel that is using this entry */
+} PgStat_IndexStatus;
+
/* ----------
* PgStat_TableXactStatus		Per-table, per-subtransaction status
* ----------
@@ -227,6 +264,29 @@ typedef struct PgStat_TableXactStatus
} PgStat_TableXactStatus;
+/* ----------
+ * PgStat_IndexXactStatus		Per-index, per-subtransaction status
+ * ----------
+ */
+typedef struct PgStat_IndexXactStatus
+{
+	PgStat_Counter tuples_inserted; /* tuples inserted in (sub)xact */
+	PgStat_Counter tuples_updated;	/* tuples updated in (sub)xact */
+	PgStat_Counter tuples_deleted;	/* tuples deleted in (sub)xact */
+	bool		truncdropped;	/* relation truncated/dropped in this
+								 * (sub)xact */
+	/* tuples i/u/d prior to truncate/drop */
+	PgStat_Counter inserted_pre_truncdrop;
+	PgStat_Counter updated_pre_truncdrop;
+	PgStat_Counter deleted_pre_truncdrop;
+	int			nest_level;		/* subtransaction nest level */
+	/* links to other structs for same relation: */
+	struct PgStat_IndexXactStatus *upper;	/* next higher subxact if any */
+	PgStat_IndexStatus *parent; /* per-table status */
+	/* structs of same subxact level are linked here: */
+	struct PgStat_IndexXactStatus *next;	/* next of same subxact */
+} PgStat_IndexXactStatus;

I don't think much of this is used? It doesn't look like you're using most of
the fields. Which makes sense - there's not really the same transactional
behaviour for indexes as there is for tables.

Fully agree. I had in mind to revisit this stuff too.

Regards,

--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

#35Bertrand Drouvot
bertranddrouvot.pg@gmail.com
In reply to: Drouvot, Bertrand (#34)
Re: Split index and table statistics into different types of stats

Hi,

On Tue, Nov 14, 2023 at 09:04:03AM +0100, Drouvot, Bertrand wrote:

On 11/13/23 9:44 PM, Andres Freund wrote:

Hi,

It's not nice from a layering POV that we need this level of awareness in
bufmgr.c. I wonder if this is an argument for first splitting out stats like
blocks_hit, blocks_fetched into something like "relfilenode stats" - they're
agnostic of the relkind.

Thanks for looking at it! Yeah I think that would make a lot of sense
to track some stats per relfilenode.

There aren't that many such stats right now,
admittedly, but I think we'll want to also track dirtied, written blocks on a
per relation basis once we can (i.e. we key the relevant stats by relfilenode
instead of oid, so we can associate stats when writing out buffers).

Agree. Then, I think that would make sense to start this effort before the
split index/table one. I can work on a per relfilenode stat patch first.

Does this patch ordering make sense to you?

1) Introduce per relfilenode stats
2) Split index and table stats

Just a quick update on this: I had a chat with Andres at pgconf.eu and we agreed
on the above ordering so that:

1) I started working on relfilenode stats (I hope to be able to provide a POC
patch soon).

2) The CF entry [1]https://commitfest.postgresql.org/47/4792/ status related to this thread has been changed to "Waiting
on Author".

[1]: https://commitfest.postgresql.org/47/4792/

Regards,

--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

#36Michael Paquier
michael@paquier.xyz
In reply to: Bertrand Drouvot (#35)
Re: Split index and table statistics into different types of stats

On Thu, Jan 25, 2024 at 08:36:17AM +0000, Bertrand Drouvot wrote:

Just a quick update on this: I had a chat with Andres at pgconf.eu and we agreed
on the above ordering so that:

1) I started working on relfilenode stats (I hope to be able to provide a POC
patch soon).

2) The CF entry [1] status related to this thread has been changed to "Waiting
on Author".

[1]: https://commitfest.postgresql.org/47/4792/

This update was from last January. With the recent work you have
posted for relfilenodes, I am going to mark this entry in the CF as
returned with feedback:
https://commitfest.postgresql.org/49/4792/

It could always be re-created later on, as required.
--
Michael