diff --git a/contrib/pgstattuple/expected/pgstattuple.out b/contrib/pgstattuple/expected/pgstattuple.out
index e920234488..169d1932b2 100644
--- a/contrib/pgstattuple/expected/pgstattuple.out
+++ b/contrib/pgstattuple/expected/pgstattuple.out
@@ -130,3 +130,11 @@ select * from pgstatginindex('test_ginidx');
2 | 0 | 0
(1 row)
+create index test_hashidx on test using hash (b);
+WARNING: hash indexes are not WAL-logged and their use is discouraged
+select * from pgstathashindex('test_hashidx');
+ version | bucket_pages | overflow_pages | bitmap_pages | zero_pages | live_items | dead_items | free_percent
+---------+--------------+----------------+--------------+------------+------------+------------+--------------
+ 2 | 4 | 0 | 1 | 0 | 0 | 0 | 100
+(1 row)
+
diff --git a/contrib/pgstattuple/pgstatindex.c b/contrib/pgstattuple/pgstatindex.c
index b40669250a..17a53e3bb7 100644
--- a/contrib/pgstattuple/pgstatindex.c
+++ b/contrib/pgstattuple/pgstatindex.c
@@ -29,6 +29,7 @@
#include "access/gin_private.h"
#include "access/heapam.h"
+#include "access/hash.h"
#include "access/htup_details.h"
#include "access/nbtree.h"
#include "catalog/namespace.h"
@@ -36,6 +37,7 @@
#include "funcapi.h"
#include "miscadmin.h"
#include "storage/bufmgr.h"
+#include "storage/lmgr.h"
#include "utils/builtins.h"
#include "utils/rel.h"
#include "utils/varlena.h"
@@ -54,6 +56,7 @@ PG_FUNCTION_INFO_V1(pgstatindexbyid);
PG_FUNCTION_INFO_V1(pg_relpages);
PG_FUNCTION_INFO_V1(pg_relpagesbyid);
PG_FUNCTION_INFO_V1(pgstatginindex);
+PG_FUNCTION_INFO_V1(pgstathashindex);
PG_FUNCTION_INFO_V1(pgstatindex_v1_5);
PG_FUNCTION_INFO_V1(pgstatindexbyid_v1_5);
@@ -66,6 +69,7 @@ Datum pgstatginindex_internal(Oid relid, FunctionCallInfo fcinfo);
#define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX)
#define IS_BTREE(r) ((r)->rd_rel->relam == BTREE_AM_OID)
#define IS_GIN(r) ((r)->rd_rel->relam == GIN_AM_OID)
+#define IS_HASH(r) ((r)->rd_rel->relam == HASH_AM_OID)
/* ------------------------------------------------
* A structure for a whole btree index statistics
@@ -102,7 +106,29 @@ typedef struct GinIndexStat
int64 pending_tuples;
} GinIndexStat;
+/* ------------------------------------------------
+ * A structure for a whole HASH index statistics
+ * used by pgstathashindex().
+ * ------------------------------------------------
+ */
+typedef struct HashIndexStat
+{
+ int32 version;
+ int32 space_per_page;
+
+ BlockNumber bucket_pages;
+ BlockNumber overflow_pages;
+ BlockNumber bitmap_pages;
+ BlockNumber zero_pages;
+
+ int64 live_items;
+ int64 dead_items;
+ uint64 free_space;
+} HashIndexStat;
+
static Datum pgstatindex_impl(Relation rel, FunctionCallInfo fcinfo);
+static void GetHashPageStats(Page page, HashIndexStat *stats);
+
/* ------------------------------------------------------
* pgstatindex()
@@ -528,3 +554,172 @@ pgstatginindex_internal(Oid relid, FunctionCallInfo fcinfo)
return (result);
}
+
+/* ------------------------------------------------------
+ * pgstathashindex()
+ *
+ * Usage: SELECT * FROM pgstathashindex('hashindex');
+ * ------------------------------------------------------
+ */
+Datum
+pgstathashindex(PG_FUNCTION_ARGS)
+{
+ Oid relid = PG_GETARG_OID(0);
+ BlockNumber nblocks;
+ BlockNumber blkno;
+ Relation rel;
+ HashIndexStat stats;
+ BufferAccessStrategy bstrategy;
+ HeapTuple tuple;
+ TupleDesc tupleDesc;
+ Datum values[8];
+ bool nulls[8];
+ Buffer metabuf;
+ HashMetaPage metap;
+ float8 free_percent;
+ uint64 total_space;
+
+ rel = index_open(relid, AccessShareLock);
+
+ if (!IS_HASH(rel))
+ elog(ERROR, "relation \"%s\" is not a HASH index",
+ RelationGetRelationName(rel));
+
+ /*
+ * Reject attempts to read non-local temporary relations; we would be
+ * likely to get wrong data since we have no visibility into the owning
+ * session's local buffers.
+ */
+ if (RELATION_IS_OTHER_TEMP(rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot access temporary indexes of other sessions")));
+
+ /* Get the information we need from the metapage. */
+ memset(&stats, 0, sizeof(stats));
+ metabuf = _hash_getbuf(rel, HASH_METAPAGE, HASH_READ, LH_META_PAGE);
+ metap = HashPageGetMeta(BufferGetPage(metabuf));
+ stats.version = metap->hashm_version;
+ stats.space_per_page = metap->hashm_bsize;
+ _hash_relbuf(rel, metabuf);
+
+ /* Get the current relation length */
+ nblocks = RelationGetNumberOfBlocks(rel);
+
+ /* prepare access strategy for this index */
+ bstrategy = GetAccessStrategy(BAS_BULKREAD);
+
+ /* Start from blkno 1 as 0th block is metapage */
+ for (blkno = 1; blkno < nblocks; blkno++)
+ {
+ Buffer buf;
+ Page page;
+ HashPageOpaque opaque;
+
+ CHECK_FOR_INTERRUPTS();
+
+ buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
+ bstrategy);
+ LockBuffer(buf, BUFFER_LOCK_SHARE);
+ page = (Page) BufferGetPage(buf);
+
+ if (PageIsNew(page))
+ stats.zero_pages++;
+ else if (PageGetSpecialSize(page) !=
+ MAXALIGN(sizeof(HashPageOpaqueData)))
+ ereport(ERROR,
+ (errcode(ERRCODE_INDEX_CORRUPTED),
+ errmsg("index \"%s\" contains corrupted page at block %u",
+ RelationGetRelationName(rel),
+ BufferGetBlockNumber(buf))));
+ else
+ {
+ opaque = (HashPageOpaque) PageGetSpecialPointer(page);
+ if (opaque->hasho_flag & LH_BUCKET_PAGE)
+ {
+ stats.bucket_pages++;
+ GetHashPageStats(page, &stats);
+ }
+ else if (opaque->hasho_flag & LH_OVERFLOW_PAGE)
+ {
+ stats.overflow_pages++;
+ GetHashPageStats(page, &stats);
+ }
+ else if (opaque->hasho_flag & LH_BITMAP_PAGE)
+ stats.bitmap_pages++;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INDEX_CORRUPTED),
+ errmsg("unexpected page type 0x%04X in HASH index \"%s\" block %u",
+ opaque->hasho_flag, RelationGetRelationName(rel),
+ BufferGetBlockNumber(buf))));
+ }
+ UnlockReleaseBuffer(buf);
+ }
+
+ /* Done accessing the index */
+ index_close(rel, AccessShareLock);
+
+ /* Count zero pages as free space. */
+ stats.free_space += stats.zero_pages * stats.space_per_page;
+
+ /*
+ * Total space available for tuples excludes the metapage and the bitmap
+ * pages.
+ */
+ total_space = (nblocks - (stats.bitmap_pages + 1)) * stats.space_per_page;
+
+ if (total_space == 0)
+ free_percent = 0.0;
+ else
+ free_percent = 100.0 * stats.free_space / total_space;
+
+ /*
+ * Build a tuple descriptor for our result type
+ */
+ if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ tupleDesc = BlessTupleDesc(tupleDesc);
+
+ /*
+ * Build and return the tuple
+ */
+ MemSet(nulls, 0, sizeof(nulls));
+ values[0] = Int32GetDatum(stats.version);
+ values[1] = Int64GetDatum((int64) stats.bucket_pages);
+ values[2] = Int64GetDatum((int64) stats.overflow_pages);
+ values[3] = Int64GetDatum((int64) stats.bitmap_pages);
+ values[4] = Int64GetDatum((int64) stats.zero_pages);
+ values[5] = Int64GetDatum(stats.live_items);
+ values[6] = Int64GetDatum(stats.dead_items);
+ values[7] = Float8GetDatum(free_percent);
+ tuple = heap_form_tuple(tupleDesc, values, nulls);
+
+ PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
+}
+
+/* -------------------------------------------------
+ * GetHashPageStatis()
+ *
+ * Collect statistics of single hash page
+ * -------------------------------------------------
+ */
+static void
+GetHashPageStats(Page page, HashIndexStat *stats)
+{
+ OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
+ int off;
+
+ /* count live and dead tuples, and free space */
+ for (off = FirstOffsetNumber; off <= maxoff; off++)
+ {
+ ItemId id = PageGetItemId(page, off);
+
+ if (!ItemIdIsDead(id))
+ stats->live_items++;
+ else
+ stats->dead_items++;
+ }
+ stats->free_space += PageGetExactFreeSpace(page);
+}
diff --git a/contrib/pgstattuple/pgstattuple--1.4--1.5.sql b/contrib/pgstattuple/pgstattuple--1.4--1.5.sql
index 65d7f19c2a..84e112e1c2 100644
--- a/contrib/pgstattuple/pgstattuple--1.4--1.5.sql
+++ b/contrib/pgstattuple/pgstattuple--1.4--1.5.sql
@@ -109,3 +109,19 @@ AS 'MODULE_PATHNAME', 'pgstattuple_approx_v1_5'
LANGUAGE C STRICT PARALLEL SAFE;
REVOKE EXECUTE ON FUNCTION pgstattuple_approx(regclass) FROM PUBLIC;
+
+/* New stuff in 1.5 begins here */
+
+CREATE OR REPLACE FUNCTION pgstathashindex(IN relname regclass,
+ OUT version INTEGER,
+ OUT bucket_pages BIGINT,
+ OUT overflow_pages BIGINT,
+ OUT bitmap_pages BIGINT,
+ OUT zero_pages BIGINT,
+ OUT live_items BIGINT,
+ OUT dead_items BIGINT,
+ OUT free_percent FLOAT8)
+AS 'MODULE_PATHNAME', 'pgstathashindex'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+REVOKE EXECUTE ON FUNCTION pgstathashindex(regclass) FROM PUBLIC;
diff --git a/contrib/pgstattuple/sql/pgstattuple.sql b/contrib/pgstattuple/sql/pgstattuple.sql
index d22c9f1c46..81fd5d693b 100644
--- a/contrib/pgstattuple/sql/pgstattuple.sql
+++ b/contrib/pgstattuple/sql/pgstattuple.sql
@@ -47,3 +47,7 @@ select pg_relpages(relname) from pg_class where relname = 'test_pkey';
create index test_ginidx on test using gin (b);
select * from pgstatginindex('test_ginidx');
+
+create index test_hashidx on test using hash (b);
+
+select * from pgstathashindex('test_hashidx');
diff --git a/doc/src/sgml/pgstattuple.sgml b/doc/src/sgml/pgstattuple.sgml
index d2fa524d6e..62b1a6f479 100644
--- a/doc/src/sgml/pgstattuple.sgml
+++ b/doc/src/sgml/pgstattuple.sgml
@@ -352,6 +352,101 @@ pending_tuples | 0
+
+
+
+ pgstathashindex
+
+ pgstathashindex(regclass) returns record>
+
+
+
+
+ pgstathashindex returns a record showing information
+ about a HASH index. For example:
+
+test=> select * from pgstathashindex('con_hash_index');
+-[ RECORD 1 ]--+-----------------
+version | 2
+bucket_pages | 33081
+overflow_pages | 0
+bitmap_pages | 1
+zero_pages | 32455
+live_items | 10204006
+dead_items | 0
+free_percent | 61.8005949100872
+
+
+
+
+ The output columns are:
+
+
+
+
+
+ Column
+ Type
+ Description
+
+
+
+
+
+ version
+ integer
+ HASH version number
+
+
+
+ bucket_pages
+ bigint
+ Number of bucket pages
+
+
+
+ overflow_pages
+ bigint
+ Number of overflow pages
+
+
+
+ bitmap_pages
+ bigint
+ Number of bitmap pages
+
+
+
+ zero_pages
+ bigint
+ Number of new or zero pages
+
+
+
+ live_items
+ bigint
+ Number of live tuples
+
+
+
+ dead_tuples
+ bigint
+ Number of dead tuples
+
+
+
+ free_percent
+ float
+ Percentage of free space
+
+
+
+
+
+
+
+
+