diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index 87a28e98c29a9dd786ebbace4d08a1ad804274c8..0a3cbeeb108ce3cb4ad6509c7db0b8395b905e9e 100644
--- a/contrib/pageinspect/Makefile
+++ b/contrib/pageinspect/Makefile
@@ -2,16 +2,16 @@
 
 MODULE_big	= pageinspect
 OBJS		= rawpage.o heapfuncs.o btreefuncs.o fsmfuncs.o \
-		  brinfuncs.o ginfuncs.o $(WIN32RES)
+		  brinfuncs.o ginfuncs.o hashfuncs.o $(WIN32RES)
 
 EXTENSION = pageinspect
-DATA = pageinspect--1.5.sql pageinspect--1.4--1.5.sql \
-	pageinspect--1.3--1.4.sql pageinspect--1.2--1.3.sql \
-	pageinspect--1.1--1.2.sql pageinspect--1.0--1.1.sql \
-	pageinspect--unpackaged--1.0.sql
+DATA = pageinspect--1.5.sql pageinspect--1.5--1.6.sql \
+	pageinspect--1.4--1.5.sql pageinspect--1.3--1.4.sql \
+	pageinspect--1.2--1.3.sql pageinspect--1.1--1.2.sql \
+	pageinspect--1.0--1.1.sql pageinspect--unpackaged--1.0.sql
 PGFILEDESC = "pageinspect - functions to inspect contents of database pages"
 
-REGRESS = page btree brin gin
+REGRESS = page btree brin gin hash
 
 ifdef USE_PGXS
 PG_CONFIG = pg_config
diff --git a/contrib/pageinspect/expected/hash.out b/contrib/pageinspect/expected/hash.out
new file mode 100644
index 0000000000000000000000000000000000000000..3abc8878008dcc4123a68199dbdf4ef248d35d40
--- /dev/null
+++ b/contrib/pageinspect/expected/hash.out
@@ -0,0 +1,150 @@
+CREATE TABLE test_hash (a int, b text);
+INSERT INTO test_hash VALUES (1, 'one');
+CREATE INDEX test_hash_a_idx ON test_hash USING hash (a);
+WARNING:  hash indexes are not WAL-logged and their use is discouraged
+\x
+SELECT hash_page_type(get_raw_page('test_hash_a_idx', 0));
+-[ RECORD 1 ]--+---------
+hash_page_type | metapage
+
+SELECT hash_page_type(get_raw_page('test_hash_a_idx', 1));
+-[ RECORD 1 ]--+-------
+hash_page_type | bucket
+
+SELECT hash_page_type(get_raw_page('test_hash_a_idx', 2));
+-[ RECORD 1 ]--+-------
+hash_page_type | bucket
+
+SELECT hash_page_type(get_raw_page('test_hash_a_idx', 3));
+-[ RECORD 1 ]--+-------
+hash_page_type | bucket
+
+SELECT hash_page_type(get_raw_page('test_hash_a_idx', 4));
+-[ RECORD 1 ]--+-------
+hash_page_type | bucket
+
+SELECT hash_page_type(get_raw_page('test_hash_a_idx', 5));
+-[ RECORD 1 ]--+-------
+hash_page_type | bitmap
+
+SELECT hash_page_type(get_raw_page('test_hash_a_idx', 6));
+ERROR:  block number 6 is out of range for relation "test_hash_a_idx"
+SELECT * FROM hash_bitmap_info('test_hash_a_idx', 0);
+ERROR:  page is not an overflow page
+DETAIL:  Expected 00000001, got 00000008.
+SELECT * FROM hash_bitmap_info('test_hash_a_idx', 1);
+ERROR:  page is not an overflow page
+DETAIL:  Expected 00000001, got 00000002.
+SELECT * FROM hash_bitmap_info('test_hash_a_idx', 2);
+ERROR:  page is not an overflow page
+DETAIL:  Expected 00000001, got 00000002.
+SELECT * FROM hash_bitmap_info('test_hash_a_idx', 3);
+ERROR:  page is not an overflow page
+DETAIL:  Expected 00000001, got 00000002.
+SELECT * FROM hash_bitmap_info('test_hash_a_idx', 4);
+ERROR:  page is not an overflow page
+DETAIL:  Expected 00000001, got 00000002.
+SELECT * FROM hash_bitmap_info('test_hash_a_idx', 5);
+ERROR:  page is not an overflow page
+DETAIL:  Expected 00000001, got 00000004.
+SELECT * FROM hash_metapage_info(get_raw_page('test_hash_a_idx', 0));
+-[ RECORD 1 ]----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+magic     | 105121344
+version   | 2
+ntuples   | 1
+ffactor   | 307
+bsize     | 8152
+bmsize    | 4096
+bmshift   | 15
+maxbucket | 3
+highmask  | 7
+lowmask   | 3
+ovflpoint | 2
+firstfree | 0
+nmaps     | 1
+procid    | 450
+spares    | {0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+mapp      | {5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+
+SELECT * FROM hash_metapage_info(get_raw_page('test_hash_a_idx', 1));
+ERROR:  page is not a hash meta page
+SELECT * FROM hash_metapage_info(get_raw_page('test_hash_a_idx', 2));
+ERROR:  page is not a hash meta page
+SELECT * FROM hash_metapage_info(get_raw_page('test_hash_a_idx', 3));
+ERROR:  page is not a hash meta page
+SELECT * FROM hash_metapage_info(get_raw_page('test_hash_a_idx', 4));
+ERROR:  page is not a hash meta page
+SELECT * FROM hash_metapage_info(get_raw_page('test_hash_a_idx', 5));
+ERROR:  page is not a hash meta page
+SELECT * FROM hash_page_stats(get_raw_page('test_hash_a_idx', 0));
+ERROR:  page is not a hash bucket or overflow page
+SELECT * FROM hash_page_stats(get_raw_page('test_hash_a_idx', 1));
+-[ RECORD 1 ]---+-----------
+live_items      | 0
+dead_items      | 0
+page_size       | 8192
+free_size       | 8148
+hasho_prevblkno | 4294967295
+hasho_nextblkno | 4294967295
+hasho_bucket    | 0
+hasho_flag      | 2
+hasho_page_id   | 65408
+
+SELECT * FROM hash_page_stats(get_raw_page('test_hash_a_idx', 2));
+-[ RECORD 1 ]---+-----------
+live_items      | 0
+dead_items      | 0
+page_size       | 8192
+free_size       | 8148
+hasho_prevblkno | 4294967295
+hasho_nextblkno | 4294967295
+hasho_bucket    | 1
+hasho_flag      | 2
+hasho_page_id   | 65408
+
+SELECT * FROM hash_page_stats(get_raw_page('test_hash_a_idx', 3));
+-[ RECORD 1 ]---+-----------
+live_items      | 1
+dead_items      | 0
+page_size       | 8192
+free_size       | 8128
+hasho_prevblkno | 4294967295
+hasho_nextblkno | 4294967295
+hasho_bucket    | 2
+hasho_flag      | 2
+hasho_page_id   | 65408
+
+SELECT * FROM hash_page_stats(get_raw_page('test_hash_a_idx', 4));
+-[ RECORD 1 ]---+-----------
+live_items      | 0
+dead_items      | 0
+page_size       | 8192
+free_size       | 8148
+hasho_prevblkno | 4294967295
+hasho_nextblkno | 4294967295
+hasho_bucket    | 3
+hasho_flag      | 2
+hasho_page_id   | 65408
+
+SELECT * FROM hash_page_stats(get_raw_page('test_hash_a_idx', 5));
+ERROR:  page is not a hash bucket or overflow page
+SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 0));
+ERROR:  page is not a hash bucket or overflow page
+SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 1));
+(0 rows)
+
+SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 2));
+(0 rows)
+
+SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 3));
+-[ RECORD 1 ]----------
+itemoffset | 1
+ctid       | (0,1)
+data       | 2389907270
+
+SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 4));
+(0 rows)
+
+SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 5));
+ERROR:  page is not a hash bucket or overflow page
+DROP TABLE test_hash;
diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c
new file mode 100644
index 0000000000000000000000000000000000000000..5812afe93662f1a87287aef72f8f9a9682a50109
--- /dev/null
+++ b/contrib/pageinspect/hashfuncs.c
@@ -0,0 +1,559 @@
+/*
+ * hashfuncs.c
+ *		Functions to investigate the content of HASH indexes
+ *
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		contrib/pageinspect/hashfuncs.c
+ */
+
+#include "postgres.h"
+
+#include "access/hash.h"
+#include "access/htup_details.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_am.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+
+PG_FUNCTION_INFO_V1(hash_page_type);
+PG_FUNCTION_INFO_V1(hash_page_stats);
+PG_FUNCTION_INFO_V1(hash_page_items);
+PG_FUNCTION_INFO_V1(hash_bitmap_info);
+PG_FUNCTION_INFO_V1(hash_metapage_info);
+
+#define IS_HASH(r) ((r)->rd_rel->relam == HASH_AM_OID)
+
+/* ------------------------------------------------
+ * structure for single hash page statistics
+ * ------------------------------------------------
+ */
+typedef struct HashPageStat
+{
+	uint16		live_items;
+	uint16		dead_items;
+	uint16		page_size;
+	uint16		free_size;
+
+	/* opaque data */
+	BlockNumber hasho_prevblkno;
+	BlockNumber hasho_nextblkno;
+	Bucket		hasho_bucket;
+	uint16		hasho_flag;
+	uint16		hasho_page_id;
+}	HashPageStat;
+
+
+/*
+ * Verify that the given bytea contains a HASH page, or die in the attempt.
+ * A pointer to the page is returned.
+ */
+static Page
+verify_hash_page(bytea *raw_page, int flags)
+{
+	Page		page;
+	int			raw_page_size;
+	int			pagetype;
+	HashPageOpaque pageopaque;
+
+	raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
+
+	if (raw_page_size != BLCKSZ)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid page size"),
+				 errdetail("Expected size %d, got %d",
+						   BLCKSZ, raw_page_size)));
+
+	page = VARDATA(raw_page);
+
+	if (PageIsNew(page))
+		ereport(ERROR,
+				(errcode(ERRCODE_INDEX_CORRUPTED),
+				 errmsg("index table contains zero page")));
+
+	if (PageGetSpecialSize(page) != MAXALIGN(sizeof(HashPageOpaqueData)))
+		ereport(ERROR,
+				(errcode(ERRCODE_INDEX_CORRUPTED),
+				 errmsg("index table contains corrupted page")));
+
+	pageopaque = (HashPageOpaque) PageGetSpecialPointer(page);
+	if (pageopaque->hasho_page_id != HASHO_PAGE_ID)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("page is not a hash page"),
+				 errdetail("Expected %08x, got %08x.",
+						   HASHO_PAGE_ID, pageopaque->hasho_page_id)));
+
+	/* Check that page type is sane. */
+	pagetype = pageopaque->hasho_flag & LH_PAGE_TYPE;
+	if (pagetype != LH_OVERFLOW_PAGE && pagetype != LH_BUCKET_PAGE &&
+		pagetype != LH_BITMAP_PAGE && pagetype != LH_META_PAGE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid hash page type %08x", pagetype)));
+
+	/* If requested, verify page type. */
+	if (flags != 0 && (pagetype & flags) == 0)
+	{
+		switch (flags)
+		{
+			case LH_META_PAGE:
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("page is not a hash meta page")));
+			case LH_BUCKET_PAGE | LH_OVERFLOW_PAGE:
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("page is not a hash bucket or overflow page")));
+			case LH_OVERFLOW_PAGE:
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("page is not a hash overflow page")));
+			default:
+				elog(ERROR,
+					 "hash page of type %08x not in mask %08x",
+					pagetype, flags);
+		}
+	}
+
+	/*
+	 * If it is the metapage, also verify magic number and version.
+	 */
+	if (pagetype == LH_META_PAGE)
+	{
+		HashMetaPage metap = HashPageGetMeta(page);
+
+		if (metap->hashm_magic != HASH_MAGIC)
+			ereport(ERROR,
+					(errcode(ERRCODE_INDEX_CORRUPTED),
+					 errmsg("invalid magic number for metadata"),
+					 errdetail("Expected 0x%08x, got 0x%08x.",
+							   HASH_MAGIC, metap->hashm_magic)));
+
+		if (metap->hashm_version != HASH_VERSION)
+			ereport(ERROR,
+					(errcode(ERRCODE_INDEX_CORRUPTED),
+					 errmsg("invalid version for metadata"),
+					 errdetail("Expected %d, got %d",
+							   HASH_VERSION, metap->hashm_version)));
+	}
+
+	return page;
+}
+
+/* -------------------------------------------------
+ * GetHashPageStatistics()
+ *
+ * Collect statistics of single hash page
+ * -------------------------------------------------
+ */
+static void
+GetHashPageStatistics(Page page, HashPageStat * stat)
+{
+	OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
+	HashPageOpaque opaque = (HashPageOpaque) PageGetSpecialPointer(page);
+	int			off;
+
+	stat->dead_items = stat->live_items = 0;
+	stat->page_size = PageGetPageSize(page);
+
+	/* hash page opaque data */
+	stat->hasho_prevblkno = opaque->hasho_prevblkno;
+	stat->hasho_nextblkno = opaque->hasho_nextblkno;
+	stat->hasho_bucket = opaque->hasho_bucket;
+	stat->hasho_flag = opaque->hasho_flag;
+	stat->hasho_page_id = opaque->hasho_page_id;
+
+	/* count live and dead tuples, and free space */
+	for (off = FirstOffsetNumber; off <= maxoff; off++)
+	{
+		ItemId		id = PageGetItemId(page, off);
+
+		if (!ItemIdIsDead(id))
+			stat->live_items++;
+		else
+			stat->dead_items++;
+	}
+	stat->free_size = PageGetFreeSpace(page);
+}
+
+/* ---------------------------------------------------
+ * hash_page_type()
+ *
+ * Usage: SELECT hash_page_type(get_raw_page('con_hash_index', 1));
+ * ---------------------------------------------------
+ */
+Datum
+hash_page_type(PG_FUNCTION_ARGS)
+{
+	bytea	   *raw_page = PG_GETARG_BYTEA_P(0);
+	Page		page;
+	HashPageOpaque opaque;
+	char	   *type;
+
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 (errmsg("must be superuser to use raw page functions"))));
+
+	page = verify_hash_page(raw_page, 0);
+	opaque = (HashPageOpaque) PageGetSpecialPointer(page);
+
+	/* page type (flags) */
+	if (opaque->hasho_flag & LH_META_PAGE)
+		type = "metapage";
+	else if (opaque->hasho_flag & LH_OVERFLOW_PAGE)
+		type = "overflow";
+	else if (opaque->hasho_flag & LH_BUCKET_PAGE)
+		type = "bucket";
+	else if (opaque->hasho_flag & LH_BITMAP_PAGE)
+		type = "bitmap";
+	else
+		type = "unused";
+
+	PG_RETURN_TEXT_P(cstring_to_text(type));
+}
+
+/* ---------------------------------------------------
+ * hash_page_stats()
+ *
+ * Usage: SELECT * FROM hash_page_stats(get_raw_page('con_hash_index', 1));
+ * ---------------------------------------------------
+ */
+Datum
+hash_page_stats(PG_FUNCTION_ARGS)
+{
+	bytea	   *raw_page = PG_GETARG_BYTEA_P(0);
+	Page		page;
+	int			j;
+	Datum		values[9];
+	bool		nulls[9];
+	HashPageStat stat;
+	HeapTuple	tuple;
+	TupleDesc	tupleDesc;
+
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 (errmsg("must be superuser to use raw page functions"))));
+
+	page = verify_hash_page(raw_page, LH_BUCKET_PAGE | LH_OVERFLOW_PAGE);
+
+	/* keep compiler quiet */
+	stat.hasho_prevblkno = stat.hasho_nextblkno = InvalidBlockNumber;
+	stat.hasho_flag = stat.hasho_page_id = stat.free_size = 0;
+
+	GetHashPageStatistics(page, &stat);
+
+	/* 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);
+
+	MemSet(nulls, 0, sizeof(nulls));
+
+	j = 0;
+	values[j++] = UInt16GetDatum(stat.live_items);
+	values[j++] = UInt16GetDatum(stat.dead_items);
+	values[j++] = UInt16GetDatum(stat.page_size);
+	values[j++] = UInt16GetDatum(stat.free_size);
+	values[j++] = UInt32GetDatum(stat.hasho_prevblkno);
+	values[j++] = UInt32GetDatum(stat.hasho_nextblkno);
+	values[j++] = UInt32GetDatum(stat.hasho_bucket);
+	values[j++] = UInt16GetDatum(stat.hasho_flag);
+	values[j++] = UInt16GetDatum(stat.hasho_page_id);
+
+	tuple = heap_form_tuple(tupleDesc, values, nulls);
+
+	PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
+}
+
+/*
+ * cross-call data structure for SRF
+ */
+struct user_args
+{
+	Page		page;
+	OffsetNumber offset;
+};
+
+/*-------------------------------------------------------
+ * hash_page_items()
+ *
+ * Get IndexTupleData set in a hash page
+ *
+ * Usage: SELECT * FROM hash_page_items(get_raw_page('con_hash_index', 1));
+ *-------------------------------------------------------
+ */
+Datum
+hash_page_items(PG_FUNCTION_ARGS)
+{
+	bytea	   *raw_page = PG_GETARG_BYTEA_P(0);
+	Page		page;
+	Datum		result;
+	Datum		values[3];
+	bool		nulls[3];
+	uint32		hashkey;
+	HeapTuple	tuple;
+	FuncCallContext *fctx;
+	MemoryContext mctx;
+	struct user_args *uargs;
+
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 (errmsg("must be superuser to use raw page functions"))));
+
+	if (SRF_IS_FIRSTCALL())
+	{
+		TupleDesc	tupleDesc;
+
+		fctx = SRF_FIRSTCALL_INIT();
+
+		page = verify_hash_page(raw_page, LH_BUCKET_PAGE | LH_OVERFLOW_PAGE);
+
+		mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
+
+		uargs = palloc(sizeof(struct user_args));
+
+		uargs->page = page;
+
+		uargs->offset = FirstOffsetNumber;
+
+		fctx->max_calls = PageGetMaxOffsetNumber(uargs->page);
+
+		/* 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);
+
+		fctx->attinmeta = TupleDescGetAttInMetadata(tupleDesc);
+
+		fctx->user_fctx = uargs;
+
+		MemoryContextSwitchTo(mctx);
+	}
+
+	fctx = SRF_PERCALL_SETUP();
+	uargs = fctx->user_fctx;
+
+	if (fctx->call_cntr < fctx->max_calls)
+	{
+		ItemId		id;
+		IndexTuple	itup;
+		int			j;
+
+		id = PageGetItemId(uargs->page, uargs->offset);
+
+		if (!ItemIdIsValid(id))
+			elog(ERROR, "invalid ItemId");
+
+		itup = (IndexTuple) PageGetItem(uargs->page, id);
+
+		MemSet(nulls, 0, sizeof(nulls));
+
+		j = 0;
+		values[j++] = UInt16GetDatum(uargs->offset);
+		values[j++] = PointerGetDatum(&itup->t_tid);
+
+		hashkey = _hash_get_indextuple_hashkey(itup);
+		values[j] = UInt32GetDatum(hashkey);
+
+		tuple = heap_form_tuple(fctx->attinmeta->tupdesc, values, nulls);
+		result = HeapTupleGetDatum(tuple);
+
+		uargs->offset = uargs->offset + 1;
+
+		SRF_RETURN_NEXT(fctx, result);
+	}
+	else
+	{
+		pfree(uargs);
+		SRF_RETURN_DONE(fctx);
+	}
+}
+
+/* ------------------------------------------------
+ * hash_bitmap_info()
+ *
+ * Get bitmap information for a particular overflow page
+ *
+ * Usage: SELECT * FROM hash_bitmap_info('con_hash_index'::regclass, 5);
+ * ------------------------------------------------
+ */
+Datum
+hash_bitmap_info(PG_FUNCTION_ARGS)
+{
+	Oid			indexRelid = PG_GETARG_OID(0);
+	uint32		ovflblkno = PG_GETARG_UINT32(1);
+	HashMetaPage metap;
+	Buffer		buf,
+				metabuf;
+	BlockNumber bitmapblkno;
+	Page		page;
+	bool		bit = false;
+	HashPageOpaque	opaque;
+	TupleDesc	tupleDesc;
+	Relation	indexRel;
+	uint32		ovflbitno;
+	int32		bitmappage,
+				bitmapbit;
+	HeapTuple	tuple;
+	int			j;
+	Datum		values[3];
+	bool		nulls[3];
+
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 (errmsg("must be superuser to use raw page functions"))));
+
+	indexRel = index_open(indexRelid, AccessShareLock);
+
+	if (!IS_HASH(indexRel))
+		elog(ERROR, "relation \"%s\" is not a hash index",
+			 RelationGetRelationName(indexRel));
+
+	if (RELATION_IS_OTHER_TEMP(indexRel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot access temporary tables of other sessions")));
+
+	if (RelationGetNumberOfBlocks(indexRel) <= (BlockNumber) (ovflblkno))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("block number %u is out of range for relation \"%s\"",
+						ovflblkno, RelationGetRelationName(indexRel))));
+
+	buf = ReadBufferExtended(indexRel, MAIN_FORKNUM, ovflblkno, RBM_NORMAL, NULL);
+	LockBuffer(buf, BUFFER_LOCK_SHARE);
+	_hash_checkpage(indexRel, buf, LH_PAGE_TYPE);
+	page = BufferGetPage(buf);
+	opaque = (HashPageOpaque) PageGetSpecialPointer(page);
+
+	if (opaque->hasho_flag != LH_OVERFLOW_PAGE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("page is not an overflow page"),
+				 errdetail("Expected %08x, got %08x.",
+							LH_OVERFLOW_PAGE, opaque->hasho_flag)));
+
+	if (BlockNumberIsValid(opaque->hasho_prevblkno))
+		bit = true;
+
+	UnlockReleaseBuffer(buf);
+
+	/* Read the metapage so we can determine which bitmap page to use */
+	metabuf = _hash_getbuf(indexRel, HASH_METAPAGE, HASH_READ, LH_META_PAGE);
+	metap = HashPageGetMeta(BufferGetPage(metabuf));
+
+	/* Identify overflow bit number */
+	ovflbitno = _hash_ovflblkno_to_bitno(metap, ovflblkno);
+
+	bitmappage = ovflbitno >> BMPG_SHIFT(metap);
+	bitmapbit = ovflbitno & BMPG_MASK(metap);
+
+	if (bitmappage >= metap->hashm_nmaps)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid overflow bit number %u", ovflbitno)));
+
+	bitmapblkno = metap->hashm_mapp[bitmappage];
+
+	_hash_relbuf(indexRel, metabuf);
+
+	index_close(indexRel, AccessShareLock);
+
+	/* 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);
+
+	MemSet(nulls, 0, sizeof(nulls));
+
+	j = 0;
+	values[j++] = UInt32GetDatum(bitmapblkno);
+	values[j++] = Int32GetDatum(bitmapbit);
+	values[j++] = BoolGetDatum(bit);
+
+	tuple = heap_form_tuple(tupleDesc, values, nulls);
+
+	PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
+}
+
+/* ------------------------------------------------
+ * hash_metapage_info()
+ *
+ * Get the meta-page information for a hash index
+ *
+ * Usage: SELECT * FROM hash_metapage_info(get_raw_page('con_hash_index', 0))
+ * ------------------------------------------------
+ */
+Datum
+hash_metapage_info(PG_FUNCTION_ARGS)
+{
+	bytea	   *raw_page = PG_GETARG_BYTEA_P(0);
+	Page		page;
+	HashMetaPageData *metad;
+	TupleDesc	tupleDesc;
+	HeapTuple	tuple;
+	int			i,
+				j;
+	Datum		values[16];
+	bool		nulls[16];
+	Datum       spares[HASH_MAX_SPLITPOINTS];
+	Datum       mapp[HASH_MAX_BITMAPS];
+
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 (errmsg("must be superuser to use raw page functions"))));
+
+	page = verify_hash_page(raw_page, LH_META_PAGE);
+
+	/* 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);
+
+	metad = HashPageGetMeta(page);
+
+	MemSet(nulls, 0, sizeof(nulls));
+
+	j = 0;
+	values[j++] = UInt32GetDatum(metad->hashm_magic);
+	values[j++] = UInt32GetDatum(metad->hashm_version);
+	values[j++] = Float8GetDatum(metad->hashm_ntuples);
+	values[j++] = UInt16GetDatum(metad->hashm_ffactor);
+	values[j++] = UInt16GetDatum(metad->hashm_bsize);
+	values[j++] = UInt16GetDatum(metad->hashm_bmsize);
+	values[j++] = UInt16GetDatum(metad->hashm_bmshift);
+	values[j++] = UInt32GetDatum(metad->hashm_maxbucket);
+	values[j++] = UInt32GetDatum(metad->hashm_highmask);
+	values[j++] = UInt32GetDatum(metad->hashm_lowmask);
+	values[j++] = UInt32GetDatum(metad->hashm_ovflpoint);
+	values[j++] = UInt32GetDatum(metad->hashm_firstfree);
+	values[j++] = UInt32GetDatum(metad->hashm_nmaps);
+	values[j++] = UInt16GetDatum(metad->hashm_procid);
+
+	for (i = 0; i < HASH_MAX_SPLITPOINTS; i++)
+		spares[i] = UInt32GetDatum(metad->hashm_spares[i]);
+	values[j++] = PointerGetDatum(construct_array(spares,
+												  HASH_MAX_SPLITPOINTS,
+												  INT8OID,
+												  8, true, 'd'));
+
+	for (i = 0; i < HASH_MAX_BITMAPS; i++)
+		mapp[i] = UInt32GetDatum(metad->hashm_mapp[i]);
+	values[j++] = PointerGetDatum(construct_array(mapp,
+												  HASH_MAX_BITMAPS,
+												  INT8OID,
+												  8, true, 'd'));
+
+	tuple = heap_form_tuple(tupleDesc, values, nulls);
+
+	PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
+}
diff --git a/contrib/pageinspect/pageinspect--1.5--1.6.sql b/contrib/pageinspect/pageinspect--1.5--1.6.sql
new file mode 100644
index 0000000000000000000000000000000000000000..d0355b4c7ecd486060c84c8184bb2b06cbb1068e
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.5--1.6.sql
@@ -0,0 +1,77 @@
+/* contrib/pageinspect/pageinspect--1.5--1.6.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION pageinspect UPDATE TO '1.6'" to load this file. \quit
+
+--
+-- HASH functions
+--
+
+--
+-- hash_page_type()
+--
+CREATE FUNCTION hash_page_type(IN page bytea)
+RETURNS text
+AS 'MODULE_PATHNAME', 'hash_page_type'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- hash_page_stats()
+--
+CREATE FUNCTION hash_page_stats(IN page bytea,
+    OUT live_items smallint,
+    OUT dead_items smallint,
+    OUT page_size smallint,
+    OUT free_size smallint,
+    OUT hasho_prevblkno int8,
+    OUT hasho_nextblkno int8,
+    OUT hasho_bucket int8,
+	OUT hasho_flag smallint,
+	OUT hasho_page_id int4)
+AS 'MODULE_PATHNAME', 'hash_page_stats'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- hash_page_items()
+--
+CREATE FUNCTION hash_page_items(IN page bytea,
+	OUT itemoffset smallint,
+	OUT ctid tid,
+	OUT data int8)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'hash_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- hash_bitmap_info()
+--
+CREATE FUNCTION hash_bitmap_info(IN index_oid regclass, IN blkno int8,
+	OUT bitmapblkno int8,
+	OUT bitmapbit int4,
+	OUT bitstatus bool)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'hash_bitmap_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- hash_metapage_info()
+--
+CREATE FUNCTION hash_metapage_info(IN page bytea,
+    OUT magic int8,
+    OUT version int8,
+    OUT ntuples double precision,
+    OUT ffactor int4,
+    OUT bsize int4,
+    OUT bmsize int4,
+    OUT bmshift int4,
+    OUT maxbucket int8,
+    OUT highmask int8,
+    OUT lowmask int8,
+    OUT ovflpoint int8,
+    OUT firstfree int8,
+    OUT nmaps int8,
+    OUT procid int4,
+    OUT spares int8[],
+    OUT mapp int8[])
+AS 'MODULE_PATHNAME', 'hash_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control
index 23c8eff9cd7dde9065eb9909a5ed98100e8061cc..1a61c9f5ad31b80b399f6779a0a729a5b56c16b9 100644
--- a/contrib/pageinspect/pageinspect.control
+++ b/contrib/pageinspect/pageinspect.control
@@ -1,5 +1,5 @@
 # pageinspect extension
 comment = 'inspect the contents of database pages at a low level'
-default_version = '1.5'
+default_version = '1.6'
 module_pathname = '$libdir/pageinspect'
 relocatable = true
diff --git a/contrib/pageinspect/sql/hash.sql b/contrib/pageinspect/sql/hash.sql
new file mode 100644
index 0000000000000000000000000000000000000000..9e7635e36d790ac7f4ec0a3e13ad6a149b8dd2de
--- /dev/null
+++ b/contrib/pageinspect/sql/hash.sql
@@ -0,0 +1,49 @@
+CREATE TABLE test_hash (a int, b text);
+INSERT INTO test_hash VALUES (1, 'one');
+CREATE INDEX test_hash_a_idx ON test_hash USING hash (a);
+
+\x
+
+SELECT hash_page_type(get_raw_page('test_hash_a_idx', 0));
+SELECT hash_page_type(get_raw_page('test_hash_a_idx', 1));
+SELECT hash_page_type(get_raw_page('test_hash_a_idx', 2));
+SELECT hash_page_type(get_raw_page('test_hash_a_idx', 3));
+SELECT hash_page_type(get_raw_page('test_hash_a_idx', 4));
+SELECT hash_page_type(get_raw_page('test_hash_a_idx', 5));
+SELECT hash_page_type(get_raw_page('test_hash_a_idx', 6));
+
+
+SELECT * FROM hash_bitmap_info('test_hash_a_idx', 0);
+SELECT * FROM hash_bitmap_info('test_hash_a_idx', 1);
+SELECT * FROM hash_bitmap_info('test_hash_a_idx', 2);
+SELECT * FROM hash_bitmap_info('test_hash_a_idx', 3);
+SELECT * FROM hash_bitmap_info('test_hash_a_idx', 4);
+SELECT * FROM hash_bitmap_info('test_hash_a_idx', 5);
+
+
+
+SELECT * FROM hash_metapage_info(get_raw_page('test_hash_a_idx', 0));
+SELECT * FROM hash_metapage_info(get_raw_page('test_hash_a_idx', 1));
+SELECT * FROM hash_metapage_info(get_raw_page('test_hash_a_idx', 2));
+SELECT * FROM hash_metapage_info(get_raw_page('test_hash_a_idx', 3));
+SELECT * FROM hash_metapage_info(get_raw_page('test_hash_a_idx', 4));
+SELECT * FROM hash_metapage_info(get_raw_page('test_hash_a_idx', 5));
+
+
+SELECT * FROM hash_page_stats(get_raw_page('test_hash_a_idx', 0));
+SELECT * FROM hash_page_stats(get_raw_page('test_hash_a_idx', 1));
+SELECT * FROM hash_page_stats(get_raw_page('test_hash_a_idx', 2));
+SELECT * FROM hash_page_stats(get_raw_page('test_hash_a_idx', 3));
+SELECT * FROM hash_page_stats(get_raw_page('test_hash_a_idx', 4));
+SELECT * FROM hash_page_stats(get_raw_page('test_hash_a_idx', 5));
+
+
+SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 0));
+SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 1));
+SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 2));
+SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 3));
+SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 4));
+SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 5));
+
+
+DROP TABLE test_hash;
diff --git a/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml
index d12dbac32db9a98ba06b4af2940d5647604264b2..4c201e75b0de33bb5ca36ec568b159daf7ac7a8c 100644
--- a/doc/src/sgml/pageinspect.sgml
+++ b/doc/src/sgml/pageinspect.sgml
@@ -486,6 +486,150 @@ test=# SELECT first_tid, nbytes, tids[0:5] AS some_tids
  (170,30)  |    376 | {"(170,30)","(170,31)","(170,32)","(170,33)","(170,34)"}
  (173,44)  |    197 | {"(173,44)","(173,45)","(173,46)","(173,47)","(173,48)"}
 (7 rows)
+</screen>
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </sect2>
+
+ <sect2>
+  <title>Hash Functions</title>
+
+  <variablelist>
+   <varlistentry>
+    <term>
+     <function>hash_page_type(page bytea) returns text</function>
+     <indexterm>
+      <primary>hash_page_type</primary>
+     </indexterm>
+    </term>
+
+    <listitem>
+     <para>
+      <function>hash_page_type</function> returns page type of
+      the given <acronym>HASH</acronym> index page.  For example:
+<screen>
+test=# SELECT hash_page_type(get_raw_page('con_hash_index', 0));
+ hash_page_type 
+----------------
+ metapage
+</screen>
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term>
+     <function>hash_page_stats(page bytea) returns setof record</function>
+     <indexterm>
+      <primary>hash_page_stats</primary>
+     </indexterm>
+    </term>
+
+    <listitem>
+     <para>
+      <function>hash_page_stats</function> returns information about
+      a bucket or overflow page of a <acronym>HASH</acronym> index.
+      For example:
+<screen>
+test=# SELECT * FROM hash_page_stats(get_raw_page('con_hash_index', 1));
+-[ RECORD 1 ]---+-----------
+live_items      | 407
+dead_items      | 0
+page_size       | 8192
+free_size       | 8
+hasho_prevblkno | 4294967295
+hasho_nextblkno | 8474
+hasho_bucket    | 0
+hasho_flag      | 66
+hasho_page_id   | 65408
+</screen>
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term>
+     <function>hash_page_items(page bytea) returns setof record</function>
+     <indexterm>
+      <primary>hash_page_items</primary>
+     </indexterm>
+    </term>
+
+    <listitem>
+     <para>
+      <function>hash_page_items</function> returns information about
+      the data stored in a bucket or overflow page of a <acronym>HASH</acronym>
+      index page.  For example:
+<screen>
+test=# SELECT * FROM hash_page_items(get_raw_page('con_hash_index', 1)) LIMIT 5;
+ itemoffset |   ctid    |    data    
+------------+-----------+------------
+          1 | (899,77)  | 1053474816
+          2 | (897,29)  | 1053474816
+          3 | (894,207) | 1053474816
+          4 | (892,159) | 1053474816
+          5 | (890,111) | 1053474816
+</screen>
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term>
+     <function>hash_bitmap_info(index oid, blkno int) returns record</function>
+     <indexterm>
+      <primary>hash_bitmap_info</primary>
+     </indexterm>
+    </term>
+
+    <listitem>
+     <para>
+      <function>hash_bitmap_info</function> shows the status of a bit
+      in the bitmap page for a particular overflow page of <acronym>HASH</acronym>
+      index. For example:
+<screen>
+test=# SELECT * FROM hash_bitmap_info('con_hash_index', 2052);
+ bitmapblkno | bitmapbit | bitstatus 
+-------------+-----------+-----------
+          65 |         3 | t
+</screen>
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term>
+     <function>hash_metapage_info(page bytea) returns record</function>
+     <indexterm>
+      <primary>hash_metapage_info</primary>
+     </indexterm>
+    </term>
+
+    <listitem>
+     <para>
+      <function>hash_metapage_info</function> returns information stored
+      in meta page of a <acronym>HASH</acronym> index.  For example:
+<screen>
+test=# SELECT * FROM hash_metapage_info(get_raw_page('con_hash_index', 0));
+-[ RECORD 1 ]-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+magic     | 105121344
+version   | 2
+ntuples   | 500500
+ffactor   | 40
+bsize     | 8152
+bmsize    | 4096
+bmshift   | 15
+maxbucket | 12512
+highmask  | 16383
+lowmask   | 8191
+ovflpoint | 14
+firstfree | 1204
+nmaps     | 1
+procid    | 450
+spares    | {0,0,0,0,0,0,1,1,1,1,1,4,59,704,1204,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+mapp      | {65,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
 </screen>
      </para>
     </listitem>
diff --git a/src/backend/access/hash/hashovfl.c b/src/backend/access/hash/hashovfl.c
index e8928efc1aae1b92eafa8f251abac2e109f3fad9..753c8a6a134166fa5bf148f912a15e5759c27268 100644
--- a/src/backend/access/hash/hashovfl.c
+++ b/src/backend/access/hash/hashovfl.c
@@ -52,10 +52,12 @@ bitno_to_blkno(HashMetaPage metap, uint32 ovflbitnum)
 }
 
 /*
+ * _hash_ovflblkno_to_bitno
+ *
  * Convert overflow page block number to bit number for free-page bitmap.
  */
-static uint32
-blkno_to_bitno(HashMetaPage metap, BlockNumber ovflblkno)
+uint32
+_hash_ovflblkno_to_bitno(HashMetaPage metap, BlockNumber ovflblkno)
 {
 	uint32		splitnum = metap->hashm_ovflpoint;
 	uint32		i;
@@ -485,7 +487,7 @@ _hash_freeovflpage(Relation rel, Buffer ovflbuf, Buffer wbuf,
 	metap = HashPageGetMeta(BufferGetPage(metabuf));
 
 	/* Identify which bit to set */
-	ovflbitno = blkno_to_bitno(metap, ovflblkno);
+	ovflbitno = _hash_ovflblkno_to_bitno(metap, ovflblkno);
 
 	bitmappage = ovflbitno >> BMPG_SHIFT(metap);
 	bitmapbit = ovflbitno & BMPG_MASK(metap);
diff --git a/src/include/access/hash.h b/src/include/access/hash.h
index 69a3873facec3fb167d93fffaf470b6d8be4bd1e..1a9b91f9f53dc64db64e2c39d1ea51c684be763f 100644
--- a/src/include/access/hash.h
+++ b/src/include/access/hash.h
@@ -58,6 +58,9 @@ typedef uint32 Bucket;
 #define LH_BUCKET_BEING_SPLIT	(1 << 5)
 #define LH_BUCKET_NEEDS_SPLIT_CLEANUP	(1 << 6)
 
+#define LH_PAGE_TYPE \
+	(LH_OVERFLOW_PAGE|LH_BUCKET_PAGE|LH_BITMAP_PAGE|LH_META_PAGE)
+
 typedef struct HashPageOpaqueData
 {
 	BlockNumber hasho_prevblkno;	/* previous ovfl (or bucket) blkno */
@@ -299,6 +302,7 @@ extern void _hash_squeezebucket(Relation rel,
 					Bucket bucket, BlockNumber bucket_blkno,
 					Buffer bucket_buf,
 					BufferAccessStrategy bstrategy);
+extern uint32 _hash_ovflblkno_to_bitno(HashMetaPage metap, BlockNumber ovflblkno);
 
 /* hashpage.c */
 extern Buffer _hash_getbuf(Relation rel, BlockNumber blkno,