pageinspect: Hash index support
Hi,
Attached is a patch that adds support for hash indexes in pageinspect.
The functionality (hash_metap, hash_page_stats and hash_page_items)
follows the B-tree functions.
This patch will need an update once Amit's and Mithun's work on
Concurrent Hash Indexes is committed to account for the new meta-page
constants.
I'll create a CommitFest entry for this submission.
Feedback is most welcome.
Best regards,
Jesper
Attachments:
0001-pageinspect-Hash-index-support_v1.patchtext/x-patch; name=0001-pageinspect-Hash-index-support_v1.patchDownload
From 55262d5fa3822afbae94f4989627dd65e91fe098 Mon Sep 17 00:00:00 2001
From: jesperpedersen <jesper.pedersen@redhat.com>
Date: Fri, 5 Aug 2016 10:16:32 -0400
Subject: [PATCH] pageinspect: Hash index support
---
contrib/pageinspect/Makefile | 10 +-
contrib/pageinspect/hashfuncs.c | 472 ++++++++++++++++++++++++++
contrib/pageinspect/pageinspect--1.5--1.6.sql | 64 ++++
contrib/pageinspect/pageinspect--1.5.sql | 279 ---------------
contrib/pageinspect/pageinspect--1.6.sql | 339 ++++++++++++++++++
contrib/pageinspect/pageinspect.control | 2 +-
doc/src/sgml/pageinspect.sgml | 100 ++++++
7 files changed, 981 insertions(+), 285 deletions(-)
create mode 100644 contrib/pageinspect/hashfuncs.c
create mode 100644 contrib/pageinspect/pageinspect--1.5--1.6.sql
delete mode 100644 contrib/pageinspect/pageinspect--1.5.sql
create mode 100644 contrib/pageinspect/pageinspect--1.6.sql
diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index a98237e..c73d728 100644
--- a/contrib/pageinspect/Makefile
+++ b/contrib/pageinspect/Makefile
@@ -2,13 +2,13 @@
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.6.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"
ifdef USE_PGXS
diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c
new file mode 100644
index 0000000..598b7b2
--- /dev/null
+++ b/contrib/pageinspect/hashfuncs.c
@@ -0,0 +1,472 @@
+/*
+ * hashfuncs.c
+ * Functions to investigate the content of HASH indexes
+ *
+ * Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * contrib/pageinspect/hashfuncs.c
+ */
+
+#include "postgres.h"
+
+#include "access/hash.h"
+#include "access/htup_details.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_am.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+#include "utils/rel.h"
+
+
+PG_FUNCTION_INFO_V1(hash_metap);
+PG_FUNCTION_INFO_V1(hash_page_items);
+PG_FUNCTION_INFO_V1(hash_page_stats);
+
+#define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX)
+#define IS_HASH(r) ((r)->rd_rel->relam == HASH_AM_OID)
+
+/* note: BlockNumber is unsigned, hence can't be negative */
+#define CHECK_RELATION_BLOCK_RANGE(rel, blkno) { \
+ if ( RelationGetNumberOfBlocks(rel) <= (BlockNumber) (blkno) ) \
+ elog(ERROR, "block number out of range"); }
+
+/* ------------------------------------------------
+ * structure for single hash page statistics
+ * ------------------------------------------------
+ */
+typedef struct HashPageStat
+{
+ uint32 blkno;
+ uint32 live_items;
+ uint32 dead_items;
+ uint32 page_size;
+ uint32 max_avail;
+ uint32 free_size;
+ uint32 avg_item_size;
+ char type;
+
+ /* opaque data */
+ BlockNumber hasho_prevblkno;
+ BlockNumber hasho_nextblkno;
+ Bucket hasho_bucket;
+ uint16 hasho_flag;
+ uint16 hasho_page_id;
+} HashPageStat;
+
+
+/* -------------------------------------------------
+ * GetHashPageStatistics()
+ *
+ * Collect statistics of single hash page
+ * -------------------------------------------------
+ */
+static void
+GetHashPageStatistics(BlockNumber blkno, Buffer buffer, HashPageStat *stat)
+{
+ Page page = BufferGetPage(buffer);
+ PageHeader phdr = (PageHeader) page;
+ OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
+ HashPageOpaque opaque = (HashPageOpaque) PageGetSpecialPointer(page);
+ int item_size = 0;
+ int off;
+
+ stat->blkno = blkno;
+
+ stat->max_avail = BLCKSZ - (BLCKSZ - phdr->pd_special + SizeOfPageHeaderData);
+
+ stat->dead_items = stat->live_items = 0;
+
+ stat->page_size = PageGetPageSize(page);
+
+ /* page type (flags) */
+ if (opaque->hasho_flag & LH_UNUSED_PAGE)
+ stat->type = 'u';
+ else if (opaque->hasho_flag & LH_OVERFLOW_PAGE)
+ stat->type = 'v';
+ else if (opaque->hasho_flag & LH_BUCKET_PAGE)
+ stat->type = 'b';
+ else if (opaque->hasho_flag & LH_BITMAP_PAGE)
+ stat->type = 'i';
+ else
+ stat->type = 'm';
+
+ /* 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++)
+ {
+ IndexTuple itup;
+
+ ItemId id = PageGetItemId(page, off);
+
+ itup = (IndexTuple) PageGetItem(page, id);
+
+ item_size += IndexTupleSize(itup);
+
+ if (!ItemIdIsDead(id))
+ stat->live_items++;
+ else
+ stat->dead_items++;
+ }
+ stat->free_size = PageGetFreeSpace(page);
+
+ if ((stat->live_items + stat->dead_items) > 0)
+ stat->avg_item_size = item_size / (stat->live_items + stat->dead_items);
+ else
+ stat->avg_item_size = 0;
+}
+
+/* ---------------------------------------------------
+ * hash_page_stats()
+ *
+ * Usage: SELECT * FROM hash_page_stats('t1_hash', 1);
+ * ---------------------------------------------------
+ */
+Datum
+hash_page_stats(PG_FUNCTION_ARGS)
+{
+ text *relname = PG_GETARG_TEXT_P(0);
+ uint32 blkno = PG_GETARG_UINT32(1);
+ Buffer buffer;
+ Relation rel;
+ RangeVar *relrv;
+ Datum result;
+ HeapTuple tuple;
+ TupleDesc tupleDesc;
+ int j;
+ char *values[12];
+ HashPageStat stat;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
+ rel = relation_openrv(relrv, AccessShareLock);
+
+ if (!IS_INDEX(rel) || !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 tables of other sessions")));
+
+ if (blkno == 0)
+ elog(ERROR, "block 0 is a meta page");
+
+ CHECK_RELATION_BLOCK_RANGE(rel, blkno);
+
+ buffer = ReadBuffer(rel, blkno);
+ LockBuffer(buffer, BUFFER_LOCK_SHARE);
+
+ /* keep compiler quiet */
+ stat.hasho_prevblkno = stat.hasho_nextblkno = InvalidBlockNumber;
+ stat.hasho_flag = stat.hasho_page_id = stat.free_size = stat.avg_item_size = 0;
+
+ GetHashPageStatistics(blkno, buffer, &stat);
+
+ UnlockReleaseBuffer(buffer);
+ relation_close(rel, 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");
+
+ j = 0;
+ values[j++] = psprintf("%d", stat.blkno);
+ values[j++] = psprintf("%c", stat.type);
+ values[j++] = psprintf("%d", stat.live_items);
+ values[j++] = psprintf("%d", stat.dead_items);
+ values[j++] = psprintf("%d", stat.avg_item_size);
+ values[j++] = psprintf("%d", stat.page_size);
+ values[j++] = psprintf("%d", stat.free_size);
+ values[j++] = psprintf("%d", stat.hasho_prevblkno);
+ values[j++] = psprintf("%d", stat.hasho_nextblkno);
+ values[j++] = psprintf("%d", stat.hasho_bucket);
+ values[j++] = psprintf("%d", stat.hasho_flag);
+ values[j++] = psprintf("%d", stat.hasho_page_id);
+
+ tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
+ values);
+
+ result = HeapTupleGetDatum(tuple);
+
+ PG_RETURN_DATUM(result);
+}
+
+/*-------------------------------------------------------
+ * hash_page_items()
+ *
+ * Get IndexTupleData set in a hash page
+ *
+ * Usage: SELECT * FROM hash_page_items('t1_hash', 1);
+ *-------------------------------------------------------
+ */
+
+/*
+ * cross-call data structure for SRF
+ */
+struct user_args
+{
+ Page page;
+ OffsetNumber offset;
+};
+
+Datum
+hash_page_items(PG_FUNCTION_ARGS)
+{
+ text *relname = PG_GETARG_TEXT_P(0);
+ uint32 blkno = PG_GETARG_UINT32(1);
+ Datum result;
+ char *values[6];
+ HeapTuple tuple;
+ FuncCallContext *fctx;
+ MemoryContext mctx;
+ struct user_args *uargs;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ RangeVar *relrv;
+ Relation rel;
+ Buffer buffer;
+ TupleDesc tupleDesc;
+
+ fctx = SRF_FIRSTCALL_INIT();
+
+ relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
+ rel = relation_openrv(relrv, AccessShareLock);
+
+ if (!IS_INDEX(rel) || !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 tables of other sessions")));
+
+ if (blkno == 0)
+ elog(ERROR, "block 0 is a meta page");
+
+ CHECK_RELATION_BLOCK_RANGE(rel, blkno);
+
+ buffer = ReadBuffer(rel, blkno);
+ LockBuffer(buffer, BUFFER_LOCK_SHARE);
+
+ /*
+ * We copy the page into local storage to avoid holding pin on the
+ * buffer longer than we must, and possibly failing to release it at
+ * all if the calling query doesn't fetch all rows.
+ */
+ mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
+
+ uargs = palloc(sizeof(struct user_args));
+
+ uargs->page = palloc(BLCKSZ);
+ memcpy(uargs->page, BufferGetPage(buffer), BLCKSZ);
+
+ UnlockReleaseBuffer(buffer);
+ relation_close(rel, AccessShareLock);
+
+ 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");
+
+ 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;
+ int off;
+ int dlen;
+ char *dump;
+ char *ptr;
+
+ id = PageGetItemId(uargs->page, uargs->offset);
+
+ if (!ItemIdIsValid(id))
+ elog(ERROR, "invalid ItemId");
+
+ itup = (IndexTuple) PageGetItem(uargs->page, id);
+
+ j = 0;
+ values[j++] = psprintf("%d", uargs->offset);
+ values[j++] = psprintf("(%u,%u)",
+ BlockIdGetBlockNumber(&(itup->t_tid.ip_blkid)),
+ itup->t_tid.ip_posid);
+ values[j++] = psprintf("%d", (int) IndexTupleSize(itup));
+ values[j++] = psprintf("%c", IndexTupleHasNulls(itup) ? 't' : 'f');
+ values[j++] = psprintf("%c", IndexTupleHasVarwidths(itup) ? 't' : 'f');
+
+ ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
+ dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info);
+ dump = palloc0(dlen * 3 + 1);
+ values[j] = dump;
+ for (off = 0; off < dlen; off++)
+ {
+ if (off > 0)
+ *dump++ = ' ';
+ sprintf(dump, "%02x", *(ptr + off) & 0xff);
+ dump += 2;
+ }
+
+ tuple = BuildTupleFromCStrings(fctx->attinmeta, values);
+ result = HeapTupleGetDatum(tuple);
+
+ uargs->offset = uargs->offset + 1;
+
+ SRF_RETURN_NEXT(fctx, result);
+ }
+ else
+ {
+ pfree(uargs->page);
+ pfree(uargs);
+ SRF_RETURN_DONE(fctx);
+ }
+}
+
+
+/* ------------------------------------------------
+ * hash_metap()
+ *
+ * Get a hash's meta-page information
+ *
+ * Usage: SELECT * FROM hash_metap('t1_hash')
+ * ------------------------------------------------
+ */
+Datum
+hash_metap(PG_FUNCTION_ARGS)
+{
+ text *relname = PG_GETARG_TEXT_P(0);
+ Datum result;
+ Relation rel;
+ RangeVar *relrv;
+ HashMetaPageData *metad;
+ TupleDesc tupleDesc;
+ int i,j;
+ char *values[16];
+ char *spares;
+ char *mapp;
+ Buffer buffer;
+ Page page;
+ HeapTuple tuple;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
+ rel = relation_openrv(relrv, AccessShareLock);
+
+ if (!IS_INDEX(rel) || !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 tables of other sessions")));
+
+ buffer = ReadBuffer(rel, 0);
+ LockBuffer(buffer, BUFFER_LOCK_SHARE);
+
+ page = BufferGetPage(buffer);
+ metad = HashPageGetMeta(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");
+
+ j = 0;
+ values[j++] = psprintf("%d", metad->hashm_magic);
+ values[j++] = psprintf("%d", metad->hashm_version);
+ values[j++] = psprintf("%f", metad->hashm_ntuples);
+ values[j++] = psprintf("%d", metad->hashm_ffactor);
+ values[j++] = psprintf("%d", metad->hashm_bsize);
+ values[j++] = psprintf("%d", metad->hashm_bmsize);
+ values[j++] = psprintf("%d", metad->hashm_bmshift);
+ values[j++] = psprintf("%d", metad->hashm_maxbucket);
+ values[j++] = psprintf("%d", metad->hashm_highmask);
+ values[j++] = psprintf("%d", metad->hashm_lowmask);
+ values[j++] = psprintf("%d", metad->hashm_ovflpoint);
+ values[j++] = psprintf("%d", metad->hashm_firstfree);
+ values[j++] = psprintf("%d", metad->hashm_nmaps);
+ values[j++] = psprintf("%d", metad->hashm_procid);
+
+ spares = palloc0(HASH_MAX_SPLITPOINTS * 5 + 1);
+ values[j++] = spares;
+ for (i = 0; i < HASH_MAX_SPLITPOINTS; i++)
+ {
+ if (i > 0)
+ *spares++ = ' ';
+
+ sprintf(spares, "%04x", metad->hashm_spares[i]);
+ spares += 4;
+ }
+
+ mapp = palloc0(HASH_MAX_BITMAPS * 5 + 1);
+ values[j++] = mapp;
+ for (i = 0; i < HASH_MAX_BITMAPS; i++)
+ {
+ if (i > 0)
+ *mapp++ = ' ';
+
+ sprintf(mapp, "%04x", metad->hashm_mapp[i]);
+ mapp += 4;
+ }
+
+ tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
+ values);
+
+ result = HeapTupleGetDatum(tuple);
+
+ UnlockReleaseBuffer(buffer);
+ relation_close(rel, AccessShareLock);
+
+ PG_RETURN_DATUM(result);
+}
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 0000000..cb59749
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.5--1.6.sql
@@ -0,0 +1,64 @@
+/* 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_metap()
+--
+CREATE FUNCTION hash_metap(IN relname text,
+ OUT magic int4,
+ OUT version int4,
+ OUT ntuples double precision,
+ OUT ffactor int2,
+ OUT bsize int2,
+ OUT bmsize int2,
+ OUT bmshift int2,
+ OUT maxbucket int4,
+ OUT highmask int4,
+ OUT lowmask int4,
+ OUT ovflpoint int4,
+ OUT firstfree int4,
+ OUT nmaps int4,
+ OUT procid int4,
+ OUT spares text,
+ OUT mapp text)
+AS 'MODULE_PATHNAME', 'hash_metap'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- hash_page_stats()
+--
+CREATE FUNCTION hash_page_stats(IN relname text, IN blkno int4,
+ OUT blkno int4,
+ OUT type "char",
+ OUT live_items int4,
+ OUT dead_items int4,
+ OUT avg_item_size int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT hasho_prevblkno int4,
+ OUT hasho_nextblkno int4,
+ OUT hasho_bucket int4,
+ OUT hasho_flag int4,
+ 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 relname text, IN blkno int4,
+ OUT itemoffset smallint,
+ OUT ctid tid,
+ OUT itemlen smallint,
+ OUT nulls bool,
+ OUT vars bool,
+ OUT data text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'hash_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect--1.5.sql b/contrib/pageinspect/pageinspect--1.5.sql
deleted file mode 100644
index 1e40c3c..0000000
--- a/contrib/pageinspect/pageinspect--1.5.sql
+++ /dev/null
@@ -1,279 +0,0 @@
-/* contrib/pageinspect/pageinspect--1.5.sql */
-
--- complain if script is sourced in psql, rather than via CREATE EXTENSION
-\echo Use "CREATE EXTENSION pageinspect" to load this file. \quit
-
---
--- get_raw_page()
---
-CREATE FUNCTION get_raw_page(text, int4)
-RETURNS bytea
-AS 'MODULE_PATHNAME', 'get_raw_page'
-LANGUAGE C STRICT PARALLEL SAFE;
-
-CREATE FUNCTION get_raw_page(text, text, int4)
-RETURNS bytea
-AS 'MODULE_PATHNAME', 'get_raw_page_fork'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- page_header()
---
-CREATE FUNCTION page_header(IN page bytea,
- OUT lsn pg_lsn,
- OUT checksum smallint,
- OUT flags smallint,
- OUT lower smallint,
- OUT upper smallint,
- OUT special smallint,
- OUT pagesize smallint,
- OUT version smallint,
- OUT prune_xid xid)
-AS 'MODULE_PATHNAME', 'page_header'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- heap_page_items()
---
-CREATE FUNCTION heap_page_items(IN page bytea,
- OUT lp smallint,
- OUT lp_off smallint,
- OUT lp_flags smallint,
- OUT lp_len smallint,
- OUT t_xmin xid,
- OUT t_xmax xid,
- OUT t_field3 int4,
- OUT t_ctid tid,
- OUT t_infomask2 integer,
- OUT t_infomask integer,
- OUT t_hoff smallint,
- OUT t_bits text,
- OUT t_oid oid,
- OUT t_data bytea)
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'heap_page_items'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- tuple_data_split()
---
-CREATE FUNCTION tuple_data_split(rel_oid oid,
- t_data bytea,
- t_infomask integer,
- t_infomask2 integer,
- t_bits text)
-RETURNS bytea[]
-AS 'MODULE_PATHNAME','tuple_data_split'
-LANGUAGE C PARALLEL SAFE;
-
-CREATE FUNCTION tuple_data_split(rel_oid oid,
- t_data bytea,
- t_infomask integer,
- t_infomask2 integer,
- t_bits text,
- do_detoast bool)
-RETURNS bytea[]
-AS 'MODULE_PATHNAME','tuple_data_split'
-LANGUAGE C PARALLEL SAFE;
-
---
--- heap_page_item_attrs()
---
-CREATE FUNCTION heap_page_item_attrs(
- IN page bytea,
- IN rel_oid regclass,
- IN do_detoast bool,
- OUT lp smallint,
- OUT lp_off smallint,
- OUT lp_flags smallint,
- OUT lp_len smallint,
- OUT t_xmin xid,
- OUT t_xmax xid,
- OUT t_field3 int4,
- OUT t_ctid tid,
- OUT t_infomask2 integer,
- OUT t_infomask integer,
- OUT t_hoff smallint,
- OUT t_bits text,
- OUT t_oid oid,
- OUT t_attrs bytea[]
- )
-RETURNS SETOF record AS $$
-SELECT lp,
- lp_off,
- lp_flags,
- lp_len,
- t_xmin,
- t_xmax,
- t_field3,
- t_ctid,
- t_infomask2,
- t_infomask,
- t_hoff,
- t_bits,
- t_oid,
- tuple_data_split(
- rel_oid,
- t_data,
- t_infomask,
- t_infomask2,
- t_bits,
- do_detoast)
- AS t_attrs
- FROM heap_page_items(page);
-$$ LANGUAGE SQL PARALLEL SAFE;
-
-CREATE FUNCTION heap_page_item_attrs(IN page bytea, IN rel_oid regclass,
- OUT lp smallint,
- OUT lp_off smallint,
- OUT lp_flags smallint,
- OUT lp_len smallint,
- OUT t_xmin xid,
- OUT t_xmax xid,
- OUT t_field3 int4,
- OUT t_ctid tid,
- OUT t_infomask2 integer,
- OUT t_infomask integer,
- OUT t_hoff smallint,
- OUT t_bits text,
- OUT t_oid oid,
- OUT t_attrs bytea[]
- )
-RETURNS SETOF record AS $$
-SELECT * from heap_page_item_attrs(page, rel_oid, false);
-$$ LANGUAGE SQL PARALLEL SAFE;
-
---
--- bt_metap()
---
-CREATE FUNCTION bt_metap(IN relname text,
- OUT magic int4,
- OUT version int4,
- OUT root int4,
- OUT level int4,
- OUT fastroot int4,
- OUT fastlevel int4)
-AS 'MODULE_PATHNAME', 'bt_metap'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- bt_page_stats()
---
-CREATE FUNCTION bt_page_stats(IN relname text, IN blkno int4,
- OUT blkno int4,
- OUT type "char",
- OUT live_items int4,
- OUT dead_items int4,
- OUT avg_item_size int4,
- OUT page_size int4,
- OUT free_size int4,
- OUT btpo_prev int4,
- OUT btpo_next int4,
- OUT btpo int4,
- OUT btpo_flags int4)
-AS 'MODULE_PATHNAME', 'bt_page_stats'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- bt_page_items()
---
-CREATE FUNCTION bt_page_items(IN relname text, IN blkno int4,
- OUT itemoffset smallint,
- OUT ctid tid,
- OUT itemlen smallint,
- OUT nulls bool,
- OUT vars bool,
- OUT data text)
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'bt_page_items'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_page_type()
---
-CREATE FUNCTION brin_page_type(IN page bytea)
-RETURNS text
-AS 'MODULE_PATHNAME', 'brin_page_type'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_metapage_info()
---
-CREATE FUNCTION brin_metapage_info(IN page bytea, OUT magic text,
- OUT version integer, OUT pagesperrange integer, OUT lastrevmappage bigint)
-AS 'MODULE_PATHNAME', 'brin_metapage_info'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_revmap_data()
---
-CREATE FUNCTION brin_revmap_data(IN page bytea,
- OUT pages tid)
-RETURNS SETOF tid
-AS 'MODULE_PATHNAME', 'brin_revmap_data'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_page_items()
---
-CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass,
- OUT itemoffset int,
- OUT blknum int,
- OUT attnum int,
- OUT allnulls bool,
- OUT hasnulls bool,
- OUT placeholder bool,
- OUT value text)
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'brin_page_items'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- fsm_page_contents()
---
-CREATE FUNCTION fsm_page_contents(IN page bytea)
-RETURNS text
-AS 'MODULE_PATHNAME', 'fsm_page_contents'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- GIN functions
---
-
---
--- gin_metapage_info()
---
-CREATE FUNCTION gin_metapage_info(IN page bytea,
- OUT pending_head bigint,
- OUT pending_tail bigint,
- OUT tail_free_size int4,
- OUT n_pending_pages bigint,
- OUT n_pending_tuples bigint,
- OUT n_total_pages bigint,
- OUT n_entry_pages bigint,
- OUT n_data_pages bigint,
- OUT n_entries bigint,
- OUT version int4)
-AS 'MODULE_PATHNAME', 'gin_metapage_info'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- gin_page_opaque_info()
---
-CREATE FUNCTION gin_page_opaque_info(IN page bytea,
- OUT rightlink bigint,
- OUT maxoff int4,
- OUT flags text[])
-AS 'MODULE_PATHNAME', 'gin_page_opaque_info'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- gin_leafpage_items()
---
-CREATE FUNCTION gin_leafpage_items(IN page bytea,
- OUT first_tid tid,
- OUT nbytes int2,
- OUT tids tid[])
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'gin_leafpage_items'
-LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect--1.6.sql b/contrib/pageinspect/pageinspect--1.6.sql
new file mode 100644
index 0000000..76833f0
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.6.sql
@@ -0,0 +1,339 @@
+/* contrib/pageinspect/pageinspect--1.6.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION pageinspect" to load this file. \quit
+
+--
+-- get_raw_page()
+--
+CREATE FUNCTION get_raw_page(text, int4)
+RETURNS bytea
+AS 'MODULE_PATHNAME', 'get_raw_page'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+CREATE FUNCTION get_raw_page(text, text, int4)
+RETURNS bytea
+AS 'MODULE_PATHNAME', 'get_raw_page_fork'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- page_header()
+--
+CREATE FUNCTION page_header(IN page bytea,
+ OUT lsn pg_lsn,
+ OUT checksum smallint,
+ OUT flags smallint,
+ OUT lower smallint,
+ OUT upper smallint,
+ OUT special smallint,
+ OUT pagesize smallint,
+ OUT version smallint,
+ OUT prune_xid xid)
+AS 'MODULE_PATHNAME', 'page_header'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- heap_page_items()
+--
+CREATE FUNCTION heap_page_items(IN page bytea,
+ OUT lp smallint,
+ OUT lp_off smallint,
+ OUT lp_flags smallint,
+ OUT lp_len smallint,
+ OUT t_xmin xid,
+ OUT t_xmax xid,
+ OUT t_field3 int4,
+ OUT t_ctid tid,
+ OUT t_infomask2 integer,
+ OUT t_infomask integer,
+ OUT t_hoff smallint,
+ OUT t_bits text,
+ OUT t_oid oid,
+ OUT t_data bytea)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'heap_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- tuple_data_split()
+--
+CREATE FUNCTION tuple_data_split(rel_oid oid,
+ t_data bytea,
+ t_infomask integer,
+ t_infomask2 integer,
+ t_bits text)
+RETURNS bytea[]
+AS 'MODULE_PATHNAME','tuple_data_split'
+LANGUAGE C PARALLEL SAFE;
+
+CREATE FUNCTION tuple_data_split(rel_oid oid,
+ t_data bytea,
+ t_infomask integer,
+ t_infomask2 integer,
+ t_bits text,
+ do_detoast bool)
+RETURNS bytea[]
+AS 'MODULE_PATHNAME','tuple_data_split'
+LANGUAGE C PARALLEL SAFE;
+
+--
+-- heap_page_item_attrs()
+--
+CREATE FUNCTION heap_page_item_attrs(
+ IN page bytea,
+ IN rel_oid regclass,
+ IN do_detoast bool,
+ OUT lp smallint,
+ OUT lp_off smallint,
+ OUT lp_flags smallint,
+ OUT lp_len smallint,
+ OUT t_xmin xid,
+ OUT t_xmax xid,
+ OUT t_field3 int4,
+ OUT t_ctid tid,
+ OUT t_infomask2 integer,
+ OUT t_infomask integer,
+ OUT t_hoff smallint,
+ OUT t_bits text,
+ OUT t_oid oid,
+ OUT t_attrs bytea[]
+ )
+RETURNS SETOF record AS $$
+SELECT lp,
+ lp_off,
+ lp_flags,
+ lp_len,
+ t_xmin,
+ t_xmax,
+ t_field3,
+ t_ctid,
+ t_infomask2,
+ t_infomask,
+ t_hoff,
+ t_bits,
+ t_oid,
+ tuple_data_split(
+ rel_oid,
+ t_data,
+ t_infomask,
+ t_infomask2,
+ t_bits,
+ do_detoast)
+ AS t_attrs
+ FROM heap_page_items(page);
+$$ LANGUAGE SQL PARALLEL SAFE;
+
+CREATE FUNCTION heap_page_item_attrs(IN page bytea, IN rel_oid regclass,
+ OUT lp smallint,
+ OUT lp_off smallint,
+ OUT lp_flags smallint,
+ OUT lp_len smallint,
+ OUT t_xmin xid,
+ OUT t_xmax xid,
+ OUT t_field3 int4,
+ OUT t_ctid tid,
+ OUT t_infomask2 integer,
+ OUT t_infomask integer,
+ OUT t_hoff smallint,
+ OUT t_bits text,
+ OUT t_oid oid,
+ OUT t_attrs bytea[]
+ )
+RETURNS SETOF record AS $$
+SELECT * from heap_page_item_attrs(page, rel_oid, false);
+$$ LANGUAGE SQL PARALLEL SAFE;
+
+--
+-- bt_metap()
+--
+CREATE FUNCTION bt_metap(IN relname text,
+ OUT magic int4,
+ OUT version int4,
+ OUT root int4,
+ OUT level int4,
+ OUT fastroot int4,
+ OUT fastlevel int4)
+AS 'MODULE_PATHNAME', 'bt_metap'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- bt_page_stats()
+--
+CREATE FUNCTION bt_page_stats(IN relname text, IN blkno int4,
+ OUT blkno int4,
+ OUT type "char",
+ OUT live_items int4,
+ OUT dead_items int4,
+ OUT avg_item_size int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT btpo_prev int4,
+ OUT btpo_next int4,
+ OUT btpo int4,
+ OUT btpo_flags int4)
+AS 'MODULE_PATHNAME', 'bt_page_stats'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- bt_page_items()
+--
+CREATE FUNCTION bt_page_items(IN relname text, IN blkno int4,
+ OUT itemoffset smallint,
+ OUT ctid tid,
+ OUT itemlen smallint,
+ OUT nulls bool,
+ OUT vars bool,
+ OUT data text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'bt_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_page_type()
+--
+CREATE FUNCTION brin_page_type(IN page bytea)
+RETURNS text
+AS 'MODULE_PATHNAME', 'brin_page_type'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_metapage_info()
+--
+CREATE FUNCTION brin_metapage_info(IN page bytea, OUT magic text,
+ OUT version integer, OUT pagesperrange integer, OUT lastrevmappage bigint)
+AS 'MODULE_PATHNAME', 'brin_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_revmap_data()
+--
+CREATE FUNCTION brin_revmap_data(IN page bytea,
+ OUT pages tid)
+RETURNS SETOF tid
+AS 'MODULE_PATHNAME', 'brin_revmap_data'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_page_items()
+--
+CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass,
+ OUT itemoffset int,
+ OUT blknum int,
+ OUT attnum int,
+ OUT allnulls bool,
+ OUT hasnulls bool,
+ OUT placeholder bool,
+ OUT value text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'brin_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- fsm_page_contents()
+--
+CREATE FUNCTION fsm_page_contents(IN page bytea)
+RETURNS text
+AS 'MODULE_PATHNAME', 'fsm_page_contents'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- GIN functions
+--
+
+--
+-- gin_metapage_info()
+--
+CREATE FUNCTION gin_metapage_info(IN page bytea,
+ OUT pending_head bigint,
+ OUT pending_tail bigint,
+ OUT tail_free_size int4,
+ OUT n_pending_pages bigint,
+ OUT n_pending_tuples bigint,
+ OUT n_total_pages bigint,
+ OUT n_entry_pages bigint,
+ OUT n_data_pages bigint,
+ OUT n_entries bigint,
+ OUT version int4)
+AS 'MODULE_PATHNAME', 'gin_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- gin_page_opaque_info()
+--
+CREATE FUNCTION gin_page_opaque_info(IN page bytea,
+ OUT rightlink bigint,
+ OUT maxoff int4,
+ OUT flags text[])
+AS 'MODULE_PATHNAME', 'gin_page_opaque_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- gin_leafpage_items()
+--
+CREATE FUNCTION gin_leafpage_items(IN page bytea,
+ OUT first_tid tid,
+ OUT nbytes int2,
+ OUT tids tid[])
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'gin_leafpage_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- HASH functions
+--
+
+--
+-- hash_metap()
+--
+CREATE FUNCTION hash_metap(IN relname text,
+ OUT magic int4,
+ OUT version int4,
+ OUT ntuples double precision,
+ OUT ffactor int2,
+ OUT bsize int2,
+ OUT bmsize int2,
+ OUT bmshift int2,
+ OUT maxbucket int4,
+ OUT highmask int4,
+ OUT lowmask int4,
+ OUT ovflpoint int4,
+ OUT firstfree int4,
+ OUT nmaps int4,
+ OUT procid int4,
+ OUT spares text,
+ OUT mapp text)
+AS 'MODULE_PATHNAME', 'hash_metap'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- hash_page_stats()
+--
+CREATE FUNCTION hash_page_stats(IN relname text, IN blkno int4,
+ OUT blkno int4,
+ OUT type "char",
+ OUT live_items int4,
+ OUT dead_items int4,
+ OUT avg_item_size int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT hasho_prevblkno int4,
+ OUT hasho_nextblkno int4,
+ OUT hasho_bucket int4,
+ OUT hasho_flag int4,
+ 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 relname text, IN blkno int4,
+ OUT itemoffset smallint,
+ OUT ctid tid,
+ OUT itemlen smallint,
+ OUT nulls bool,
+ OUT vars bool,
+ OUT data text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'hash_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control
index 23c8eff..1a61c9f 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/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml
index 5d187ed..836b19e 100644
--- a/doc/src/sgml/pageinspect.sgml
+++ b/doc/src/sgml/pageinspect.sgml
@@ -452,6 +452,106 @@ test=# SELECT first_tid, nbytes, tids[0:5] as some_tids
<varlistentry>
<term>
+ <function>hash_metap(relname text) returns record</function>
+ <indexterm>
+ <primary>hash_metap</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+ <para>
+ <function>hash_metap</function> returns information about a hash
+ index's metapage. For example:
+<screen>
+test=# SELECT * FROM hash_metap('mytab_index');
+-[ RECORD 1 ]-----
+magic | 105121344
+version | 2
+ntuples | 1000000
+ffactor | 307
+bsize | 8152
+bmsize | 4096
+bmshift | 15
+maxbucket | 4095
+highmask | 8191
+lowmask | 4095
+ovflpoint | 12
+firstfree | 0
+nmaps | 1
+procid | 400
+spares | 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 0000 ...
+mapp | 1001 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 ...
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <function>hash_page_stats(relname text, blkno int) returns record</function>
+ <indexterm>
+ <primary>hash_page_stats</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+ <para>
+ <function>hash_page_stats</function> returns summary information about
+ single pages of hash indexes. For example:
+<screen>
+test=# SELECT * FROM hash_page_stats('mytab_index', 1);
+-[ RECORD 1 ]-+-----
+blkno | 1
+type | b
+live_items | 211
+dead_items | 0
+avg_item_size | 16
+page_size | 8192
+free_size | 3928
+hasho_prevblkno | -1
+hasho_nextblkno | -1
+hasho_bucket | 0
+hasho_flag | 2
+hasho_page_id | 65408
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <function>hash_page_items(relname text, blkno int) returns setof record</function>
+ <indexterm>
+ <primary>hash_page_items</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+ <para>
+ <function>hash_page_items</function> returns detailed information about
+ all of the items on a hash index page. For example:
+<screen>
+test=# SELECT * FROM hash_page_items('mytab_index', 1);
+ itemoffset | ctid | itemlen | nulls | vars | data
+------------+------------+---------+-------+------+-------------------------
+ 1 | (2057,114) | 16 | f | f | 00 a0 25 02 00 00 00 00
+ 2 | (4033,38) | 16 | f | f | 00 20 8d 02 00 00 00 00
+ 3 | (4223,130) | 16 | f | f | 00 80 f7 02 00 00 00 00
+ 4 | (1524,126) | 16 | f | f | 00 70 de 04 00 00 00 00
+ 5 | (5850,86) | 16 | f | f | 00 70 1a 07 00 00 00 00
+...
+ 210 | (5541,140) | 16 | f | f | 00 90 a9 ff 00 00 00 00
+ 211 | (4654,93) | 16 | f | f | 00 d0 fb ff 00 00 00 00
+
+</screen>
+ The <structfield>ctid</> column points to a heap tuple
+ (BlockIdData,OffsetNumber).
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
<function>fsm_page_contents(page bytea) returns text</function>
<indexterm>
<primary>fsm_page_contents</primary>
--
2.7.4
Jesper Pedersen wrote:
Hi,
Attached is a patch that adds support for hash indexes in pageinspect.
The functionality (hash_metap, hash_page_stats and hash_page_items) follows
the B-tree functions.
I suggest that pageinspect functions are more convenient to use via the
get_raw_page interface, that is, instead of reading the buffer
themselves, the buffer is handed over from elsewhere and they receive it
as bytea. This enables other use cases such as grabbing a page from one
server and examining it in another one.
--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Aug 31, 2016 at 2:06 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:
Jesper Pedersen wrote:
Attached is a patch that adds support for hash indexes in pageinspect.
The functionality (hash_metap, hash_page_stats and hash_page_items) follows
the B-tree functions.I suggest that pageinspect functions are more convenient to use via the
get_raw_page interface, that is, instead of reading the buffer
themselves, the buffer is handed over from elsewhere and they receive it
as bytea. This enables other use cases such as grabbing a page from one
server and examining it in another one.
+1.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Aug 30, 2016 at 10:06 AM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:
Jesper Pedersen wrote:
Hi,
Attached is a patch that adds support for hash indexes in pageinspect.
The functionality (hash_metap, hash_page_stats and hash_page_items)
follows
the B-tree functions.
I suggest that pageinspect functions are more convenient to use via the
get_raw_page interface, that is, instead of reading the buffer
themselves, the buffer is handed over from elsewhere and they receive it
as bytea. This enables other use cases such as grabbing a page from one
server and examining it in another one.
I've never needed to do that, but it does sound like it might be useful.
But it is also annoying to have to do that when you want to examine a
current server. Could we use overloading, so that it can be used in either
way?
For hash_page_items, the 'data' is always a 32 bit integer, isn't it?
(unlike other pageinspect features, where the data could be any
user-defined data type). If so, I'd rather see it in plain hex (without
the spaces, without the trailing zeros)
+ /* page type (flags) */
+ if (opaque->hasho_flag & LH_UNUSED_PAGE)
+ stat->type = 'u';
This can never be true, because LH_UNUSED_PAGE is zero. You should make
this be the fall-through case, and have LH_META_PAGE be explicitly tested
for.
Cheers,
Jeff
On 09/14/2016 04:21 PM, Jeff Janes wrote:
I suggest that pageinspect functions are more convenient to use via the
get_raw_page interface, that is, instead of reading the buffer
themselves, the buffer is handed over from elsewhere and they receive it
as bytea. This enables other use cases such as grabbing a page from one
server and examining it in another one.I've never needed to do that, but it does sound like it might be useful.
But it is also annoying to have to do that when you want to examine a
current server. Could we use overloading, so that it can be used in either
way?For hash_page_items, the 'data' is always a 32 bit integer, isn't it?
(unlike other pageinspect features, where the data could be any
user-defined data type). If so, I'd rather see it in plain hex (without
the spaces, without the trailing zeros)+ /* page type (flags) */ + if (opaque->hasho_flag & LH_UNUSED_PAGE) + stat->type = 'u';This can never be true, because LH_UNUSED_PAGE is zero. You should make
this be the fall-through case, and have LH_META_PAGE be explicitly tested
for.
This version adds the overloaded get_raw_page based methods. However,
I'm not verifying the page other than page_id, as hasho_flag can contain
multiple flags in Amit's patches. And, I don't see having different page
ids as a benefit - at least not part of this patch.
We should probably just have one of the method sets, so I'll send a v3
with the set voted for.
I kept the 'data' field as is, for now.
Best regards,
Jesper
Attachments:
0001-pageinspect-Hash-index-support_v2.patchtext/x-patch; name=0001-pageinspect-Hash-index-support_v2.patchDownload
From 0dc44e4b3cc1d31c53684a41fbd7959978e69089 Mon Sep 17 00:00:00 2001
From: jesperpedersen <jesper.pedersen@redhat.com>
Date: Fri, 5 Aug 2016 10:16:32 -0400
Subject: [PATCH] pageinspect: Hash index support
---
contrib/pageinspect/Makefile | 10 +-
contrib/pageinspect/hashfuncs.c | 766 ++++++++++++++++++++++++++
contrib/pageinspect/pageinspect--1.5--1.6.sql | 111 ++++
contrib/pageinspect/pageinspect--1.5.sql | 279 ----------
contrib/pageinspect/pageinspect--1.6.sql | 386 +++++++++++++
contrib/pageinspect/pageinspect.control | 2 +-
doc/src/sgml/pageinspect.sgml | 103 ++++
7 files changed, 1372 insertions(+), 285 deletions(-)
create mode 100644 contrib/pageinspect/hashfuncs.c
create mode 100644 contrib/pageinspect/pageinspect--1.5--1.6.sql
delete mode 100644 contrib/pageinspect/pageinspect--1.5.sql
create mode 100644 contrib/pageinspect/pageinspect--1.6.sql
diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index a98237e..c73d728 100644
--- a/contrib/pageinspect/Makefile
+++ b/contrib/pageinspect/Makefile
@@ -2,13 +2,13 @@
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.6.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"
ifdef USE_PGXS
diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c
new file mode 100644
index 0000000..380203c
--- /dev/null
+++ b/contrib/pageinspect/hashfuncs.c
@@ -0,0 +1,766 @@
+/*
+ * hashfuncs.c
+ * Functions to investigate the content of HASH indexes
+ *
+ * Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * contrib/pageinspect/hashfuncs.c
+ */
+
+#include "postgres.h"
+
+#include "access/hash.h"
+#include "access/htup_details.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_am.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+#include "utils/rel.h"
+
+
+PG_FUNCTION_INFO_V1(hash_metap_bytea);
+PG_FUNCTION_INFO_V1(hash_metap);
+PG_FUNCTION_INFO_V1(hash_page_items_bytea);
+PG_FUNCTION_INFO_V1(hash_page_items);
+PG_FUNCTION_INFO_V1(hash_page_stats_bytea);
+PG_FUNCTION_INFO_V1(hash_page_stats);
+
+#define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX)
+#define IS_HASH(r) ((r)->rd_rel->relam == HASH_AM_OID)
+
+/* note: BlockNumber is unsigned, hence can't be negative */
+#define CHECK_RELATION_BLOCK_RANGE(rel, blkno) { \
+ if ( RelationGetNumberOfBlocks(rel) <= (BlockNumber) (blkno) ) \
+ elog(ERROR, "block number out of range"); }
+
+/* ------------------------------------------------
+ * structure for single hash page statistics
+ * ------------------------------------------------
+ */
+typedef struct HashPageStat
+{
+ uint32 blkno;
+ uint32 live_items;
+ uint32 dead_items;
+ uint32 page_size;
+ uint32 max_avail;
+ uint32 free_size;
+ uint32 avg_item_size;
+ char type;
+
+ /* 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)
+{
+ Page page;
+ int raw_page_size;
+ HashPageOpaque pageopaque;
+
+ raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
+
+ if (raw_page_size != BLCKSZ)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("input page too small"),
+ errdetail("Expected size %d, got %d",
+ BLCKSZ, raw_page_size)));
+
+ page = VARDATA(raw_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)));
+
+ return page;
+}
+
+/* -------------------------------------------------
+ * GetHashPageStatistics()
+ *
+ * Collect statistics of single hash page
+ * -------------------------------------------------
+ */
+static void
+GetHashPageStatistics(BlockNumber blkno, Page page, HashPageStat *stat)
+{
+ PageHeader phdr = (PageHeader) page;
+ OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
+ HashPageOpaque opaque = (HashPageOpaque) PageGetSpecialPointer(page);
+ int item_size = 0;
+ int off;
+
+ stat->blkno = blkno;
+
+ stat->max_avail = BLCKSZ - (BLCKSZ - phdr->pd_special + SizeOfPageHeaderData);
+
+ stat->dead_items = stat->live_items = 0;
+
+ stat->page_size = PageGetPageSize(page);
+
+ /* page type (flags) */
+ if (opaque->hasho_flag & LH_META_PAGE)
+ stat->type = 'm';
+ else if (opaque->hasho_flag & LH_OVERFLOW_PAGE)
+ stat->type = 'v';
+ else if (opaque->hasho_flag & LH_BUCKET_PAGE)
+ stat->type = 'b';
+ else if (opaque->hasho_flag & LH_BITMAP_PAGE)
+ stat->type = 'i';
+ else
+ stat->type = 'u';
+
+ /* 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++)
+ {
+ IndexTuple itup;
+
+ ItemId id = PageGetItemId(page, off);
+
+ itup = (IndexTuple) PageGetItem(page, id);
+
+ item_size += IndexTupleSize(itup);
+
+ if (!ItemIdIsDead(id))
+ stat->live_items++;
+ else
+ stat->dead_items++;
+ }
+ stat->free_size = PageGetFreeSpace(page);
+
+ if ((stat->live_items + stat->dead_items) > 0)
+ stat->avg_item_size = item_size / (stat->live_items + stat->dead_items);
+ else
+ stat->avg_item_size = 0;
+}
+
+/* ---------------------------------------------------
+ * hash_page_stats_bytea()
+ *
+ * Usage: SELECT * FROM hash_page_stats(get_raw_page('t1_hash', 1), 1);
+ * ---------------------------------------------------
+ */
+Datum
+hash_page_stats_bytea(PG_FUNCTION_ARGS)
+{
+ bytea *raw_page = PG_GETARG_BYTEA_P(0);
+ uint32 blkno = PG_GETARG_UINT32(1);
+ Page page;
+ int j;
+ char *values[12];
+ HashPageStat stat;
+ Datum result;
+ HeapTuple tuple;
+ TupleDesc tupleDesc;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ page = verify_hash_page(raw_page);
+
+ /* keep compiler quiet */
+ stat.hasho_prevblkno = stat.hasho_nextblkno = InvalidBlockNumber;
+ stat.hasho_flag = stat.hasho_page_id = stat.free_size = stat.avg_item_size = 0;
+
+ GetHashPageStatistics(blkno, 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);
+
+ j = 0;
+ values[j++] = psprintf("%d", stat.blkno);
+ values[j++] = psprintf("%c", stat.type);
+ values[j++] = psprintf("%d", stat.live_items);
+ values[j++] = psprintf("%d", stat.dead_items);
+ values[j++] = psprintf("%d", stat.avg_item_size);
+ values[j++] = psprintf("%d", stat.page_size);
+ values[j++] = psprintf("%d", stat.free_size);
+ values[j++] = psprintf("%d", stat.hasho_prevblkno);
+ values[j++] = psprintf("%d", stat.hasho_nextblkno);
+ values[j++] = psprintf("%d", stat.hasho_bucket);
+ values[j++] = psprintf("%d", stat.hasho_flag);
+ values[j++] = psprintf("%d", stat.hasho_page_id);
+
+ tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
+ values);
+
+ result = HeapTupleGetDatum(tuple);
+
+ PG_RETURN_DATUM(result);
+}
+
+/* ---------------------------------------------------
+ * hash_page_stats()
+ *
+ * Usage: SELECT * FROM hash_page_stats('t1_hash', 1);
+ * ---------------------------------------------------
+ */
+Datum
+hash_page_stats(PG_FUNCTION_ARGS)
+{
+ text *relname = PG_GETARG_TEXT_P(0);
+ uint32 blkno = PG_GETARG_UINT32(1);
+ Buffer buffer;
+ Relation rel;
+ RangeVar *relrv;
+ Datum result;
+ HeapTuple tuple;
+ TupleDesc tupleDesc;
+ int j;
+ char *values[12];
+ HashPageStat stat;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
+ rel = relation_openrv(relrv, AccessShareLock);
+
+ if (!IS_INDEX(rel) || !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 tables of other sessions")));
+
+ if (blkno == 0)
+ elog(ERROR, "block 0 is a meta page");
+
+ CHECK_RELATION_BLOCK_RANGE(rel, blkno);
+
+ buffer = ReadBuffer(rel, blkno);
+ LockBuffer(buffer, BUFFER_LOCK_SHARE);
+
+ /* keep compiler quiet */
+ stat.hasho_prevblkno = stat.hasho_nextblkno = InvalidBlockNumber;
+ stat.hasho_flag = stat.hasho_page_id = stat.free_size = stat.avg_item_size = 0;
+
+ GetHashPageStatistics(blkno, BufferGetPage(buffer), &stat);
+
+ UnlockReleaseBuffer(buffer);
+ relation_close(rel, 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);
+
+ j = 0;
+ values[j++] = psprintf("%d", stat.blkno);
+ values[j++] = psprintf("%c", stat.type);
+ values[j++] = psprintf("%d", stat.live_items);
+ values[j++] = psprintf("%d", stat.dead_items);
+ values[j++] = psprintf("%d", stat.avg_item_size);
+ values[j++] = psprintf("%d", stat.page_size);
+ values[j++] = psprintf("%d", stat.free_size);
+ values[j++] = psprintf("%d", stat.hasho_prevblkno);
+ values[j++] = psprintf("%d", stat.hasho_nextblkno);
+ values[j++] = psprintf("%d", stat.hasho_bucket);
+ values[j++] = psprintf("%d", stat.hasho_flag);
+ values[j++] = psprintf("%d", stat.hasho_page_id);
+
+ tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
+ values);
+
+ result = HeapTupleGetDatum(tuple);
+
+ PG_RETURN_DATUM(result);
+}
+
+/*
+ * cross-call data structure for SRF
+ */
+struct user_args
+{
+ Page page;
+ OffsetNumber offset;
+};
+
+/*-------------------------------------------------------
+ * hash_page_items_bytea()
+ *
+ * Get IndexTupleData set in a hash page
+ *
+ * Usage: SELECT * FROM hash_page_items(get_raw_page('t1_hash', 1));
+ *-------------------------------------------------------
+ */
+Datum
+hash_page_items_bytea(PG_FUNCTION_ARGS)
+{
+ bytea *raw_page = PG_GETARG_BYTEA_P(0);
+ Page page;
+ Datum result;
+ char *values[6];
+ HeapTuple tuple;
+ FuncCallContext *fctx;
+ MemoryContext mctx;
+ struct user_args *uargs;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ TupleDesc tupleDesc;
+
+ fctx = SRF_FIRSTCALL_INIT();
+
+ page = verify_hash_page(raw_page);
+
+ /*
+ * We copy the page into local storage to avoid holding pin on the
+ * buffer longer than we must, and possibly failing to release it at
+ * all if the calling query doesn't fetch all rows.
+ */
+ mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
+
+ uargs = palloc(sizeof(struct user_args));
+
+ uargs->page = palloc(BLCKSZ);
+ memcpy(uargs->page, page, BLCKSZ);
+
+ 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;
+ int off;
+ int dlen;
+ char *dump;
+ char *ptr;
+
+ id = PageGetItemId(uargs->page, uargs->offset);
+
+ if (!ItemIdIsValid(id))
+ elog(ERROR, "invalid ItemId");
+
+ itup = (IndexTuple) PageGetItem(uargs->page, id);
+
+ j = 0;
+ values[j++] = psprintf("%d", uargs->offset);
+ values[j++] = psprintf("(%u,%u)",
+ BlockIdGetBlockNumber(&(itup->t_tid.ip_blkid)),
+ itup->t_tid.ip_posid);
+ values[j++] = psprintf("%d", (int) IndexTupleSize(itup));
+ values[j++] = psprintf("%c", IndexTupleHasNulls(itup) ? 't' : 'f');
+ values[j++] = psprintf("%c", IndexTupleHasVarwidths(itup) ? 't' : 'f');
+
+ ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
+ dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info);
+ dump = palloc0(dlen * 3 + 1);
+ values[j] = dump;
+ for (off = 0; off < dlen; off++)
+ {
+ if (off > 0)
+ *dump++ = ' ';
+ sprintf(dump, "%02x", *(ptr + off) & 0xff);
+ dump += 2;
+ }
+
+ tuple = BuildTupleFromCStrings(fctx->attinmeta, values);
+ result = HeapTupleGetDatum(tuple);
+
+ uargs->offset = uargs->offset + 1;
+
+ SRF_RETURN_NEXT(fctx, result);
+ }
+ else
+ {
+ pfree(uargs->page);
+ pfree(uargs);
+ SRF_RETURN_DONE(fctx);
+ }
+}
+
+/*-------------------------------------------------------
+ * hash_page_items()
+ *
+ * Get IndexTupleData set in a hash page
+ *
+ * Usage: SELECT * FROM hash_page_items('t1_hash', 1);
+ *-------------------------------------------------------
+ */
+Datum
+hash_page_items(PG_FUNCTION_ARGS)
+{
+ text *relname = PG_GETARG_TEXT_P(0);
+ uint32 blkno = PG_GETARG_UINT32(1);
+ Datum result;
+ char *values[6];
+ HeapTuple tuple;
+ FuncCallContext *fctx;
+ MemoryContext mctx;
+ struct user_args *uargs;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ RangeVar *relrv;
+ Relation rel;
+ Buffer buffer;
+ TupleDesc tupleDesc;
+
+ fctx = SRF_FIRSTCALL_INIT();
+
+ relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
+ rel = relation_openrv(relrv, AccessShareLock);
+
+ if (!IS_INDEX(rel) || !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 tables of other sessions")));
+
+ if (blkno == 0)
+ elog(ERROR, "block 0 is a meta page");
+
+ CHECK_RELATION_BLOCK_RANGE(rel, blkno);
+
+ buffer = ReadBuffer(rel, blkno);
+ LockBuffer(buffer, BUFFER_LOCK_SHARE);
+
+ /*
+ * We copy the page into local storage to avoid holding pin on the
+ * buffer longer than we must, and possibly failing to release it at
+ * all if the calling query doesn't fetch all rows.
+ */
+ mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
+
+ uargs = palloc(sizeof(struct user_args));
+
+ uargs->page = palloc(BLCKSZ);
+ memcpy(uargs->page, BufferGetPage(buffer), BLCKSZ);
+
+ UnlockReleaseBuffer(buffer);
+ relation_close(rel, AccessShareLock);
+
+ 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;
+ int off;
+ int dlen;
+ char *dump;
+ char *ptr;
+
+ id = PageGetItemId(uargs->page, uargs->offset);
+
+ if (!ItemIdIsValid(id))
+ elog(ERROR, "invalid ItemId");
+
+ itup = (IndexTuple) PageGetItem(uargs->page, id);
+
+ j = 0;
+ values[j++] = psprintf("%d", uargs->offset);
+ values[j++] = psprintf("(%u,%u)",
+ BlockIdGetBlockNumber(&(itup->t_tid.ip_blkid)),
+ itup->t_tid.ip_posid);
+ values[j++] = psprintf("%d", (int) IndexTupleSize(itup));
+ values[j++] = psprintf("%c", IndexTupleHasNulls(itup) ? 't' : 'f');
+ values[j++] = psprintf("%c", IndexTupleHasVarwidths(itup) ? 't' : 'f');
+
+ ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
+ dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info);
+ dump = palloc0(dlen * 3 + 1);
+ values[j] = dump;
+ for (off = 0; off < dlen; off++)
+ {
+ if (off > 0)
+ *dump++ = ' ';
+ sprintf(dump, "%02x", *(ptr + off) & 0xff);
+ dump += 2;
+ }
+
+ tuple = BuildTupleFromCStrings(fctx->attinmeta, values);
+ result = HeapTupleGetDatum(tuple);
+
+ uargs->offset = uargs->offset + 1;
+
+ SRF_RETURN_NEXT(fctx, result);
+ }
+ else
+ {
+ pfree(uargs->page);
+ pfree(uargs);
+ SRF_RETURN_DONE(fctx);
+ }
+}
+
+
+/* ------------------------------------------------
+ * hash_metap_bytea()
+ *
+ * Get a hash's meta-page information
+ *
+ * Usage: SELECT * FROM hash_metap_bytea(get_raw_page('t1_hash', 0))
+ * ------------------------------------------------
+ */
+Datum
+hash_metap_bytea(PG_FUNCTION_ARGS)
+{
+ bytea *raw_page = PG_GETARG_BYTEA_P(0);
+ Datum result;
+ Page page;
+ HashMetaPageData *metad;
+ TupleDesc tupleDesc;
+ HeapTuple tuple;
+ int i,j;
+ char *values[16];
+ char *spares;
+ char *mapp;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ page = verify_hash_page(raw_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);
+
+ j = 0;
+ values[j++] = psprintf("%d", metad->hashm_magic);
+ values[j++] = psprintf("%d", metad->hashm_version);
+ values[j++] = psprintf("%f", metad->hashm_ntuples);
+ values[j++] = psprintf("%d", metad->hashm_ffactor);
+ values[j++] = psprintf("%d", metad->hashm_bsize);
+ values[j++] = psprintf("%d", metad->hashm_bmsize);
+ values[j++] = psprintf("%d", metad->hashm_bmshift);
+ values[j++] = psprintf("%d", metad->hashm_maxbucket);
+ values[j++] = psprintf("%d", metad->hashm_highmask);
+ values[j++] = psprintf("%d", metad->hashm_lowmask);
+ values[j++] = psprintf("%d", metad->hashm_ovflpoint);
+ values[j++] = psprintf("%d", metad->hashm_firstfree);
+ values[j++] = psprintf("%d", metad->hashm_nmaps);
+ values[j++] = psprintf("%d", metad->hashm_procid);
+
+ spares = palloc0(HASH_MAX_SPLITPOINTS * 5 + 1);
+ values[j++] = spares;
+ for (i = 0; i < HASH_MAX_SPLITPOINTS; i++)
+ {
+ if (i > 0)
+ *spares++ = ' ';
+
+ sprintf(spares, "%04x", metad->hashm_spares[i]);
+ spares += 4;
+ }
+
+ mapp = palloc0(HASH_MAX_BITMAPS * 5 + 1);
+ values[j++] = mapp;
+ for (i = 0; i < HASH_MAX_BITMAPS; i++)
+ {
+ if (i > 0)
+ *mapp++ = ' ';
+
+ sprintf(mapp, "%04x", metad->hashm_mapp[i]);
+ mapp += 4;
+ }
+
+ tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
+ values);
+
+ result = HeapTupleGetDatum(tuple);
+
+ PG_RETURN_DATUM(result);
+}
+
+/* ------------------------------------------------
+ * hash_metap()
+ *
+ * Get a hash's meta-page information
+ *
+ * Usage: SELECT * FROM hash_metap('t1_hash')
+ * ------------------------------------------------
+ */
+Datum
+hash_metap(PG_FUNCTION_ARGS)
+{
+ text *relname = PG_GETARG_TEXT_P(0);
+ Datum result;
+ Relation rel;
+ RangeVar *relrv;
+ HashMetaPageData *metad;
+ TupleDesc tupleDesc;
+ int i,j;
+ char *values[16];
+ char *spares;
+ char *mapp;
+ Buffer buffer;
+ Page page;
+ HeapTuple tuple;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
+ rel = relation_openrv(relrv, AccessShareLock);
+
+ if (!IS_INDEX(rel) || !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 tables of other sessions")));
+
+ buffer = ReadBuffer(rel, 0);
+ LockBuffer(buffer, BUFFER_LOCK_SHARE);
+
+ page = BufferGetPage(buffer);
+ metad = HashPageGetMeta(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);
+
+ j = 0;
+ values[j++] = psprintf("%d", metad->hashm_magic);
+ values[j++] = psprintf("%d", metad->hashm_version);
+ values[j++] = psprintf("%f", metad->hashm_ntuples);
+ values[j++] = psprintf("%d", metad->hashm_ffactor);
+ values[j++] = psprintf("%d", metad->hashm_bsize);
+ values[j++] = psprintf("%d", metad->hashm_bmsize);
+ values[j++] = psprintf("%d", metad->hashm_bmshift);
+ values[j++] = psprintf("%d", metad->hashm_maxbucket);
+ values[j++] = psprintf("%d", metad->hashm_highmask);
+ values[j++] = psprintf("%d", metad->hashm_lowmask);
+ values[j++] = psprintf("%d", metad->hashm_ovflpoint);
+ values[j++] = psprintf("%d", metad->hashm_firstfree);
+ values[j++] = psprintf("%d", metad->hashm_nmaps);
+ values[j++] = psprintf("%d", metad->hashm_procid);
+
+ spares = palloc0(HASH_MAX_SPLITPOINTS * 5 + 1);
+ values[j++] = spares;
+ for (i = 0; i < HASH_MAX_SPLITPOINTS; i++)
+ {
+ if (i > 0)
+ *spares++ = ' ';
+
+ sprintf(spares, "%04x", metad->hashm_spares[i]);
+ spares += 4;
+ }
+
+ mapp = palloc0(HASH_MAX_BITMAPS * 5 + 1);
+ values[j++] = mapp;
+ for (i = 0; i < HASH_MAX_BITMAPS; i++)
+ {
+ if (i > 0)
+ *mapp++ = ' ';
+
+ sprintf(mapp, "%04x", metad->hashm_mapp[i]);
+ mapp += 4;
+ }
+
+ tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
+ values);
+
+ result = HeapTupleGetDatum(tuple);
+
+ UnlockReleaseBuffer(buffer);
+ relation_close(rel, AccessShareLock);
+
+ PG_RETURN_DATUM(result);
+}
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 0000000..4e0be67
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.5--1.6.sql
@@ -0,0 +1,111 @@
+/* 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_metap()
+--
+CREATE FUNCTION hash_metap_bytea(IN page bytea,
+ OUT magic int4,
+ OUT version int4,
+ OUT ntuples double precision,
+ OUT ffactor int2,
+ OUT bsize int2,
+ OUT bmsize int2,
+ OUT bmshift int2,
+ OUT maxbucket int4,
+ OUT highmask int4,
+ OUT lowmask int4,
+ OUT ovflpoint int4,
+ OUT firstfree int4,
+ OUT nmaps int4,
+ OUT procid int4,
+ OUT spares text,
+ OUT mapp text)
+AS 'MODULE_PATHNAME', 'hash_metap_bytea'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+CREATE FUNCTION hash_metap(IN relname text,
+ OUT magic int4,
+ OUT version int4,
+ OUT ntuples double precision,
+ OUT ffactor int2,
+ OUT bsize int2,
+ OUT bmsize int2,
+ OUT bmshift int2,
+ OUT maxbucket int4,
+ OUT highmask int4,
+ OUT lowmask int4,
+ OUT ovflpoint int4,
+ OUT firstfree int4,
+ OUT nmaps int4,
+ OUT procid int4,
+ OUT spares text,
+ OUT mapp text)
+AS 'MODULE_PATHNAME', 'hash_metap'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- hash_page_stats()
+--
+CREATE FUNCTION hash_page_stats_bytea(IN page bytea, IN blkno int4,
+ OUT blkno int4,
+ OUT type "char",
+ OUT live_items int4,
+ OUT dead_items int4,
+ OUT avg_item_size int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT hasho_prevblkno int4,
+ OUT hasho_nextblkno int4,
+ OUT hasho_bucket int4,
+ OUT hasho_flag int4,
+ OUT hasho_page_id int4)
+AS 'MODULE_PATHNAME', 'hash_page_stats_bytea'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+CREATE FUNCTION hash_page_stats(IN relname text, IN blkno int4,
+ OUT blkno int4,
+ OUT type "char",
+ OUT live_items int4,
+ OUT dead_items int4,
+ OUT avg_item_size int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT hasho_prevblkno int4,
+ OUT hasho_nextblkno int4,
+ OUT hasho_bucket int4,
+ OUT hasho_flag int4,
+ OUT hasho_page_id int4)
+AS 'MODULE_PATHNAME', 'hash_page_stats'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- hash_page_items()
+--
+CREATE FUNCTION hash_page_items_bytea(IN page bytea,
+ OUT itemoffset smallint,
+ OUT ctid tid,
+ OUT itemlen smallint,
+ OUT nulls bool,
+ OUT vars bool,
+ OUT data text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'hash_page_items_bytea'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+CREATE FUNCTION hash_page_items(IN relname text, IN blkno int4,
+ OUT itemoffset smallint,
+ OUT ctid tid,
+ OUT itemlen smallint,
+ OUT nulls bool,
+ OUT vars bool,
+ OUT data text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'hash_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect--1.5.sql b/contrib/pageinspect/pageinspect--1.5.sql
deleted file mode 100644
index 1e40c3c..0000000
--- a/contrib/pageinspect/pageinspect--1.5.sql
+++ /dev/null
@@ -1,279 +0,0 @@
-/* contrib/pageinspect/pageinspect--1.5.sql */
-
--- complain if script is sourced in psql, rather than via CREATE EXTENSION
-\echo Use "CREATE EXTENSION pageinspect" to load this file. \quit
-
---
--- get_raw_page()
---
-CREATE FUNCTION get_raw_page(text, int4)
-RETURNS bytea
-AS 'MODULE_PATHNAME', 'get_raw_page'
-LANGUAGE C STRICT PARALLEL SAFE;
-
-CREATE FUNCTION get_raw_page(text, text, int4)
-RETURNS bytea
-AS 'MODULE_PATHNAME', 'get_raw_page_fork'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- page_header()
---
-CREATE FUNCTION page_header(IN page bytea,
- OUT lsn pg_lsn,
- OUT checksum smallint,
- OUT flags smallint,
- OUT lower smallint,
- OUT upper smallint,
- OUT special smallint,
- OUT pagesize smallint,
- OUT version smallint,
- OUT prune_xid xid)
-AS 'MODULE_PATHNAME', 'page_header'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- heap_page_items()
---
-CREATE FUNCTION heap_page_items(IN page bytea,
- OUT lp smallint,
- OUT lp_off smallint,
- OUT lp_flags smallint,
- OUT lp_len smallint,
- OUT t_xmin xid,
- OUT t_xmax xid,
- OUT t_field3 int4,
- OUT t_ctid tid,
- OUT t_infomask2 integer,
- OUT t_infomask integer,
- OUT t_hoff smallint,
- OUT t_bits text,
- OUT t_oid oid,
- OUT t_data bytea)
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'heap_page_items'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- tuple_data_split()
---
-CREATE FUNCTION tuple_data_split(rel_oid oid,
- t_data bytea,
- t_infomask integer,
- t_infomask2 integer,
- t_bits text)
-RETURNS bytea[]
-AS 'MODULE_PATHNAME','tuple_data_split'
-LANGUAGE C PARALLEL SAFE;
-
-CREATE FUNCTION tuple_data_split(rel_oid oid,
- t_data bytea,
- t_infomask integer,
- t_infomask2 integer,
- t_bits text,
- do_detoast bool)
-RETURNS bytea[]
-AS 'MODULE_PATHNAME','tuple_data_split'
-LANGUAGE C PARALLEL SAFE;
-
---
--- heap_page_item_attrs()
---
-CREATE FUNCTION heap_page_item_attrs(
- IN page bytea,
- IN rel_oid regclass,
- IN do_detoast bool,
- OUT lp smallint,
- OUT lp_off smallint,
- OUT lp_flags smallint,
- OUT lp_len smallint,
- OUT t_xmin xid,
- OUT t_xmax xid,
- OUT t_field3 int4,
- OUT t_ctid tid,
- OUT t_infomask2 integer,
- OUT t_infomask integer,
- OUT t_hoff smallint,
- OUT t_bits text,
- OUT t_oid oid,
- OUT t_attrs bytea[]
- )
-RETURNS SETOF record AS $$
-SELECT lp,
- lp_off,
- lp_flags,
- lp_len,
- t_xmin,
- t_xmax,
- t_field3,
- t_ctid,
- t_infomask2,
- t_infomask,
- t_hoff,
- t_bits,
- t_oid,
- tuple_data_split(
- rel_oid,
- t_data,
- t_infomask,
- t_infomask2,
- t_bits,
- do_detoast)
- AS t_attrs
- FROM heap_page_items(page);
-$$ LANGUAGE SQL PARALLEL SAFE;
-
-CREATE FUNCTION heap_page_item_attrs(IN page bytea, IN rel_oid regclass,
- OUT lp smallint,
- OUT lp_off smallint,
- OUT lp_flags smallint,
- OUT lp_len smallint,
- OUT t_xmin xid,
- OUT t_xmax xid,
- OUT t_field3 int4,
- OUT t_ctid tid,
- OUT t_infomask2 integer,
- OUT t_infomask integer,
- OUT t_hoff smallint,
- OUT t_bits text,
- OUT t_oid oid,
- OUT t_attrs bytea[]
- )
-RETURNS SETOF record AS $$
-SELECT * from heap_page_item_attrs(page, rel_oid, false);
-$$ LANGUAGE SQL PARALLEL SAFE;
-
---
--- bt_metap()
---
-CREATE FUNCTION bt_metap(IN relname text,
- OUT magic int4,
- OUT version int4,
- OUT root int4,
- OUT level int4,
- OUT fastroot int4,
- OUT fastlevel int4)
-AS 'MODULE_PATHNAME', 'bt_metap'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- bt_page_stats()
---
-CREATE FUNCTION bt_page_stats(IN relname text, IN blkno int4,
- OUT blkno int4,
- OUT type "char",
- OUT live_items int4,
- OUT dead_items int4,
- OUT avg_item_size int4,
- OUT page_size int4,
- OUT free_size int4,
- OUT btpo_prev int4,
- OUT btpo_next int4,
- OUT btpo int4,
- OUT btpo_flags int4)
-AS 'MODULE_PATHNAME', 'bt_page_stats'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- bt_page_items()
---
-CREATE FUNCTION bt_page_items(IN relname text, IN blkno int4,
- OUT itemoffset smallint,
- OUT ctid tid,
- OUT itemlen smallint,
- OUT nulls bool,
- OUT vars bool,
- OUT data text)
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'bt_page_items'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_page_type()
---
-CREATE FUNCTION brin_page_type(IN page bytea)
-RETURNS text
-AS 'MODULE_PATHNAME', 'brin_page_type'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_metapage_info()
---
-CREATE FUNCTION brin_metapage_info(IN page bytea, OUT magic text,
- OUT version integer, OUT pagesperrange integer, OUT lastrevmappage bigint)
-AS 'MODULE_PATHNAME', 'brin_metapage_info'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_revmap_data()
---
-CREATE FUNCTION brin_revmap_data(IN page bytea,
- OUT pages tid)
-RETURNS SETOF tid
-AS 'MODULE_PATHNAME', 'brin_revmap_data'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_page_items()
---
-CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass,
- OUT itemoffset int,
- OUT blknum int,
- OUT attnum int,
- OUT allnulls bool,
- OUT hasnulls bool,
- OUT placeholder bool,
- OUT value text)
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'brin_page_items'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- fsm_page_contents()
---
-CREATE FUNCTION fsm_page_contents(IN page bytea)
-RETURNS text
-AS 'MODULE_PATHNAME', 'fsm_page_contents'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- GIN functions
---
-
---
--- gin_metapage_info()
---
-CREATE FUNCTION gin_metapage_info(IN page bytea,
- OUT pending_head bigint,
- OUT pending_tail bigint,
- OUT tail_free_size int4,
- OUT n_pending_pages bigint,
- OUT n_pending_tuples bigint,
- OUT n_total_pages bigint,
- OUT n_entry_pages bigint,
- OUT n_data_pages bigint,
- OUT n_entries bigint,
- OUT version int4)
-AS 'MODULE_PATHNAME', 'gin_metapage_info'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- gin_page_opaque_info()
---
-CREATE FUNCTION gin_page_opaque_info(IN page bytea,
- OUT rightlink bigint,
- OUT maxoff int4,
- OUT flags text[])
-AS 'MODULE_PATHNAME', 'gin_page_opaque_info'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- gin_leafpage_items()
---
-CREATE FUNCTION gin_leafpage_items(IN page bytea,
- OUT first_tid tid,
- OUT nbytes int2,
- OUT tids tid[])
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'gin_leafpage_items'
-LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect--1.6.sql b/contrib/pageinspect/pageinspect--1.6.sql
new file mode 100644
index 0000000..682ba89
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.6.sql
@@ -0,0 +1,386 @@
+/* contrib/pageinspect/pageinspect--1.6.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION pageinspect" to load this file. \quit
+
+--
+-- get_raw_page()
+--
+CREATE FUNCTION get_raw_page(text, int4)
+RETURNS bytea
+AS 'MODULE_PATHNAME', 'get_raw_page'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+CREATE FUNCTION get_raw_page(text, text, int4)
+RETURNS bytea
+AS 'MODULE_PATHNAME', 'get_raw_page_fork'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- page_header()
+--
+CREATE FUNCTION page_header(IN page bytea,
+ OUT lsn pg_lsn,
+ OUT checksum smallint,
+ OUT flags smallint,
+ OUT lower smallint,
+ OUT upper smallint,
+ OUT special smallint,
+ OUT pagesize smallint,
+ OUT version smallint,
+ OUT prune_xid xid)
+AS 'MODULE_PATHNAME', 'page_header'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- heap_page_items()
+--
+CREATE FUNCTION heap_page_items(IN page bytea,
+ OUT lp smallint,
+ OUT lp_off smallint,
+ OUT lp_flags smallint,
+ OUT lp_len smallint,
+ OUT t_xmin xid,
+ OUT t_xmax xid,
+ OUT t_field3 int4,
+ OUT t_ctid tid,
+ OUT t_infomask2 integer,
+ OUT t_infomask integer,
+ OUT t_hoff smallint,
+ OUT t_bits text,
+ OUT t_oid oid,
+ OUT t_data bytea)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'heap_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- tuple_data_split()
+--
+CREATE FUNCTION tuple_data_split(rel_oid oid,
+ t_data bytea,
+ t_infomask integer,
+ t_infomask2 integer,
+ t_bits text)
+RETURNS bytea[]
+AS 'MODULE_PATHNAME','tuple_data_split'
+LANGUAGE C PARALLEL SAFE;
+
+CREATE FUNCTION tuple_data_split(rel_oid oid,
+ t_data bytea,
+ t_infomask integer,
+ t_infomask2 integer,
+ t_bits text,
+ do_detoast bool)
+RETURNS bytea[]
+AS 'MODULE_PATHNAME','tuple_data_split'
+LANGUAGE C PARALLEL SAFE;
+
+--
+-- heap_page_item_attrs()
+--
+CREATE FUNCTION heap_page_item_attrs(
+ IN page bytea,
+ IN rel_oid regclass,
+ IN do_detoast bool,
+ OUT lp smallint,
+ OUT lp_off smallint,
+ OUT lp_flags smallint,
+ OUT lp_len smallint,
+ OUT t_xmin xid,
+ OUT t_xmax xid,
+ OUT t_field3 int4,
+ OUT t_ctid tid,
+ OUT t_infomask2 integer,
+ OUT t_infomask integer,
+ OUT t_hoff smallint,
+ OUT t_bits text,
+ OUT t_oid oid,
+ OUT t_attrs bytea[]
+ )
+RETURNS SETOF record AS $$
+SELECT lp,
+ lp_off,
+ lp_flags,
+ lp_len,
+ t_xmin,
+ t_xmax,
+ t_field3,
+ t_ctid,
+ t_infomask2,
+ t_infomask,
+ t_hoff,
+ t_bits,
+ t_oid,
+ tuple_data_split(
+ rel_oid,
+ t_data,
+ t_infomask,
+ t_infomask2,
+ t_bits,
+ do_detoast)
+ AS t_attrs
+ FROM heap_page_items(page);
+$$ LANGUAGE SQL PARALLEL SAFE;
+
+CREATE FUNCTION heap_page_item_attrs(IN page bytea, IN rel_oid regclass,
+ OUT lp smallint,
+ OUT lp_off smallint,
+ OUT lp_flags smallint,
+ OUT lp_len smallint,
+ OUT t_xmin xid,
+ OUT t_xmax xid,
+ OUT t_field3 int4,
+ OUT t_ctid tid,
+ OUT t_infomask2 integer,
+ OUT t_infomask integer,
+ OUT t_hoff smallint,
+ OUT t_bits text,
+ OUT t_oid oid,
+ OUT t_attrs bytea[]
+ )
+RETURNS SETOF record AS $$
+SELECT * from heap_page_item_attrs(page, rel_oid, false);
+$$ LANGUAGE SQL PARALLEL SAFE;
+
+--
+-- bt_metap()
+--
+CREATE FUNCTION bt_metap(IN relname text,
+ OUT magic int4,
+ OUT version int4,
+ OUT root int4,
+ OUT level int4,
+ OUT fastroot int4,
+ OUT fastlevel int4)
+AS 'MODULE_PATHNAME', 'bt_metap'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- bt_page_stats()
+--
+CREATE FUNCTION bt_page_stats(IN relname text, IN blkno int4,
+ OUT blkno int4,
+ OUT type "char",
+ OUT live_items int4,
+ OUT dead_items int4,
+ OUT avg_item_size int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT btpo_prev int4,
+ OUT btpo_next int4,
+ OUT btpo int4,
+ OUT btpo_flags int4)
+AS 'MODULE_PATHNAME', 'bt_page_stats'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- bt_page_items()
+--
+CREATE FUNCTION bt_page_items(IN relname text, IN blkno int4,
+ OUT itemoffset smallint,
+ OUT ctid tid,
+ OUT itemlen smallint,
+ OUT nulls bool,
+ OUT vars bool,
+ OUT data text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'bt_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_page_type()
+--
+CREATE FUNCTION brin_page_type(IN page bytea)
+RETURNS text
+AS 'MODULE_PATHNAME', 'brin_page_type'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_metapage_info()
+--
+CREATE FUNCTION brin_metapage_info(IN page bytea, OUT magic text,
+ OUT version integer, OUT pagesperrange integer, OUT lastrevmappage bigint)
+AS 'MODULE_PATHNAME', 'brin_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_revmap_data()
+--
+CREATE FUNCTION brin_revmap_data(IN page bytea,
+ OUT pages tid)
+RETURNS SETOF tid
+AS 'MODULE_PATHNAME', 'brin_revmap_data'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_page_items()
+--
+CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass,
+ OUT itemoffset int,
+ OUT blknum int,
+ OUT attnum int,
+ OUT allnulls bool,
+ OUT hasnulls bool,
+ OUT placeholder bool,
+ OUT value text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'brin_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- fsm_page_contents()
+--
+CREATE FUNCTION fsm_page_contents(IN page bytea)
+RETURNS text
+AS 'MODULE_PATHNAME', 'fsm_page_contents'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- GIN functions
+--
+
+--
+-- gin_metapage_info()
+--
+CREATE FUNCTION gin_metapage_info(IN page bytea,
+ OUT pending_head bigint,
+ OUT pending_tail bigint,
+ OUT tail_free_size int4,
+ OUT n_pending_pages bigint,
+ OUT n_pending_tuples bigint,
+ OUT n_total_pages bigint,
+ OUT n_entry_pages bigint,
+ OUT n_data_pages bigint,
+ OUT n_entries bigint,
+ OUT version int4)
+AS 'MODULE_PATHNAME', 'gin_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- gin_page_opaque_info()
+--
+CREATE FUNCTION gin_page_opaque_info(IN page bytea,
+ OUT rightlink bigint,
+ OUT maxoff int4,
+ OUT flags text[])
+AS 'MODULE_PATHNAME', 'gin_page_opaque_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- gin_leafpage_items()
+--
+CREATE FUNCTION gin_leafpage_items(IN page bytea,
+ OUT first_tid tid,
+ OUT nbytes int2,
+ OUT tids tid[])
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'gin_leafpage_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- HASH functions
+--
+
+--
+-- hash_metap()
+--
+CREATE FUNCTION hash_metap_bytea(IN page bytea,
+ OUT magic int4,
+ OUT version int4,
+ OUT ntuples double precision,
+ OUT ffactor int2,
+ OUT bsize int2,
+ OUT bmsize int2,
+ OUT bmshift int2,
+ OUT maxbucket int4,
+ OUT highmask int4,
+ OUT lowmask int4,
+ OUT ovflpoint int4,
+ OUT firstfree int4,
+ OUT nmaps int4,
+ OUT procid int4,
+ OUT spares text,
+ OUT mapp text)
+AS 'MODULE_PATHNAME', 'hash_metap_bytea'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+CREATE FUNCTION hash_metap(IN relname text,
+ OUT magic int4,
+ OUT version int4,
+ OUT ntuples double precision,
+ OUT ffactor int2,
+ OUT bsize int2,
+ OUT bmsize int2,
+ OUT bmshift int2,
+ OUT maxbucket int4,
+ OUT highmask int4,
+ OUT lowmask int4,
+ OUT ovflpoint int4,
+ OUT firstfree int4,
+ OUT nmaps int4,
+ OUT procid int4,
+ OUT spares text,
+ OUT mapp text)
+AS 'MODULE_PATHNAME', 'hash_metap'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- hash_page_stats()
+--
+CREATE FUNCTION hash_page_stats_bytea(IN page bytea, IN blkno int4,
+ OUT blkno int4,
+ OUT type "char",
+ OUT live_items int4,
+ OUT dead_items int4,
+ OUT avg_item_size int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT hasho_prevblkno int4,
+ OUT hasho_nextblkno int4,
+ OUT hasho_bucket int4,
+ OUT hasho_flag int4,
+ OUT hasho_page_id int4)
+AS 'MODULE_PATHNAME', 'hash_page_stats_bytea'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+CREATE FUNCTION hash_page_stats(IN relname text, IN blkno int4,
+ OUT blkno int4,
+ OUT type "char",
+ OUT live_items int4,
+ OUT dead_items int4,
+ OUT avg_item_size int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT hasho_prevblkno int4,
+ OUT hasho_nextblkno int4,
+ OUT hasho_bucket int4,
+ OUT hasho_flag int4,
+ OUT hasho_page_id int4)
+AS 'MODULE_PATHNAME', 'hash_page_stats'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- hash_page_items()
+--
+CREATE FUNCTION hash_page_items_bytea(IN page bytea,
+ OUT itemoffset smallint,
+ OUT ctid tid,
+ OUT itemlen smallint,
+ OUT nulls bool,
+ OUT vars bool,
+ OUT data text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'hash_page_items_bytea'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+CREATE FUNCTION hash_page_items(IN relname text, IN blkno int4,
+ OUT itemoffset smallint,
+ OUT ctid tid,
+ OUT itemlen smallint,
+ OUT nulls bool,
+ OUT vars bool,
+ OUT data text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'hash_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control
index 23c8eff..1a61c9f 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/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml
index 5d187ed..ea17675 100644
--- a/doc/src/sgml/pageinspect.sgml
+++ b/doc/src/sgml/pageinspect.sgml
@@ -452,6 +452,109 @@ test=# SELECT first_tid, nbytes, tids[0:5] as some_tids
<varlistentry>
<term>
+ <function>hash_metap(relname text) returns record</function>
+ <indexterm>
+ <primary>hash_metap</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+ <para>
+ <function>hash_metap</function> returns information about a hash
+ index's metapage. For example:
+<screen>
+test=# SELECT * FROM hash_metap('mytab_index');
+-[ RECORD 1 ]-----
+magic | 105121344
+version | 2
+ntuples | 1000000
+ffactor | 307
+bsize | 8152
+bmsize | 4096
+bmshift | 15
+maxbucket | 4095
+highmask | 8191
+lowmask | 4095
+ovflpoint | 12
+firstfree | 0
+nmaps | 1
+procid | 400
+spares | 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 0000 ...
+mapp | 1001 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 ...
+</screen>
+ Similar there is a <function>hash_metap_bytea</function> which takes its data from <function>get_raw_page</function>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <function>hash_page_stats(relname text, blkno int) returns record</function>
+ <indexterm>
+ <primary>hash_page_stats</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+ <para>
+ <function>hash_page_stats</function> returns summary information about
+ single pages of hash indexes. For example:
+<screen>
+test=# SELECT * FROM hash_page_stats('mytab_index', 1);
+-[ RECORD 1 ]-+-----
+blkno | 1
+type | b
+live_items | 211
+dead_items | 0
+avg_item_size | 16
+page_size | 8192
+free_size | 3928
+hasho_prevblkno | -1
+hasho_nextblkno | -1
+hasho_bucket | 0
+hasho_flag | 2
+hasho_page_id | 65408
+</screen>
+ Similar there is a <function>hash_page_stats_bytea</function> which takes its data from <function>get_raw_page</function>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <function>hash_page_items(relname text, blkno int) returns setof record</function>
+ <indexterm>
+ <primary>hash_page_items</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+ <para>
+ <function>hash_page_items</function> returns detailed information about
+ all of the items on a hash index page. For example:
+<screen>
+test=# SELECT * FROM hash_page_items('mytab_index', 1);
+ itemoffset | ctid | itemlen | nulls | vars | data
+------------+------------+---------+-------+------+-------------------------
+ 1 | (2057,114) | 16 | f | f | 00 a0 25 02 00 00 00 00
+ 2 | (4033,38) | 16 | f | f | 00 20 8d 02 00 00 00 00
+ 3 | (4223,130) | 16 | f | f | 00 80 f7 02 00 00 00 00
+ 4 | (1524,126) | 16 | f | f | 00 70 de 04 00 00 00 00
+ 5 | (5850,86) | 16 | f | f | 00 70 1a 07 00 00 00 00
+...
+ 210 | (5541,140) | 16 | f | f | 00 90 a9 ff 00 00 00 00
+ 211 | (4654,93) | 16 | f | f | 00 d0 fb ff 00 00 00 00
+
+</screen>
+ The <structfield>ctid</> column points to a heap tuple
+ (BlockIdData,OffsetNumber).
+ Similar there is a <function>hash_page_items_bytea</function> which takes its data from <function>get_raw_page</function>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
<function>fsm_page_contents(page bytea) returns text</function>
<indexterm>
<primary>fsm_page_contents</primary>
--
2.7.4
On Tue, Sep 20, 2016 at 1:11 AM, Jesper Pedersen
<jesper.pedersen@redhat.com> wrote:
This version adds the overloaded get_raw_page based methods. However, I'm
not verifying the page other than page_id, as hasho_flag can contain
multiple flags in Amit's patches. And, I don't see having different page ids
as a benefit - at least not part of this patch.We should probably just have one of the method sets, so I'll send a v3 with
the set voted for.I kept the 'data' field as is, for now.
$ git diff master --check
contrib/pageinspect/hashfuncs.c:758: space before tab in indent.
+ values);
That's always good to run to check the format of a patch before sending it.
You did not get right the comments from Alvaro upthread. The following
functions are added with this patch:
function hash_metap(text)
function hash_metap_bytea(bytea)
function hash_page_items(text,integer)
function hash_page_items_bytea(bytea)
function hash_page_stats(text,integer)
function hash_page_stats_bytea(bytea,integer)
Now the following set of functions would be sufficient:
function hash_metapage_info(bytea)
function hash_page_items(bytea)
function hash_page_stats(bytea)
The last time pageinspect has been updated, when BRIN functions have
been added, it has been discussed to just use (bytea) as an argument
interface and just rely on get_raw_page() to get the pages wanted, so
I think that we had better stick with that and keep things simple.
Note: the patch checks if a superuser is calling the new functions,
which is a good thing.
I am switching the patch back to "waiting on author". As far as I can
see the patch is in rather good shape, you just need to adapt the docs
and shave all the parts that are not needed for the final result.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 09/20/2016 03:19 AM, Michael Paquier wrote:
You did not get right the comments from Alvaro upthread. The following
functions are added with this patch:
function hash_metap(text)
function hash_metap_bytea(bytea)
function hash_page_items(text,integer)
function hash_page_items_bytea(bytea)
function hash_page_stats(text,integer)
function hash_page_stats_bytea(bytea,integer)Now the following set of functions would be sufficient:
function hash_metapage_info(bytea)
function hash_page_items(bytea)
function hash_page_stats(bytea)
The last time pageinspect has been updated, when BRIN functions have
been added, it has been discussed to just use (bytea) as an argument
interface and just rely on get_raw_page() to get the pages wanted, so
I think that we had better stick with that and keep things simple.
Yes, I know, Alvaro and you voted for the bytea methods, and Jeff asked
for both.
Attached is v3 with only the bytea based methods.
Alvaro, Michael and Jeff - Thanks for the review !
Best regards,
Jesper
Attachments:
0001-pageinspect-Hash-index-support_v3.patchtext/x-patch; name=0001-pageinspect-Hash-index-support_v3.patchDownload
From 0aff82ccb40f00efe9e48cacef9c8a45c1327da2 Mon Sep 17 00:00:00 2001
From: jesperpedersen <jesper.pedersen@redhat.com>
Date: Fri, 5 Aug 2016 10:16:32 -0400
Subject: [PATCH] pageinspect: Hash index support
---
contrib/pageinspect/Makefile | 10 +-
contrib/pageinspect/hashfuncs.c | 408 ++++++++++++++++++++++++++
contrib/pageinspect/pageinspect--1.5--1.6.sql | 64 ++++
contrib/pageinspect/pageinspect--1.5.sql | 279 ------------------
contrib/pageinspect/pageinspect--1.6.sql | 339 +++++++++++++++++++++
contrib/pageinspect/pageinspect.control | 2 +-
doc/src/sgml/pageinspect.sgml | 100 +++++++
7 files changed, 917 insertions(+), 285 deletions(-)
create mode 100644 contrib/pageinspect/hashfuncs.c
create mode 100644 contrib/pageinspect/pageinspect--1.5--1.6.sql
delete mode 100644 contrib/pageinspect/pageinspect--1.5.sql
create mode 100644 contrib/pageinspect/pageinspect--1.6.sql
diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index a98237e..c73d728 100644
--- a/contrib/pageinspect/Makefile
+++ b/contrib/pageinspect/Makefile
@@ -2,13 +2,13 @@
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.6.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"
ifdef USE_PGXS
diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c
new file mode 100644
index 0000000..4d2cd2a
--- /dev/null
+++ b/contrib/pageinspect/hashfuncs.c
@@ -0,0 +1,408 @@
+/*
+ * hashfuncs.c
+ * Functions to investigate the content of HASH indexes
+ *
+ * Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * contrib/pageinspect/hashfuncs.c
+ */
+
+#include "postgres.h"
+
+#include "access/hash.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+
+PG_FUNCTION_INFO_V1(hash_metap);
+PG_FUNCTION_INFO_V1(hash_page_items);
+PG_FUNCTION_INFO_V1(hash_page_stats);
+
+/* ------------------------------------------------
+ * structure for single hash page statistics
+ * ------------------------------------------------
+ */
+typedef struct HashPageStat
+{
+ uint32 blkno;
+ uint32 live_items;
+ uint32 dead_items;
+ uint32 page_size;
+ uint32 max_avail;
+ uint32 free_size;
+ uint32 avg_item_size;
+ char type;
+
+ /* 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)
+{
+ Page page;
+ int raw_page_size;
+ HashPageOpaque pageopaque;
+
+ raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
+
+ if (raw_page_size != BLCKSZ)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("input page too small"),
+ errdetail("Expected size %d, got %d",
+ BLCKSZ, raw_page_size)));
+
+ page = VARDATA(raw_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)));
+
+ return page;
+}
+
+/* -------------------------------------------------
+ * GetHashPageStatistics()
+ *
+ * Collect statistics of single hash page
+ * -------------------------------------------------
+ */
+static void
+GetHashPageStatistics(BlockNumber blkno, Page page, HashPageStat *stat)
+{
+ PageHeader phdr = (PageHeader) page;
+ OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
+ HashPageOpaque opaque = (HashPageOpaque) PageGetSpecialPointer(page);
+ int item_size = 0;
+ int off;
+
+ stat->blkno = blkno;
+
+ stat->max_avail = BLCKSZ - (BLCKSZ - phdr->pd_special + SizeOfPageHeaderData);
+
+ stat->dead_items = stat->live_items = 0;
+
+ stat->page_size = PageGetPageSize(page);
+
+ /* page type (flags) */
+ if (opaque->hasho_flag & LH_META_PAGE)
+ stat->type = 'm';
+ else if (opaque->hasho_flag & LH_OVERFLOW_PAGE)
+ stat->type = 'v';
+ else if (opaque->hasho_flag & LH_BUCKET_PAGE)
+ stat->type = 'b';
+ else if (opaque->hasho_flag & LH_BITMAP_PAGE)
+ stat->type = 'i';
+ else
+ stat->type = 'u';
+
+ /* 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++)
+ {
+ IndexTuple itup;
+
+ ItemId id = PageGetItemId(page, off);
+
+ itup = (IndexTuple) PageGetItem(page, id);
+
+ item_size += IndexTupleSize(itup);
+
+ if (!ItemIdIsDead(id))
+ stat->live_items++;
+ else
+ stat->dead_items++;
+ }
+ stat->free_size = PageGetFreeSpace(page);
+
+ if ((stat->live_items + stat->dead_items) > 0)
+ stat->avg_item_size = item_size / (stat->live_items + stat->dead_items);
+ else
+ stat->avg_item_size = 0;
+}
+
+/* ---------------------------------------------------
+ * hash_page_stats()
+ *
+ * Usage: SELECT * FROM hash_page_stats(get_raw_page('t1_hash', 1), 1);
+ * ---------------------------------------------------
+ */
+Datum
+hash_page_stats(PG_FUNCTION_ARGS)
+{
+ bytea *raw_page = PG_GETARG_BYTEA_P(0);
+ uint32 blkno = PG_GETARG_UINT32(1);
+ Page page;
+ int j;
+ char *values[12];
+ HashPageStat stat;
+ Datum result;
+ HeapTuple tuple;
+ TupleDesc tupleDesc;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ page = verify_hash_page(raw_page);
+
+ /* keep compiler quiet */
+ stat.hasho_prevblkno = stat.hasho_nextblkno = InvalidBlockNumber;
+ stat.hasho_flag = stat.hasho_page_id = stat.free_size = stat.avg_item_size = 0;
+
+ GetHashPageStatistics(blkno, 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);
+
+ j = 0;
+ values[j++] = psprintf("%d", stat.blkno);
+ values[j++] = psprintf("%c", stat.type);
+ values[j++] = psprintf("%d", stat.live_items);
+ values[j++] = psprintf("%d", stat.dead_items);
+ values[j++] = psprintf("%d", stat.avg_item_size);
+ values[j++] = psprintf("%d", stat.page_size);
+ values[j++] = psprintf("%d", stat.free_size);
+ values[j++] = psprintf("%d", stat.hasho_prevblkno);
+ values[j++] = psprintf("%d", stat.hasho_nextblkno);
+ values[j++] = psprintf("%d", stat.hasho_bucket);
+ values[j++] = psprintf("%d", stat.hasho_flag);
+ values[j++] = psprintf("%d", stat.hasho_page_id);
+
+ tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
+ values);
+
+ result = HeapTupleGetDatum(tuple);
+
+ PG_RETURN_DATUM(result);
+}
+
+/*
+ * 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('t1_hash', 1));
+ *-------------------------------------------------------
+ */
+Datum
+hash_page_items(PG_FUNCTION_ARGS)
+{
+ bytea *raw_page = PG_GETARG_BYTEA_P(0);
+ Page page;
+ Datum result;
+ char *values[6];
+ HeapTuple tuple;
+ FuncCallContext *fctx;
+ MemoryContext mctx;
+ struct user_args *uargs;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ TupleDesc tupleDesc;
+
+ fctx = SRF_FIRSTCALL_INIT();
+
+ page = verify_hash_page(raw_page);
+
+ /*
+ * We copy the page into local storage to avoid holding pin on the
+ * buffer longer than we must, and possibly failing to release it at
+ * all if the calling query doesn't fetch all rows.
+ */
+ mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
+
+ uargs = palloc(sizeof(struct user_args));
+
+ uargs->page = palloc(BLCKSZ);
+ memcpy(uargs->page, page, BLCKSZ);
+
+ 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;
+ int off;
+ int dlen;
+ char *dump;
+ char *ptr;
+
+ id = PageGetItemId(uargs->page, uargs->offset);
+
+ if (!ItemIdIsValid(id))
+ elog(ERROR, "invalid ItemId");
+
+ itup = (IndexTuple) PageGetItem(uargs->page, id);
+
+ j = 0;
+ values[j++] = psprintf("%d", uargs->offset);
+ values[j++] = psprintf("(%u,%u)",
+ BlockIdGetBlockNumber(&(itup->t_tid.ip_blkid)),
+ itup->t_tid.ip_posid);
+ values[j++] = psprintf("%d", (int) IndexTupleSize(itup));
+ values[j++] = psprintf("%c", IndexTupleHasNulls(itup) ? 't' : 'f');
+ values[j++] = psprintf("%c", IndexTupleHasVarwidths(itup) ? 't' : 'f');
+
+ ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
+ dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info);
+ dump = palloc0(dlen * 3 + 1);
+ values[j] = dump;
+ for (off = 0; off < dlen; off++)
+ {
+ if (off > 0)
+ *dump++ = ' ';
+ sprintf(dump, "%02x", *(ptr + off) & 0xff);
+ dump += 2;
+ }
+
+ tuple = BuildTupleFromCStrings(fctx->attinmeta, values);
+ result = HeapTupleGetDatum(tuple);
+
+ uargs->offset = uargs->offset + 1;
+
+ SRF_RETURN_NEXT(fctx, result);
+ }
+ else
+ {
+ pfree(uargs->page);
+ pfree(uargs);
+ SRF_RETURN_DONE(fctx);
+ }
+}
+
+/* ------------------------------------------------
+ * hash_metap()
+ *
+ * Get a hash's meta-page information
+ *
+ * Usage: SELECT * FROM hash_metap_bytea(get_raw_page('t1_hash', 0))
+ * ------------------------------------------------
+ */
+Datum
+hash_metap(PG_FUNCTION_ARGS)
+{
+ bytea *raw_page = PG_GETARG_BYTEA_P(0);
+ Datum result;
+ Page page;
+ HashMetaPageData *metad;
+ TupleDesc tupleDesc;
+ HeapTuple tuple;
+ int i,j;
+ char *values[16];
+ char *spares;
+ char *mapp;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ page = verify_hash_page(raw_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);
+
+ j = 0;
+ values[j++] = psprintf("%d", metad->hashm_magic);
+ values[j++] = psprintf("%d", metad->hashm_version);
+ values[j++] = psprintf("%f", metad->hashm_ntuples);
+ values[j++] = psprintf("%d", metad->hashm_ffactor);
+ values[j++] = psprintf("%d", metad->hashm_bsize);
+ values[j++] = psprintf("%d", metad->hashm_bmsize);
+ values[j++] = psprintf("%d", metad->hashm_bmshift);
+ values[j++] = psprintf("%d", metad->hashm_maxbucket);
+ values[j++] = psprintf("%d", metad->hashm_highmask);
+ values[j++] = psprintf("%d", metad->hashm_lowmask);
+ values[j++] = psprintf("%d", metad->hashm_ovflpoint);
+ values[j++] = psprintf("%d", metad->hashm_firstfree);
+ values[j++] = psprintf("%d", metad->hashm_nmaps);
+ values[j++] = psprintf("%d", metad->hashm_procid);
+
+ spares = palloc0(HASH_MAX_SPLITPOINTS * 5 + 1);
+ values[j++] = spares;
+ for (i = 0; i < HASH_MAX_SPLITPOINTS; i++)
+ {
+ if (i > 0)
+ *spares++ = ' ';
+
+ sprintf(spares, "%04x", metad->hashm_spares[i]);
+ spares += 4;
+ }
+
+ mapp = palloc0(HASH_MAX_BITMAPS * 5 + 1);
+ values[j++] = mapp;
+ for (i = 0; i < HASH_MAX_BITMAPS; i++)
+ {
+ if (i > 0)
+ *mapp++ = ' ';
+
+ sprintf(mapp, "%04x", metad->hashm_mapp[i]);
+ mapp += 4;
+ }
+
+ tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
+ values);
+
+ result = HeapTupleGetDatum(tuple);
+
+ PG_RETURN_DATUM(result);
+}
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 0000000..8eb1da8
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.5--1.6.sql
@@ -0,0 +1,64 @@
+/* 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_metap()
+--
+CREATE FUNCTION hash_metap(IN page bytea,
+ OUT magic int4,
+ OUT version int4,
+ OUT ntuples double precision,
+ OUT ffactor int2,
+ OUT bsize int2,
+ OUT bmsize int2,
+ OUT bmshift int2,
+ OUT maxbucket int4,
+ OUT highmask int4,
+ OUT lowmask int4,
+ OUT ovflpoint int4,
+ OUT firstfree int4,
+ OUT nmaps int4,
+ OUT procid int4,
+ OUT spares text,
+ OUT mapp text)
+AS 'MODULE_PATHNAME', 'hash_metap'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- hash_page_stats()
+--
+CREATE FUNCTION hash_page_stats(IN page bytea, IN blkno int4,
+ OUT blkno int4,
+ OUT type "char",
+ OUT live_items int4,
+ OUT dead_items int4,
+ OUT avg_item_size int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT hasho_prevblkno int4,
+ OUT hasho_nextblkno int4,
+ OUT hasho_bucket int4,
+ OUT hasho_flag int4,
+ 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 itemlen smallint,
+ OUT nulls bool,
+ OUT vars bool,
+ OUT data text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'hash_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect--1.5.sql b/contrib/pageinspect/pageinspect--1.5.sql
deleted file mode 100644
index 1e40c3c..0000000
--- a/contrib/pageinspect/pageinspect--1.5.sql
+++ /dev/null
@@ -1,279 +0,0 @@
-/* contrib/pageinspect/pageinspect--1.5.sql */
-
--- complain if script is sourced in psql, rather than via CREATE EXTENSION
-\echo Use "CREATE EXTENSION pageinspect" to load this file. \quit
-
---
--- get_raw_page()
---
-CREATE FUNCTION get_raw_page(text, int4)
-RETURNS bytea
-AS 'MODULE_PATHNAME', 'get_raw_page'
-LANGUAGE C STRICT PARALLEL SAFE;
-
-CREATE FUNCTION get_raw_page(text, text, int4)
-RETURNS bytea
-AS 'MODULE_PATHNAME', 'get_raw_page_fork'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- page_header()
---
-CREATE FUNCTION page_header(IN page bytea,
- OUT lsn pg_lsn,
- OUT checksum smallint,
- OUT flags smallint,
- OUT lower smallint,
- OUT upper smallint,
- OUT special smallint,
- OUT pagesize smallint,
- OUT version smallint,
- OUT prune_xid xid)
-AS 'MODULE_PATHNAME', 'page_header'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- heap_page_items()
---
-CREATE FUNCTION heap_page_items(IN page bytea,
- OUT lp smallint,
- OUT lp_off smallint,
- OUT lp_flags smallint,
- OUT lp_len smallint,
- OUT t_xmin xid,
- OUT t_xmax xid,
- OUT t_field3 int4,
- OUT t_ctid tid,
- OUT t_infomask2 integer,
- OUT t_infomask integer,
- OUT t_hoff smallint,
- OUT t_bits text,
- OUT t_oid oid,
- OUT t_data bytea)
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'heap_page_items'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- tuple_data_split()
---
-CREATE FUNCTION tuple_data_split(rel_oid oid,
- t_data bytea,
- t_infomask integer,
- t_infomask2 integer,
- t_bits text)
-RETURNS bytea[]
-AS 'MODULE_PATHNAME','tuple_data_split'
-LANGUAGE C PARALLEL SAFE;
-
-CREATE FUNCTION tuple_data_split(rel_oid oid,
- t_data bytea,
- t_infomask integer,
- t_infomask2 integer,
- t_bits text,
- do_detoast bool)
-RETURNS bytea[]
-AS 'MODULE_PATHNAME','tuple_data_split'
-LANGUAGE C PARALLEL SAFE;
-
---
--- heap_page_item_attrs()
---
-CREATE FUNCTION heap_page_item_attrs(
- IN page bytea,
- IN rel_oid regclass,
- IN do_detoast bool,
- OUT lp smallint,
- OUT lp_off smallint,
- OUT lp_flags smallint,
- OUT lp_len smallint,
- OUT t_xmin xid,
- OUT t_xmax xid,
- OUT t_field3 int4,
- OUT t_ctid tid,
- OUT t_infomask2 integer,
- OUT t_infomask integer,
- OUT t_hoff smallint,
- OUT t_bits text,
- OUT t_oid oid,
- OUT t_attrs bytea[]
- )
-RETURNS SETOF record AS $$
-SELECT lp,
- lp_off,
- lp_flags,
- lp_len,
- t_xmin,
- t_xmax,
- t_field3,
- t_ctid,
- t_infomask2,
- t_infomask,
- t_hoff,
- t_bits,
- t_oid,
- tuple_data_split(
- rel_oid,
- t_data,
- t_infomask,
- t_infomask2,
- t_bits,
- do_detoast)
- AS t_attrs
- FROM heap_page_items(page);
-$$ LANGUAGE SQL PARALLEL SAFE;
-
-CREATE FUNCTION heap_page_item_attrs(IN page bytea, IN rel_oid regclass,
- OUT lp smallint,
- OUT lp_off smallint,
- OUT lp_flags smallint,
- OUT lp_len smallint,
- OUT t_xmin xid,
- OUT t_xmax xid,
- OUT t_field3 int4,
- OUT t_ctid tid,
- OUT t_infomask2 integer,
- OUT t_infomask integer,
- OUT t_hoff smallint,
- OUT t_bits text,
- OUT t_oid oid,
- OUT t_attrs bytea[]
- )
-RETURNS SETOF record AS $$
-SELECT * from heap_page_item_attrs(page, rel_oid, false);
-$$ LANGUAGE SQL PARALLEL SAFE;
-
---
--- bt_metap()
---
-CREATE FUNCTION bt_metap(IN relname text,
- OUT magic int4,
- OUT version int4,
- OUT root int4,
- OUT level int4,
- OUT fastroot int4,
- OUT fastlevel int4)
-AS 'MODULE_PATHNAME', 'bt_metap'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- bt_page_stats()
---
-CREATE FUNCTION bt_page_stats(IN relname text, IN blkno int4,
- OUT blkno int4,
- OUT type "char",
- OUT live_items int4,
- OUT dead_items int4,
- OUT avg_item_size int4,
- OUT page_size int4,
- OUT free_size int4,
- OUT btpo_prev int4,
- OUT btpo_next int4,
- OUT btpo int4,
- OUT btpo_flags int4)
-AS 'MODULE_PATHNAME', 'bt_page_stats'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- bt_page_items()
---
-CREATE FUNCTION bt_page_items(IN relname text, IN blkno int4,
- OUT itemoffset smallint,
- OUT ctid tid,
- OUT itemlen smallint,
- OUT nulls bool,
- OUT vars bool,
- OUT data text)
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'bt_page_items'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_page_type()
---
-CREATE FUNCTION brin_page_type(IN page bytea)
-RETURNS text
-AS 'MODULE_PATHNAME', 'brin_page_type'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_metapage_info()
---
-CREATE FUNCTION brin_metapage_info(IN page bytea, OUT magic text,
- OUT version integer, OUT pagesperrange integer, OUT lastrevmappage bigint)
-AS 'MODULE_PATHNAME', 'brin_metapage_info'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_revmap_data()
---
-CREATE FUNCTION brin_revmap_data(IN page bytea,
- OUT pages tid)
-RETURNS SETOF tid
-AS 'MODULE_PATHNAME', 'brin_revmap_data'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_page_items()
---
-CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass,
- OUT itemoffset int,
- OUT blknum int,
- OUT attnum int,
- OUT allnulls bool,
- OUT hasnulls bool,
- OUT placeholder bool,
- OUT value text)
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'brin_page_items'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- fsm_page_contents()
---
-CREATE FUNCTION fsm_page_contents(IN page bytea)
-RETURNS text
-AS 'MODULE_PATHNAME', 'fsm_page_contents'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- GIN functions
---
-
---
--- gin_metapage_info()
---
-CREATE FUNCTION gin_metapage_info(IN page bytea,
- OUT pending_head bigint,
- OUT pending_tail bigint,
- OUT tail_free_size int4,
- OUT n_pending_pages bigint,
- OUT n_pending_tuples bigint,
- OUT n_total_pages bigint,
- OUT n_entry_pages bigint,
- OUT n_data_pages bigint,
- OUT n_entries bigint,
- OUT version int4)
-AS 'MODULE_PATHNAME', 'gin_metapage_info'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- gin_page_opaque_info()
---
-CREATE FUNCTION gin_page_opaque_info(IN page bytea,
- OUT rightlink bigint,
- OUT maxoff int4,
- OUT flags text[])
-AS 'MODULE_PATHNAME', 'gin_page_opaque_info'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- gin_leafpage_items()
---
-CREATE FUNCTION gin_leafpage_items(IN page bytea,
- OUT first_tid tid,
- OUT nbytes int2,
- OUT tids tid[])
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'gin_leafpage_items'
-LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect--1.6.sql b/contrib/pageinspect/pageinspect--1.6.sql
new file mode 100644
index 0000000..700fe57
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.6.sql
@@ -0,0 +1,339 @@
+/* contrib/pageinspect/pageinspect--1.6.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION pageinspect" to load this file. \quit
+
+--
+-- get_raw_page()
+--
+CREATE FUNCTION get_raw_page(text, int4)
+RETURNS bytea
+AS 'MODULE_PATHNAME', 'get_raw_page'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+CREATE FUNCTION get_raw_page(text, text, int4)
+RETURNS bytea
+AS 'MODULE_PATHNAME', 'get_raw_page_fork'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- page_header()
+--
+CREATE FUNCTION page_header(IN page bytea,
+ OUT lsn pg_lsn,
+ OUT checksum smallint,
+ OUT flags smallint,
+ OUT lower smallint,
+ OUT upper smallint,
+ OUT special smallint,
+ OUT pagesize smallint,
+ OUT version smallint,
+ OUT prune_xid xid)
+AS 'MODULE_PATHNAME', 'page_header'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- heap_page_items()
+--
+CREATE FUNCTION heap_page_items(IN page bytea,
+ OUT lp smallint,
+ OUT lp_off smallint,
+ OUT lp_flags smallint,
+ OUT lp_len smallint,
+ OUT t_xmin xid,
+ OUT t_xmax xid,
+ OUT t_field3 int4,
+ OUT t_ctid tid,
+ OUT t_infomask2 integer,
+ OUT t_infomask integer,
+ OUT t_hoff smallint,
+ OUT t_bits text,
+ OUT t_oid oid,
+ OUT t_data bytea)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'heap_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- tuple_data_split()
+--
+CREATE FUNCTION tuple_data_split(rel_oid oid,
+ t_data bytea,
+ t_infomask integer,
+ t_infomask2 integer,
+ t_bits text)
+RETURNS bytea[]
+AS 'MODULE_PATHNAME','tuple_data_split'
+LANGUAGE C PARALLEL SAFE;
+
+CREATE FUNCTION tuple_data_split(rel_oid oid,
+ t_data bytea,
+ t_infomask integer,
+ t_infomask2 integer,
+ t_bits text,
+ do_detoast bool)
+RETURNS bytea[]
+AS 'MODULE_PATHNAME','tuple_data_split'
+LANGUAGE C PARALLEL SAFE;
+
+--
+-- heap_page_item_attrs()
+--
+CREATE FUNCTION heap_page_item_attrs(
+ IN page bytea,
+ IN rel_oid regclass,
+ IN do_detoast bool,
+ OUT lp smallint,
+ OUT lp_off smallint,
+ OUT lp_flags smallint,
+ OUT lp_len smallint,
+ OUT t_xmin xid,
+ OUT t_xmax xid,
+ OUT t_field3 int4,
+ OUT t_ctid tid,
+ OUT t_infomask2 integer,
+ OUT t_infomask integer,
+ OUT t_hoff smallint,
+ OUT t_bits text,
+ OUT t_oid oid,
+ OUT t_attrs bytea[]
+ )
+RETURNS SETOF record AS $$
+SELECT lp,
+ lp_off,
+ lp_flags,
+ lp_len,
+ t_xmin,
+ t_xmax,
+ t_field3,
+ t_ctid,
+ t_infomask2,
+ t_infomask,
+ t_hoff,
+ t_bits,
+ t_oid,
+ tuple_data_split(
+ rel_oid,
+ t_data,
+ t_infomask,
+ t_infomask2,
+ t_bits,
+ do_detoast)
+ AS t_attrs
+ FROM heap_page_items(page);
+$$ LANGUAGE SQL PARALLEL SAFE;
+
+CREATE FUNCTION heap_page_item_attrs(IN page bytea, IN rel_oid regclass,
+ OUT lp smallint,
+ OUT lp_off smallint,
+ OUT lp_flags smallint,
+ OUT lp_len smallint,
+ OUT t_xmin xid,
+ OUT t_xmax xid,
+ OUT t_field3 int4,
+ OUT t_ctid tid,
+ OUT t_infomask2 integer,
+ OUT t_infomask integer,
+ OUT t_hoff smallint,
+ OUT t_bits text,
+ OUT t_oid oid,
+ OUT t_attrs bytea[]
+ )
+RETURNS SETOF record AS $$
+SELECT * from heap_page_item_attrs(page, rel_oid, false);
+$$ LANGUAGE SQL PARALLEL SAFE;
+
+--
+-- bt_metap()
+--
+CREATE FUNCTION bt_metap(IN relname text,
+ OUT magic int4,
+ OUT version int4,
+ OUT root int4,
+ OUT level int4,
+ OUT fastroot int4,
+ OUT fastlevel int4)
+AS 'MODULE_PATHNAME', 'bt_metap'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- bt_page_stats()
+--
+CREATE FUNCTION bt_page_stats(IN relname text, IN blkno int4,
+ OUT blkno int4,
+ OUT type "char",
+ OUT live_items int4,
+ OUT dead_items int4,
+ OUT avg_item_size int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT btpo_prev int4,
+ OUT btpo_next int4,
+ OUT btpo int4,
+ OUT btpo_flags int4)
+AS 'MODULE_PATHNAME', 'bt_page_stats'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- bt_page_items()
+--
+CREATE FUNCTION bt_page_items(IN relname text, IN blkno int4,
+ OUT itemoffset smallint,
+ OUT ctid tid,
+ OUT itemlen smallint,
+ OUT nulls bool,
+ OUT vars bool,
+ OUT data text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'bt_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_page_type()
+--
+CREATE FUNCTION brin_page_type(IN page bytea)
+RETURNS text
+AS 'MODULE_PATHNAME', 'brin_page_type'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_metapage_info()
+--
+CREATE FUNCTION brin_metapage_info(IN page bytea, OUT magic text,
+ OUT version integer, OUT pagesperrange integer, OUT lastrevmappage bigint)
+AS 'MODULE_PATHNAME', 'brin_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_revmap_data()
+--
+CREATE FUNCTION brin_revmap_data(IN page bytea,
+ OUT pages tid)
+RETURNS SETOF tid
+AS 'MODULE_PATHNAME', 'brin_revmap_data'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_page_items()
+--
+CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass,
+ OUT itemoffset int,
+ OUT blknum int,
+ OUT attnum int,
+ OUT allnulls bool,
+ OUT hasnulls bool,
+ OUT placeholder bool,
+ OUT value text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'brin_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- fsm_page_contents()
+--
+CREATE FUNCTION fsm_page_contents(IN page bytea)
+RETURNS text
+AS 'MODULE_PATHNAME', 'fsm_page_contents'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- GIN functions
+--
+
+--
+-- gin_metapage_info()
+--
+CREATE FUNCTION gin_metapage_info(IN page bytea,
+ OUT pending_head bigint,
+ OUT pending_tail bigint,
+ OUT tail_free_size int4,
+ OUT n_pending_pages bigint,
+ OUT n_pending_tuples bigint,
+ OUT n_total_pages bigint,
+ OUT n_entry_pages bigint,
+ OUT n_data_pages bigint,
+ OUT n_entries bigint,
+ OUT version int4)
+AS 'MODULE_PATHNAME', 'gin_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- gin_page_opaque_info()
+--
+CREATE FUNCTION gin_page_opaque_info(IN page bytea,
+ OUT rightlink bigint,
+ OUT maxoff int4,
+ OUT flags text[])
+AS 'MODULE_PATHNAME', 'gin_page_opaque_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- gin_leafpage_items()
+--
+CREATE FUNCTION gin_leafpage_items(IN page bytea,
+ OUT first_tid tid,
+ OUT nbytes int2,
+ OUT tids tid[])
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'gin_leafpage_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- HASH functions
+--
+
+--
+-- hash_metap()
+--
+CREATE FUNCTION hash_metap(IN page bytea,
+ OUT magic int4,
+ OUT version int4,
+ OUT ntuples double precision,
+ OUT ffactor int2,
+ OUT bsize int2,
+ OUT bmsize int2,
+ OUT bmshift int2,
+ OUT maxbucket int4,
+ OUT highmask int4,
+ OUT lowmask int4,
+ OUT ovflpoint int4,
+ OUT firstfree int4,
+ OUT nmaps int4,
+ OUT procid int4,
+ OUT spares text,
+ OUT mapp text)
+AS 'MODULE_PATHNAME', 'hash_metap'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- hash_page_stats()
+--
+CREATE FUNCTION hash_page_stats(IN page bytea, IN blkno int4,
+ OUT blkno int4,
+ OUT type "char",
+ OUT live_items int4,
+ OUT dead_items int4,
+ OUT avg_item_size int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT hasho_prevblkno int4,
+ OUT hasho_nextblkno int4,
+ OUT hasho_bucket int4,
+ OUT hasho_flag int4,
+ 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 itemlen smallint,
+ OUT nulls bool,
+ OUT vars bool,
+ OUT data text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'hash_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control
index 23c8eff..1a61c9f 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/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml
index 5d187ed..030e6a4 100644
--- a/doc/src/sgml/pageinspect.sgml
+++ b/doc/src/sgml/pageinspect.sgml
@@ -452,6 +452,106 @@ test=# SELECT first_tid, nbytes, tids[0:5] as some_tids
<varlistentry>
<term>
+ <function>hash_metap(page bytes) returns record</function>
+ <indexterm>
+ <primary>hash_metap</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+ <para>
+ <function>hash_metap</function> returns information about a hash
+ index's metapage. For example:
+<screen>
+test=# SELECT * FROM hash_metap(get_raw_page('mytab_index', 0));
+-[ RECORD 1 ]-----
+magic | 105121344
+version | 2
+ntuples | 1000000
+ffactor | 307
+bsize | 8152
+bmsize | 4096
+bmshift | 15
+maxbucket | 4095
+highmask | 8191
+lowmask | 4095
+ovflpoint | 12
+firstfree | 0
+nmaps | 1
+procid | 400
+spares | 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 0000 ...
+mapp | 1001 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 ...
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <function>hash_page_stats(page bytea, blkno int) returns record</function>
+ <indexterm>
+ <primary>hash_page_stats</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+ <para>
+ <function>hash_page_stats</function> returns summary information about
+ single pages of hash indexes. For example:
+<screen>
+test=# SELECT * FROM hash_page_stats(get_raw_page('mytab_index', 1), 1);
+-[ RECORD 1 ]-+-----
+blkno | 1
+type | b
+live_items | 211
+dead_items | 0
+avg_item_size | 16
+page_size | 8192
+free_size | 3928
+hasho_prevblkno | -1
+hasho_nextblkno | -1
+hasho_bucket | 0
+hasho_flag | 2
+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 detailed information about
+ all of the items on a hash index page. For example:
+<screen>
+test=# SELECT * FROM hash_page_items(get_raw_page('mytab_index', 1));
+ itemoffset | ctid | itemlen | nulls | vars | data
+------------+------------+---------+-------+------+-------------------------
+ 1 | (2057,114) | 16 | f | f | 00 a0 25 02 00 00 00 00
+ 2 | (4033,38) | 16 | f | f | 00 20 8d 02 00 00 00 00
+ 3 | (4223,130) | 16 | f | f | 00 80 f7 02 00 00 00 00
+ 4 | (1524,126) | 16 | f | f | 00 70 de 04 00 00 00 00
+ 5 | (5850,86) | 16 | f | f | 00 70 1a 07 00 00 00 00
+...
+ 210 | (5541,140) | 16 | f | f | 00 90 a9 ff 00 00 00 00
+ 211 | (4654,93) | 16 | f | f | 00 d0 fb ff 00 00 00 00
+
+</screen>
+ The <structfield>ctid</> column points to a heap tuple
+ (BlockIdData,OffsetNumber).
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
<function>fsm_page_contents(page bytea) returns text</function>
<indexterm>
<primary>fsm_page_contents</primary>
--
2.7.4
On Tue, Sep 20, 2016 at 5:40 AM, Jesper Pedersen <jesper.pedersen@redhat.com
wrote:
On 09/20/2016 03:19 AM, Michael Paquier wrote:
You did not get right the comments from Alvaro upthread. The following
functions are added with this patch:
function hash_metap(text)
function hash_metap_bytea(bytea)
function hash_page_items(text,integer)
function hash_page_items_bytea(bytea)
function hash_page_stats(text,integer)
function hash_page_stats_bytea(bytea,integer)Now the following set of functions would be sufficient:
function hash_metapage_info(bytea)
function hash_page_items(bytea)
function hash_page_stats(bytea)
The last time pageinspect has been updated, when BRIN functions have
been added, it has been discussed to just use (bytea) as an argument
interface and just rely on get_raw_page() to get the pages wanted, so
I think that we had better stick with that and keep things simple.Yes, I know, Alvaro and you voted for the bytea methods, and Jeff asked
for both.Attached is v3 with only the bytea based methods.
Alvaro, Michael and Jeff - Thanks for the review !
Is the 2nd "1" in this call needed?
SELECT * FROM hash_page_stats(get_raw_page('mytab_index', 1), 1)
As far as I can tell, that argument is only used to stuff into the output
field "blkno", it is not used to instruct the interpretation of the raw
page itself. It doesn't seem worthwhile to have the parameter that only
echos back to the user what the user already put in (twice). The only
existing funtions which take the blkno argument are those that don't use
the get_raw_page style.
Also, should we document what the single letter values mean in the
hash_page_stats.type column? It is not obvious that 'i' means bitmap, for
example.
Cheers,
Jeff
Hi,
I am getting a fatal error along with some warnings when trying to
apply the v3 patch using "git apply" command.
[ashu@localhost postgresql]$ git apply
0001-pageinspect-Hash-index-support_v3.patch
0001-pageinspect-Hash-index-support_v3.patch:29: trailing whitespace.
brinfuncs.o ginfuncs.o hashfuncs.o $(WIN32RES)
0001-pageinspect-Hash-index-support_v3.patch:36: trailing whitespace.
DATA = pageinspect--1.6.sql pageinspect--1.5--1.6.sql \
0001-pageinspect-Hash-index-support_v3.patch:37: trailing whitespace.
pageinspect--1.4--1.5.sql pageinspect--1.3--1.4.sql \
0001-pageinspect-Hash-index-support_v3.patch:38: trailing whitespace.
pageinspect--1.2--1.3.sql pageinspect--1.1--1.2.sql \
0001-pageinspect-Hash-index-support_v3.patch:39: trailing whitespace.
pageinspect--1.0--1.1.sql pageinspect--unpackaged--1.0.sql
fatal: git apply: bad git-diff - expected /dev/null on line 46
Also, i think the USAGE for hash_metap() is refering to
hash_metap_bytea(). Please correct it. I have just started reviewing
the patch, will keep on posting my comments upon further review.
Thanks.
With Regards,
Ashutosh Sharma.
EnterpriseDB: http://www.enterprisedb.com
On Tue, Sep 20, 2016 at 10:15 PM, Jeff Janes <jeff.janes@gmail.com> wrote:
On Tue, Sep 20, 2016 at 5:40 AM, Jesper Pedersen
<jesper.pedersen@redhat.com> wrote:On 09/20/2016 03:19 AM, Michael Paquier wrote:
You did not get right the comments from Alvaro upthread. The following
functions are added with this patch:
function hash_metap(text)
function hash_metap_bytea(bytea)
function hash_page_items(text,integer)
function hash_page_items_bytea(bytea)
function hash_page_stats(text,integer)
function hash_page_stats_bytea(bytea,integer)Now the following set of functions would be sufficient:
function hash_metapage_info(bytea)
function hash_page_items(bytea)
function hash_page_stats(bytea)
The last time pageinspect has been updated, when BRIN functions have
been added, it has been discussed to just use (bytea) as an argument
interface and just rely on get_raw_page() to get the pages wanted, so
I think that we had better stick with that and keep things simple.Yes, I know, Alvaro and you voted for the bytea methods, and Jeff asked
for both.Attached is v3 with only the bytea based methods.
Alvaro, Michael and Jeff - Thanks for the review !
Is the 2nd "1" in this call needed?
SELECT * FROM hash_page_stats(get_raw_page('mytab_index', 1), 1)
As far as I can tell, that argument is only used to stuff into the output
field "blkno", it is not used to instruct the interpretation of the raw page
itself. It doesn't seem worthwhile to have the parameter that only echos
back to the user what the user already put in (twice). The only existing
funtions which take the blkno argument are those that don't use the
get_raw_page style.Also, should we document what the single letter values mean in the
hash_page_stats.type column? It is not obvious that 'i' means bitmap, for
example.Cheers,
Jeff
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 09/20/2016 12:45 PM, Jeff Janes wrote:
Is the 2nd "1" in this call needed?
SELECT * FROM hash_page_stats(get_raw_page('mytab_index', 1), 1)
As far as I can tell, that argument is only used to stuff into the output
field "blkno", it is not used to instruct the interpretation of the raw
page itself. It doesn't seem worthwhile to have the parameter that only
echos back to the user what the user already put in (twice). The only
existing funtions which take the blkno argument are those that don't use
the get_raw_page style.Also, should we document what the single letter values mean in the
hash_page_stats.type column? It is not obvious that 'i' means bitmap, for
example.
Adjusted in v4. Code/doc will need an update once the CHI patch goes in.
Best regards,
Jesper
Attachments:
0001-pageinspect-Hash-index-support_v4.patchtext/x-patch; name=0001-pageinspect-Hash-index-support_v4.patchDownload
From 1f27a2bb28cc6dfea9cba015d7cceab768f67d0a Mon Sep 17 00:00:00 2001
From: jesperpedersen <jesper.pedersen@redhat.com>
Date: Fri, 5 Aug 2016 10:16:32 -0400
Subject: [PATCH] pageinspect: Hash index support
---
contrib/pageinspect/Makefile | 10 +-
contrib/pageinspect/hashfuncs.c | 403 ++++++++++++++++++++++++++
contrib/pageinspect/pageinspect--1.5--1.6.sql | 63 ++++
contrib/pageinspect/pageinspect--1.5.sql | 279 ------------------
contrib/pageinspect/pageinspect--1.6.sql | 338 +++++++++++++++++++++
contrib/pageinspect/pageinspect.control | 2 +-
doc/src/sgml/pageinspect.sgml | 104 +++++++
7 files changed, 914 insertions(+), 285 deletions(-)
create mode 100644 contrib/pageinspect/hashfuncs.c
create mode 100644 contrib/pageinspect/pageinspect--1.5--1.6.sql
delete mode 100644 contrib/pageinspect/pageinspect--1.5.sql
create mode 100644 contrib/pageinspect/pageinspect--1.6.sql
diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index a98237e..c73d728 100644
--- a/contrib/pageinspect/Makefile
+++ b/contrib/pageinspect/Makefile
@@ -2,13 +2,13 @@
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.6.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"
ifdef USE_PGXS
diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c
new file mode 100644
index 0000000..6efbf22
--- /dev/null
+++ b/contrib/pageinspect/hashfuncs.c
@@ -0,0 +1,403 @@
+/*
+ * hashfuncs.c
+ * Functions to investigate the content of HASH indexes
+ *
+ * Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * contrib/pageinspect/hashfuncs.c
+ */
+
+#include "postgres.h"
+
+#include "access/hash.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+
+PG_FUNCTION_INFO_V1(hash_metap);
+PG_FUNCTION_INFO_V1(hash_page_items);
+PG_FUNCTION_INFO_V1(hash_page_stats);
+
+/* ------------------------------------------------
+ * structure for single hash page statistics
+ * ------------------------------------------------
+ */
+typedef struct HashPageStat
+{
+ uint32 live_items;
+ uint32 dead_items;
+ uint32 page_size;
+ uint32 max_avail;
+ uint32 free_size;
+ uint32 avg_item_size;
+ char type;
+
+ /* 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)
+{
+ Page page;
+ int raw_page_size;
+ HashPageOpaque pageopaque;
+
+ raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
+
+ if (raw_page_size != BLCKSZ)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("input page too small"),
+ errdetail("Expected size %d, got %d",
+ BLCKSZ, raw_page_size)));
+
+ page = VARDATA(raw_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)));
+
+ return page;
+}
+
+/* -------------------------------------------------
+ * GetHashPageStatistics()
+ *
+ * Collect statistics of single hash page
+ * -------------------------------------------------
+ */
+static void
+GetHashPageStatistics(Page page, HashPageStat *stat)
+{
+ PageHeader phdr = (PageHeader) page;
+ OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
+ HashPageOpaque opaque = (HashPageOpaque) PageGetSpecialPointer(page);
+ int item_size = 0;
+ int off;
+
+ stat->max_avail = BLCKSZ - (BLCKSZ - phdr->pd_special + SizeOfPageHeaderData);
+
+ stat->dead_items = stat->live_items = 0;
+
+ stat->page_size = PageGetPageSize(page);
+
+ /* page type (flags) */
+ if (opaque->hasho_flag & LH_META_PAGE)
+ stat->type = 'm';
+ else if (opaque->hasho_flag & LH_OVERFLOW_PAGE)
+ stat->type = 'v';
+ else if (opaque->hasho_flag & LH_BUCKET_PAGE)
+ stat->type = 'b';
+ else if (opaque->hasho_flag & LH_BITMAP_PAGE)
+ stat->type = 'i';
+ else
+ stat->type = 'u';
+
+ /* 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++)
+ {
+ IndexTuple itup;
+
+ ItemId id = PageGetItemId(page, off);
+
+ itup = (IndexTuple) PageGetItem(page, id);
+
+ item_size += IndexTupleSize(itup);
+
+ if (!ItemIdIsDead(id))
+ stat->live_items++;
+ else
+ stat->dead_items++;
+ }
+ stat->free_size = PageGetFreeSpace(page);
+
+ if ((stat->live_items + stat->dead_items) > 0)
+ stat->avg_item_size = item_size / (stat->live_items + stat->dead_items);
+ else
+ stat->avg_item_size = 0;
+}
+
+/* ---------------------------------------------------
+ * hash_page_stats()
+ *
+ * Usage: SELECT * FROM hash_page_stats(get_raw_page('t1_hash', 1));
+ * ---------------------------------------------------
+ */
+Datum
+hash_page_stats(PG_FUNCTION_ARGS)
+{
+ bytea *raw_page = PG_GETARG_BYTEA_P(0);
+ Page page;
+ int j;
+ char *values[11];
+ HashPageStat stat;
+ Datum result;
+ HeapTuple tuple;
+ TupleDesc tupleDesc;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ page = verify_hash_page(raw_page);
+
+ /* keep compiler quiet */
+ stat.hasho_prevblkno = stat.hasho_nextblkno = InvalidBlockNumber;
+ stat.hasho_flag = stat.hasho_page_id = stat.free_size = stat.avg_item_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);
+
+ j = 0;
+ values[j++] = psprintf("%c", stat.type);
+ values[j++] = psprintf("%d", stat.live_items);
+ values[j++] = psprintf("%d", stat.dead_items);
+ values[j++] = psprintf("%d", stat.avg_item_size);
+ values[j++] = psprintf("%d", stat.page_size);
+ values[j++] = psprintf("%d", stat.free_size);
+ values[j++] = psprintf("%d", stat.hasho_prevblkno);
+ values[j++] = psprintf("%d", stat.hasho_nextblkno);
+ values[j++] = psprintf("%d", stat.hasho_bucket);
+ values[j++] = psprintf("%d", stat.hasho_flag);
+ values[j++] = psprintf("%d", stat.hasho_page_id);
+
+ tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
+ values);
+
+ result = HeapTupleGetDatum(tuple);
+
+ PG_RETURN_DATUM(result);
+}
+
+/*
+ * 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('t1_hash', 1));
+ *-------------------------------------------------------
+ */
+Datum
+hash_page_items(PG_FUNCTION_ARGS)
+{
+ bytea *raw_page = PG_GETARG_BYTEA_P(0);
+ Page page;
+ Datum result;
+ char *values[6];
+ HeapTuple tuple;
+ FuncCallContext *fctx;
+ MemoryContext mctx;
+ struct user_args *uargs;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ TupleDesc tupleDesc;
+
+ fctx = SRF_FIRSTCALL_INIT();
+
+ page = verify_hash_page(raw_page);
+
+ /*
+ * We copy the page into local storage to avoid holding pin on the
+ * buffer longer than we must, and possibly failing to release it at
+ * all if the calling query doesn't fetch all rows.
+ */
+ mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
+
+ uargs = palloc(sizeof(struct user_args));
+
+ uargs->page = palloc(BLCKSZ);
+ memcpy(uargs->page, page, BLCKSZ);
+
+ 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;
+ int off;
+ int dlen;
+ char *dump;
+ char *ptr;
+
+ id = PageGetItemId(uargs->page, uargs->offset);
+
+ if (!ItemIdIsValid(id))
+ elog(ERROR, "invalid ItemId");
+
+ itup = (IndexTuple) PageGetItem(uargs->page, id);
+
+ j = 0;
+ values[j++] = psprintf("%d", uargs->offset);
+ values[j++] = psprintf("(%u,%u)",
+ BlockIdGetBlockNumber(&(itup->t_tid.ip_blkid)),
+ itup->t_tid.ip_posid);
+ values[j++] = psprintf("%d", (int) IndexTupleSize(itup));
+ values[j++] = psprintf("%c", IndexTupleHasNulls(itup) ? 't' : 'f');
+ values[j++] = psprintf("%c", IndexTupleHasVarwidths(itup) ? 't' : 'f');
+
+ ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
+ dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info);
+ dump = palloc0(dlen * 3 + 1);
+ values[j] = dump;
+ for (off = 0; off < dlen; off++)
+ {
+ if (off > 0)
+ *dump++ = ' ';
+ sprintf(dump, "%02x", *(ptr + off) & 0xff);
+ dump += 2;
+ }
+
+ tuple = BuildTupleFromCStrings(fctx->attinmeta, values);
+ result = HeapTupleGetDatum(tuple);
+
+ uargs->offset = uargs->offset + 1;
+
+ SRF_RETURN_NEXT(fctx, result);
+ }
+ else
+ {
+ pfree(uargs->page);
+ pfree(uargs);
+ SRF_RETURN_DONE(fctx);
+ }
+}
+
+/* ------------------------------------------------
+ * hash_metap()
+ *
+ * Get a hash's meta-page information
+ *
+ * Usage: SELECT * FROM hash_metap(get_raw_page('t1_hash', 0))
+ * ------------------------------------------------
+ */
+Datum
+hash_metap(PG_FUNCTION_ARGS)
+{
+ bytea *raw_page = PG_GETARG_BYTEA_P(0);
+ Datum result;
+ Page page;
+ HashMetaPageData *metad;
+ TupleDesc tupleDesc;
+ HeapTuple tuple;
+ int i,j;
+ char *values[16];
+ char *spares;
+ char *mapp;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ page = verify_hash_page(raw_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);
+
+ j = 0;
+ values[j++] = psprintf("%d", metad->hashm_magic);
+ values[j++] = psprintf("%d", metad->hashm_version);
+ values[j++] = psprintf("%f", metad->hashm_ntuples);
+ values[j++] = psprintf("%d", metad->hashm_ffactor);
+ values[j++] = psprintf("%d", metad->hashm_bsize);
+ values[j++] = psprintf("%d", metad->hashm_bmsize);
+ values[j++] = psprintf("%d", metad->hashm_bmshift);
+ values[j++] = psprintf("%d", metad->hashm_maxbucket);
+ values[j++] = psprintf("%d", metad->hashm_highmask);
+ values[j++] = psprintf("%d", metad->hashm_lowmask);
+ values[j++] = psprintf("%d", metad->hashm_ovflpoint);
+ values[j++] = psprintf("%d", metad->hashm_firstfree);
+ values[j++] = psprintf("%d", metad->hashm_nmaps);
+ values[j++] = psprintf("%d", metad->hashm_procid);
+
+ spares = palloc0(HASH_MAX_SPLITPOINTS * 5 + 1);
+ values[j++] = spares;
+ for (i = 0; i < HASH_MAX_SPLITPOINTS; i++)
+ {
+ if (i > 0)
+ *spares++ = ' ';
+
+ sprintf(spares, "%04x", metad->hashm_spares[i]);
+ spares += 4;
+ }
+
+ mapp = palloc0(HASH_MAX_BITMAPS * 5 + 1);
+ values[j++] = mapp;
+ for (i = 0; i < HASH_MAX_BITMAPS; i++)
+ {
+ if (i > 0)
+ *mapp++ = ' ';
+
+ sprintf(mapp, "%04x", metad->hashm_mapp[i]);
+ mapp += 4;
+ }
+
+ tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
+ values);
+
+ result = HeapTupleGetDatum(tuple);
+
+ PG_RETURN_DATUM(result);
+}
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 0000000..a60df65
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.5--1.6.sql
@@ -0,0 +1,63 @@
+/* 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_metap()
+--
+CREATE FUNCTION hash_metap(IN page bytea,
+ OUT magic int4,
+ OUT version int4,
+ OUT ntuples double precision,
+ OUT ffactor int2,
+ OUT bsize int2,
+ OUT bmsize int2,
+ OUT bmshift int2,
+ OUT maxbucket int4,
+ OUT highmask int4,
+ OUT lowmask int4,
+ OUT ovflpoint int4,
+ OUT firstfree int4,
+ OUT nmaps int4,
+ OUT procid int4,
+ OUT spares text,
+ OUT mapp text)
+AS 'MODULE_PATHNAME', 'hash_metap'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- hash_page_stats()
+--
+CREATE FUNCTION hash_page_stats(IN page bytea,
+ OUT type "char",
+ OUT live_items int4,
+ OUT dead_items int4,
+ OUT avg_item_size int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT hasho_prevblkno int4,
+ OUT hasho_nextblkno int4,
+ OUT hasho_bucket int4,
+ OUT hasho_flag int4,
+ 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 itemlen smallint,
+ OUT nulls bool,
+ OUT vars bool,
+ OUT data text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'hash_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect--1.5.sql b/contrib/pageinspect/pageinspect--1.5.sql
deleted file mode 100644
index 1e40c3c..0000000
--- a/contrib/pageinspect/pageinspect--1.5.sql
+++ /dev/null
@@ -1,279 +0,0 @@
-/* contrib/pageinspect/pageinspect--1.5.sql */
-
--- complain if script is sourced in psql, rather than via CREATE EXTENSION
-\echo Use "CREATE EXTENSION pageinspect" to load this file. \quit
-
---
--- get_raw_page()
---
-CREATE FUNCTION get_raw_page(text, int4)
-RETURNS bytea
-AS 'MODULE_PATHNAME', 'get_raw_page'
-LANGUAGE C STRICT PARALLEL SAFE;
-
-CREATE FUNCTION get_raw_page(text, text, int4)
-RETURNS bytea
-AS 'MODULE_PATHNAME', 'get_raw_page_fork'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- page_header()
---
-CREATE FUNCTION page_header(IN page bytea,
- OUT lsn pg_lsn,
- OUT checksum smallint,
- OUT flags smallint,
- OUT lower smallint,
- OUT upper smallint,
- OUT special smallint,
- OUT pagesize smallint,
- OUT version smallint,
- OUT prune_xid xid)
-AS 'MODULE_PATHNAME', 'page_header'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- heap_page_items()
---
-CREATE FUNCTION heap_page_items(IN page bytea,
- OUT lp smallint,
- OUT lp_off smallint,
- OUT lp_flags smallint,
- OUT lp_len smallint,
- OUT t_xmin xid,
- OUT t_xmax xid,
- OUT t_field3 int4,
- OUT t_ctid tid,
- OUT t_infomask2 integer,
- OUT t_infomask integer,
- OUT t_hoff smallint,
- OUT t_bits text,
- OUT t_oid oid,
- OUT t_data bytea)
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'heap_page_items'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- tuple_data_split()
---
-CREATE FUNCTION tuple_data_split(rel_oid oid,
- t_data bytea,
- t_infomask integer,
- t_infomask2 integer,
- t_bits text)
-RETURNS bytea[]
-AS 'MODULE_PATHNAME','tuple_data_split'
-LANGUAGE C PARALLEL SAFE;
-
-CREATE FUNCTION tuple_data_split(rel_oid oid,
- t_data bytea,
- t_infomask integer,
- t_infomask2 integer,
- t_bits text,
- do_detoast bool)
-RETURNS bytea[]
-AS 'MODULE_PATHNAME','tuple_data_split'
-LANGUAGE C PARALLEL SAFE;
-
---
--- heap_page_item_attrs()
---
-CREATE FUNCTION heap_page_item_attrs(
- IN page bytea,
- IN rel_oid regclass,
- IN do_detoast bool,
- OUT lp smallint,
- OUT lp_off smallint,
- OUT lp_flags smallint,
- OUT lp_len smallint,
- OUT t_xmin xid,
- OUT t_xmax xid,
- OUT t_field3 int4,
- OUT t_ctid tid,
- OUT t_infomask2 integer,
- OUT t_infomask integer,
- OUT t_hoff smallint,
- OUT t_bits text,
- OUT t_oid oid,
- OUT t_attrs bytea[]
- )
-RETURNS SETOF record AS $$
-SELECT lp,
- lp_off,
- lp_flags,
- lp_len,
- t_xmin,
- t_xmax,
- t_field3,
- t_ctid,
- t_infomask2,
- t_infomask,
- t_hoff,
- t_bits,
- t_oid,
- tuple_data_split(
- rel_oid,
- t_data,
- t_infomask,
- t_infomask2,
- t_bits,
- do_detoast)
- AS t_attrs
- FROM heap_page_items(page);
-$$ LANGUAGE SQL PARALLEL SAFE;
-
-CREATE FUNCTION heap_page_item_attrs(IN page bytea, IN rel_oid regclass,
- OUT lp smallint,
- OUT lp_off smallint,
- OUT lp_flags smallint,
- OUT lp_len smallint,
- OUT t_xmin xid,
- OUT t_xmax xid,
- OUT t_field3 int4,
- OUT t_ctid tid,
- OUT t_infomask2 integer,
- OUT t_infomask integer,
- OUT t_hoff smallint,
- OUT t_bits text,
- OUT t_oid oid,
- OUT t_attrs bytea[]
- )
-RETURNS SETOF record AS $$
-SELECT * from heap_page_item_attrs(page, rel_oid, false);
-$$ LANGUAGE SQL PARALLEL SAFE;
-
---
--- bt_metap()
---
-CREATE FUNCTION bt_metap(IN relname text,
- OUT magic int4,
- OUT version int4,
- OUT root int4,
- OUT level int4,
- OUT fastroot int4,
- OUT fastlevel int4)
-AS 'MODULE_PATHNAME', 'bt_metap'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- bt_page_stats()
---
-CREATE FUNCTION bt_page_stats(IN relname text, IN blkno int4,
- OUT blkno int4,
- OUT type "char",
- OUT live_items int4,
- OUT dead_items int4,
- OUT avg_item_size int4,
- OUT page_size int4,
- OUT free_size int4,
- OUT btpo_prev int4,
- OUT btpo_next int4,
- OUT btpo int4,
- OUT btpo_flags int4)
-AS 'MODULE_PATHNAME', 'bt_page_stats'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- bt_page_items()
---
-CREATE FUNCTION bt_page_items(IN relname text, IN blkno int4,
- OUT itemoffset smallint,
- OUT ctid tid,
- OUT itemlen smallint,
- OUT nulls bool,
- OUT vars bool,
- OUT data text)
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'bt_page_items'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_page_type()
---
-CREATE FUNCTION brin_page_type(IN page bytea)
-RETURNS text
-AS 'MODULE_PATHNAME', 'brin_page_type'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_metapage_info()
---
-CREATE FUNCTION brin_metapage_info(IN page bytea, OUT magic text,
- OUT version integer, OUT pagesperrange integer, OUT lastrevmappage bigint)
-AS 'MODULE_PATHNAME', 'brin_metapage_info'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_revmap_data()
---
-CREATE FUNCTION brin_revmap_data(IN page bytea,
- OUT pages tid)
-RETURNS SETOF tid
-AS 'MODULE_PATHNAME', 'brin_revmap_data'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_page_items()
---
-CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass,
- OUT itemoffset int,
- OUT blknum int,
- OUT attnum int,
- OUT allnulls bool,
- OUT hasnulls bool,
- OUT placeholder bool,
- OUT value text)
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'brin_page_items'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- fsm_page_contents()
---
-CREATE FUNCTION fsm_page_contents(IN page bytea)
-RETURNS text
-AS 'MODULE_PATHNAME', 'fsm_page_contents'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- GIN functions
---
-
---
--- gin_metapage_info()
---
-CREATE FUNCTION gin_metapage_info(IN page bytea,
- OUT pending_head bigint,
- OUT pending_tail bigint,
- OUT tail_free_size int4,
- OUT n_pending_pages bigint,
- OUT n_pending_tuples bigint,
- OUT n_total_pages bigint,
- OUT n_entry_pages bigint,
- OUT n_data_pages bigint,
- OUT n_entries bigint,
- OUT version int4)
-AS 'MODULE_PATHNAME', 'gin_metapage_info'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- gin_page_opaque_info()
---
-CREATE FUNCTION gin_page_opaque_info(IN page bytea,
- OUT rightlink bigint,
- OUT maxoff int4,
- OUT flags text[])
-AS 'MODULE_PATHNAME', 'gin_page_opaque_info'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- gin_leafpage_items()
---
-CREATE FUNCTION gin_leafpage_items(IN page bytea,
- OUT first_tid tid,
- OUT nbytes int2,
- OUT tids tid[])
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'gin_leafpage_items'
-LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect--1.6.sql b/contrib/pageinspect/pageinspect--1.6.sql
new file mode 100644
index 0000000..65e07a9
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.6.sql
@@ -0,0 +1,338 @@
+/* contrib/pageinspect/pageinspect--1.6.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION pageinspect" to load this file. \quit
+
+--
+-- get_raw_page()
+--
+CREATE FUNCTION get_raw_page(text, int4)
+RETURNS bytea
+AS 'MODULE_PATHNAME', 'get_raw_page'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+CREATE FUNCTION get_raw_page(text, text, int4)
+RETURNS bytea
+AS 'MODULE_PATHNAME', 'get_raw_page_fork'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- page_header()
+--
+CREATE FUNCTION page_header(IN page bytea,
+ OUT lsn pg_lsn,
+ OUT checksum smallint,
+ OUT flags smallint,
+ OUT lower smallint,
+ OUT upper smallint,
+ OUT special smallint,
+ OUT pagesize smallint,
+ OUT version smallint,
+ OUT prune_xid xid)
+AS 'MODULE_PATHNAME', 'page_header'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- heap_page_items()
+--
+CREATE FUNCTION heap_page_items(IN page bytea,
+ OUT lp smallint,
+ OUT lp_off smallint,
+ OUT lp_flags smallint,
+ OUT lp_len smallint,
+ OUT t_xmin xid,
+ OUT t_xmax xid,
+ OUT t_field3 int4,
+ OUT t_ctid tid,
+ OUT t_infomask2 integer,
+ OUT t_infomask integer,
+ OUT t_hoff smallint,
+ OUT t_bits text,
+ OUT t_oid oid,
+ OUT t_data bytea)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'heap_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- tuple_data_split()
+--
+CREATE FUNCTION tuple_data_split(rel_oid oid,
+ t_data bytea,
+ t_infomask integer,
+ t_infomask2 integer,
+ t_bits text)
+RETURNS bytea[]
+AS 'MODULE_PATHNAME','tuple_data_split'
+LANGUAGE C PARALLEL SAFE;
+
+CREATE FUNCTION tuple_data_split(rel_oid oid,
+ t_data bytea,
+ t_infomask integer,
+ t_infomask2 integer,
+ t_bits text,
+ do_detoast bool)
+RETURNS bytea[]
+AS 'MODULE_PATHNAME','tuple_data_split'
+LANGUAGE C PARALLEL SAFE;
+
+--
+-- heap_page_item_attrs()
+--
+CREATE FUNCTION heap_page_item_attrs(
+ IN page bytea,
+ IN rel_oid regclass,
+ IN do_detoast bool,
+ OUT lp smallint,
+ OUT lp_off smallint,
+ OUT lp_flags smallint,
+ OUT lp_len smallint,
+ OUT t_xmin xid,
+ OUT t_xmax xid,
+ OUT t_field3 int4,
+ OUT t_ctid tid,
+ OUT t_infomask2 integer,
+ OUT t_infomask integer,
+ OUT t_hoff smallint,
+ OUT t_bits text,
+ OUT t_oid oid,
+ OUT t_attrs bytea[]
+ )
+RETURNS SETOF record AS $$
+SELECT lp,
+ lp_off,
+ lp_flags,
+ lp_len,
+ t_xmin,
+ t_xmax,
+ t_field3,
+ t_ctid,
+ t_infomask2,
+ t_infomask,
+ t_hoff,
+ t_bits,
+ t_oid,
+ tuple_data_split(
+ rel_oid,
+ t_data,
+ t_infomask,
+ t_infomask2,
+ t_bits,
+ do_detoast)
+ AS t_attrs
+ FROM heap_page_items(page);
+$$ LANGUAGE SQL PARALLEL SAFE;
+
+CREATE FUNCTION heap_page_item_attrs(IN page bytea, IN rel_oid regclass,
+ OUT lp smallint,
+ OUT lp_off smallint,
+ OUT lp_flags smallint,
+ OUT lp_len smallint,
+ OUT t_xmin xid,
+ OUT t_xmax xid,
+ OUT t_field3 int4,
+ OUT t_ctid tid,
+ OUT t_infomask2 integer,
+ OUT t_infomask integer,
+ OUT t_hoff smallint,
+ OUT t_bits text,
+ OUT t_oid oid,
+ OUT t_attrs bytea[]
+ )
+RETURNS SETOF record AS $$
+SELECT * from heap_page_item_attrs(page, rel_oid, false);
+$$ LANGUAGE SQL PARALLEL SAFE;
+
+--
+-- bt_metap()
+--
+CREATE FUNCTION bt_metap(IN relname text,
+ OUT magic int4,
+ OUT version int4,
+ OUT root int4,
+ OUT level int4,
+ OUT fastroot int4,
+ OUT fastlevel int4)
+AS 'MODULE_PATHNAME', 'bt_metap'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- bt_page_stats()
+--
+CREATE FUNCTION bt_page_stats(IN relname text, IN blkno int4,
+ OUT blkno int4,
+ OUT type "char",
+ OUT live_items int4,
+ OUT dead_items int4,
+ OUT avg_item_size int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT btpo_prev int4,
+ OUT btpo_next int4,
+ OUT btpo int4,
+ OUT btpo_flags int4)
+AS 'MODULE_PATHNAME', 'bt_page_stats'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- bt_page_items()
+--
+CREATE FUNCTION bt_page_items(IN relname text, IN blkno int4,
+ OUT itemoffset smallint,
+ OUT ctid tid,
+ OUT itemlen smallint,
+ OUT nulls bool,
+ OUT vars bool,
+ OUT data text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'bt_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_page_type()
+--
+CREATE FUNCTION brin_page_type(IN page bytea)
+RETURNS text
+AS 'MODULE_PATHNAME', 'brin_page_type'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_metapage_info()
+--
+CREATE FUNCTION brin_metapage_info(IN page bytea, OUT magic text,
+ OUT version integer, OUT pagesperrange integer, OUT lastrevmappage bigint)
+AS 'MODULE_PATHNAME', 'brin_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_revmap_data()
+--
+CREATE FUNCTION brin_revmap_data(IN page bytea,
+ OUT pages tid)
+RETURNS SETOF tid
+AS 'MODULE_PATHNAME', 'brin_revmap_data'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_page_items()
+--
+CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass,
+ OUT itemoffset int,
+ OUT blknum int,
+ OUT attnum int,
+ OUT allnulls bool,
+ OUT hasnulls bool,
+ OUT placeholder bool,
+ OUT value text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'brin_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- fsm_page_contents()
+--
+CREATE FUNCTION fsm_page_contents(IN page bytea)
+RETURNS text
+AS 'MODULE_PATHNAME', 'fsm_page_contents'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- GIN functions
+--
+
+--
+-- gin_metapage_info()
+--
+CREATE FUNCTION gin_metapage_info(IN page bytea,
+ OUT pending_head bigint,
+ OUT pending_tail bigint,
+ OUT tail_free_size int4,
+ OUT n_pending_pages bigint,
+ OUT n_pending_tuples bigint,
+ OUT n_total_pages bigint,
+ OUT n_entry_pages bigint,
+ OUT n_data_pages bigint,
+ OUT n_entries bigint,
+ OUT version int4)
+AS 'MODULE_PATHNAME', 'gin_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- gin_page_opaque_info()
+--
+CREATE FUNCTION gin_page_opaque_info(IN page bytea,
+ OUT rightlink bigint,
+ OUT maxoff int4,
+ OUT flags text[])
+AS 'MODULE_PATHNAME', 'gin_page_opaque_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- gin_leafpage_items()
+--
+CREATE FUNCTION gin_leafpage_items(IN page bytea,
+ OUT first_tid tid,
+ OUT nbytes int2,
+ OUT tids tid[])
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'gin_leafpage_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- HASH functions
+--
+
+--
+-- hash_metap()
+--
+CREATE FUNCTION hash_metap(IN page bytea,
+ OUT magic int4,
+ OUT version int4,
+ OUT ntuples double precision,
+ OUT ffactor int2,
+ OUT bsize int2,
+ OUT bmsize int2,
+ OUT bmshift int2,
+ OUT maxbucket int4,
+ OUT highmask int4,
+ OUT lowmask int4,
+ OUT ovflpoint int4,
+ OUT firstfree int4,
+ OUT nmaps int4,
+ OUT procid int4,
+ OUT spares text,
+ OUT mapp text)
+AS 'MODULE_PATHNAME', 'hash_metap'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- hash_page_stats()
+--
+CREATE FUNCTION hash_page_stats(IN page bytea,
+ OUT type "char",
+ OUT live_items int4,
+ OUT dead_items int4,
+ OUT avg_item_size int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT hasho_prevblkno int4,
+ OUT hasho_nextblkno int4,
+ OUT hasho_bucket int4,
+ OUT hasho_flag int4,
+ 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 itemlen smallint,
+ OUT nulls bool,
+ OUT vars bool,
+ OUT data text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'hash_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control
index 23c8eff..1a61c9f 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/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml
index 5d187ed..8923842 100644
--- a/doc/src/sgml/pageinspect.sgml
+++ b/doc/src/sgml/pageinspect.sgml
@@ -452,6 +452,110 @@ test=# SELECT first_tid, nbytes, tids[0:5] as some_tids
<varlistentry>
<term>
+ <function>hash_metap(page bytes) returns record</function>
+ <indexterm>
+ <primary>hash_metap</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+ <para>
+ <function>hash_metap</function> returns information about a hash
+ index's metapage. For example:
+<screen>
+test=# SELECT * FROM hash_metap(get_raw_page('mytab_index', 0));
+-[ RECORD 1 ]-----
+magic | 105121344
+version | 2
+ntuples | 1000000
+ffactor | 307
+bsize | 8152
+bmsize | 4096
+bmshift | 15
+maxbucket | 4095
+highmask | 8191
+lowmask | 4095
+ovflpoint | 12
+firstfree | 0
+nmaps | 1
+procid | 400
+spares | 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 0000 ...
+mapp | 1001 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 ...
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <function>hash_page_stats(page bytea, blkno int) returns record</function>
+ <indexterm>
+ <primary>hash_page_stats</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+ <para>
+ <function>hash_page_stats</function> returns summary information about
+ single pages of hash indexes. For example:
+<screen>
+test=# SELECT * FROM hash_page_stats(get_raw_page('mytab_index', 1));
+-[ RECORD 1 ]-+-----
+type | b
+live_items | 211
+dead_items | 0
+avg_item_size | 16
+page_size | 8192
+free_size | 3928
+hasho_prevblkno | -1
+hasho_nextblkno | -1
+hasho_bucket | 0
+hasho_flag | 2
+hasho_page_id | 65408
+</screen>
+ </para>
+ <para>
+ The type information will be '<literal>m</literal>' for a metadata page,
+ '<literal>v</literal>' for an overflow page, '<literal>b</literal>' for a bucket page,
+ '<literal>i</literal>' for a bitmap page, and '<literal>u</literal>' for an unused page.
+ </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 detailed information about
+ all of the items on a hash index page. For example:
+<screen>
+test=# SELECT * FROM hash_page_items(get_raw_page('mytab_index', 1));
+ itemoffset | ctid | itemlen | nulls | vars | data
+------------+------------+---------+-------+------+-------------------------
+ 1 | (2057,114) | 16 | f | f | 00 a0 25 02 00 00 00 00
+ 2 | (4033,38) | 16 | f | f | 00 20 8d 02 00 00 00 00
+ 3 | (4223,130) | 16 | f | f | 00 80 f7 02 00 00 00 00
+ 4 | (1524,126) | 16 | f | f | 00 70 de 04 00 00 00 00
+ 5 | (5850,86) | 16 | f | f | 00 70 1a 07 00 00 00 00
+...
+ 210 | (5541,140) | 16 | f | f | 00 90 a9 ff 00 00 00 00
+ 211 | (4654,93) | 16 | f | f | 00 d0 fb ff 00 00 00 00
+
+</screen>
+ The <structfield>ctid</> column points to a heap tuple
+ (BlockIdData,OffsetNumber).
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
<function>fsm_page_contents(page bytea) returns text</function>
<indexterm>
<primary>fsm_page_contents</primary>
--
2.7.4
Hi,
On 09/20/2016 01:46 PM, Ashutosh Sharma wrote:
I am getting a fatal error along with some warnings when trying to
apply the v3 patch using "git apply" command.[ashu@localhost postgresql]$ git apply
0001-pageinspect-Hash-index-support_v3.patch
0001-pageinspect-Hash-index-support_v3.patch:29: trailing whitespace.
brinfuncs.o ginfuncs.o hashfuncs.o $(WIN32RES)
0001-pageinspect-Hash-index-support_v3.patch:36: trailing whitespace.
DATA = pageinspect--1.6.sql pageinspect--1.5--1.6.sql \
0001-pageinspect-Hash-index-support_v3.patch:37: trailing whitespace.
pageinspect--1.4--1.5.sql pageinspect--1.3--1.4.sql \
0001-pageinspect-Hash-index-support_v3.patch:38: trailing whitespace.
pageinspect--1.2--1.3.sql pageinspect--1.1--1.2.sql \
0001-pageinspect-Hash-index-support_v3.patch:39: trailing whitespace.
pageinspect--1.0--1.1.sql pageinspect--unpackaged--1.0.sql
fatal: git apply: bad git-diff - expected /dev/null on line 46
Which platform are you on ?
The development branch is @
https://github.com/jesperpedersen/postgres/tree/pageinspect_hash
Also, i think the USAGE for hash_metap() is refering to
hash_metap_bytea(). Please correct it. I have just started reviewing
the patch, will keep on posting my comments upon further review.
Fixed in v4.
Thanks for the review.
Best regards,
Jesper
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Sep 21, 2016 at 3:25 AM, Jesper Pedersen
<jesper.pedersen@redhat.com> wrote:
On 09/20/2016 12:45 PM, Jeff Janes wrote:
Is the 2nd "1" in this call needed?
SELECT * FROM hash_page_stats(get_raw_page('mytab_index', 1), 1)
As far as I can tell, that argument is only used to stuff into the output
field "blkno", it is not used to instruct the interpretation of the raw
page itself. It doesn't seem worthwhile to have the parameter that only
echos back to the user what the user already put in (twice). The only
existing funtions which take the blkno argument are those that don't use
the get_raw_page style.Also, should we document what the single letter values mean in the
hash_page_stats.type column? It is not obvious that 'i' means bitmap, for
example.Adjusted in v4.
Thanks for the new version.
Code/doc will need an update once the CHI patch goes in.
If you are referring to the WAL support, I guess that this patch or
the other patch could just rebase and adjust as needed.
hash_page_items and hash_page_stats share a lot of common points with
their btree equivalents, still doing a refactoring would just impact
the visibility of the code, and it is wanted as educational in this
module, so let's go with things the way you suggest.
+ <para>
+ The type information will be '<literal>m</literal>' for a metadata page,
+ '<literal>v</literal>' for an overflow page,
'<literal>b</literal>' for a bucket page,
+ '<literal>i</literal>' for a bitmap page, and
'<literal>u</literal>' for an unused page.
+ </para>
Other functions don't go into this level of details, so I would just
rip out this paragraph.
The patch looks in pretty good shape to me, so I am switching it as
ready for committer.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi,
Which platform are you on ?
The development branch is @
https://github.com/jesperpedersen/postgres/tree/pageinspect_hash
I am working on centOS 7. I am still facing the issue when applying
your patch using "git apply" command but if i use 'patch' command it
works fine however i am seeing some warning messages with 'patch'
command as well.
I continued reviewing your v4 patch and here are some more comments:
1). Got below error if i pass meta page to hash_page_items(). Does
hash_page_items() accept only bucket or bitmap page as input?
postgres=# SELECT * FROM hash_page_items(get_raw_page('hash_i4_index', 0));
ERROR: invalid memory alloc request size 18446744073709551593
2). Server crashed when below query was executed on a code that was
build with cassert-enabled.
postgres=# SELECT * FROM hash_page_stats(get_raw_page('hash_i4_index', 0));
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
!> \q
The callstack is as follows:
(gdb) bt
#0 0x00007fef794f15f7 in raise () from /lib64/libc.so.6
#1 0x00007fef794f2ce8 in abort () from /lib64/libc.so.6
#2 0x0000000000967381 in ExceptionalCondition
(conditionName=0x7fef699c942d "!(((id)->lp_len != 0))",
errorType=0x7fef699c92d4 "FailedAssertion",
fileName=0x7fef699c9397 "hashfuncs.c", lineNumber=123) at assert.c:54
#3 0x00007fef699c6ed6 in GetHashPageStatistics (page=0x1060c2c "",
stat=0x7ffd76846230) at hashfuncs.c:123
#4 0x00007fef699c70a4 in hash_page_stats (fcinfo=0x7ffd768463e0) at
hashfuncs.c:169
#5 0x0000000000682e6b in ExecMakeTableFunctionResult
(funcexpr=0x10457f0, econtext=0x1044e28, argContext=0x104ee38,
expectedDesc=0x1047640,
randomAccess=0 '\000') at execQual.c:2216
#6 0x00000000006a88e2 in FunctionNext (node=0x1044d10) at nodeFunctionscan.c:94
#7 0x000000000068a3d9 in ExecScanFetch (node=0x1044d10,
accessMtd=0x6a882b <FunctionNext>, recheckMtd=0x6a8c10
<FunctionRecheck>) at execScan.c:95
#8 0x000000000068a448 in ExecScan (node=0x1044d10, accessMtd=0x6a882b
<FunctionNext>, recheckMtd=0x6a8c10 <FunctionRecheck>) at
execScan.c:145
#9 0x00000000006a8c45 in ExecFunctionScan (node=0x1044d10) at
nodeFunctionscan.c:268
#10 0x000000000067f0cf in ExecProcNode (node=0x1044d10) at execProcnode.c:449
#11 0x000000000067b40b in ExecutePlan (estate=0x1044bf8,
planstate=0x1044d10, use_parallel_mode=0 '\000', operation=CMD_SELECT,
sendTuples=1 '\001',
numberTuples=0, direction=ForwardScanDirection, dest=0x10444c8) at
execMain.c:1567
#12 0x0000000000679527 in standard_ExecutorRun (queryDesc=0xfac778,
direction=ForwardScanDirection, count=0) at execMain.c:338
#13 0x00000000006793ab in ExecutorRun (queryDesc=0xfac778,
direction=ForwardScanDirection, count=0) at execMain.c:286
#14 0x0000000000816b3e in PortalRunSelect (portal=0xfa49e8, forward=1
'\001', count=0, dest=0x10444c8) at pquery.c:948
#15 0x00000000008167d1 in PortalRun (portal=0xfa49e8,
count=9223372036854775807, isTopLevel=1 '\001', dest=0x10444c8,
altdest=0x10444c8,
completionTag=0x7ffd76846c60 "") at pquery.c:789
#16 0x0000000000810a27 in exec_simple_query (query_string=0x1007018
"SELECT * FROM hash_page_stats(get_raw_page('hash_i4_index', 0));") at
postgres.c:1094
#17 0x0000000000814b5e in PostgresMain (argc=1, argv=0xfb1d08,
dbname=0xf873d8 "postgres", username=0xfb1b70 "edb") at
postgres.c:4070
#18 0x000000000078990c in BackendRun (port=0xfacb50) at postmaster.c:4260
3). Could you please let me know, what does the hard coded values '*5'
and '+1' represents in the below lines of code. You have used them
when allocating memory for storing spare pages allocated before
certain split point and block number of bitmap pages inside
hash_metap().
spares = palloc0(HASH_MAX_SPLITPOINTS * 5 + 1);
mapp = palloc0(HASH_MAX_BITMAPS * 5 + 1);
I guess it would be better to add some comments if using any hard coded values.
4). Inside hash_page_stats(), you are first calling verify_hash_page()
to verify if it is a hash page or not and then calling
GetHashPageStatistics() to get the page stats wherein you are trying
to check what is the type of hash page. Below is the code snippet for
this,
+ /* page type (flags) */
+ if (opaque->hasho_flag & LH_META_PAGE)
+ stat->type = 'm';
+ else if (opaque->hasho_flag & LH_OVERFLOW_PAGE)
+ stat->type = 'v';
+ else if (opaque->hasho_flag & LH_BUCKET_PAGE)
+ stat->type = 'b';
+ else if (opaque->hasho_flag & LH_BITMAP_PAGE)
+ stat->type = 'i';
+ else
+ stat->type = 'u';
My question is, can we have a hash page that does not fall under the
category of Metapage, overflow page, bitmap page, bucket page? I guess
we can't have such type of hash page and incase if we found such type
of undefined page i guess we should error out instead of reading the
page because it is quite possible that the page would be corrupted.
Please let me know your thoughts on this.
5). I think we have added enough functions to show the page level
statistics but not the index level statistics like the total number of
overflow pages , bucket pages, number of free overflow pages, number
of bitmap pages etc. in the hash index. How about adding a function
that would store the index level statistics?
With Regards,
Ashutosh Sharma
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi,
On 09/21/2016 07:24 AM, Ashutosh Sharma wrote:
The development branch is @
https://github.com/jesperpedersen/postgres/tree/pageinspect_hashI am working on centOS 7. I am still facing the issue when applying
your patch using "git apply" command but if i use 'patch' command it
works fine however i am seeing some warning messages with 'patch'
command as well.
git apply w/ v4 works here, so you will have to investigate what happens
on your side.
I continued reviewing your v4 patch and here are some more comments:
1). Got below error if i pass meta page to hash_page_items(). Does
hash_page_items() accept only bucket or bitmap page as input?postgres=# SELECT * FROM hash_page_items(get_raw_page('hash_i4_index', 0));
ERROR: invalid memory alloc request size 18446744073709551593
page_stats / page_items should not be used on the metadata page.
As these functions are marked as superuser only it is expected that
people provides the correct input, especially since the raw page
structure is used as the input.
2). Server crashed when below query was executed on a code that was
build with cassert-enabled.postgres=# SELECT * FROM hash_page_stats(get_raw_page('hash_i4_index', 0));
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
!> \qThe callstack is as follows:
(gdb) bt
#0 0x00007fef794f15f7 in raise () from /lib64/libc.so.6
#1 0x00007fef794f2ce8 in abort () from /lib64/libc.so.6
#2 0x0000000000967381 in ExceptionalCondition
(conditionName=0x7fef699c942d "!(((id)->lp_len != 0))",
errorType=0x7fef699c92d4 "FailedAssertion",
fileName=0x7fef699c9397 "hashfuncs.c", lineNumber=123) at assert.c:54
#3 0x00007fef699c6ed6 in GetHashPageStatistics (page=0x1060c2c "",
stat=0x7ffd76846230) at hashfuncs.c:123
#4 0x00007fef699c70a4 in hash_page_stats (fcinfo=0x7ffd768463e0) at
hashfuncs.c:169
#5 0x0000000000682e6b in ExecMakeTableFunctionResult
(funcexpr=0x10457f0, econtext=0x1044e28, argContext=0x104ee38,
expectedDesc=0x1047640,
randomAccess=0 '\000') at execQual.c:2216
#6 0x00000000006a88e2 in FunctionNext (node=0x1044d10) at nodeFunctionscan.c:94
#7 0x000000000068a3d9 in ExecScanFetch (node=0x1044d10,
accessMtd=0x6a882b <FunctionNext>, recheckMtd=0x6a8c10
<FunctionRecheck>) at execScan.c:95
#8 0x000000000068a448 in ExecScan (node=0x1044d10, accessMtd=0x6a882b
<FunctionNext>, recheckMtd=0x6a8c10 <FunctionRecheck>) at
execScan.c:145
#9 0x00000000006a8c45 in ExecFunctionScan (node=0x1044d10) at
nodeFunctionscan.c:268
#10 0x000000000067f0cf in ExecProcNode (node=0x1044d10) at execProcnode.c:449
#11 0x000000000067b40b in ExecutePlan (estate=0x1044bf8,
planstate=0x1044d10, use_parallel_mode=0 '\000', operation=CMD_SELECT,
sendTuples=1 '\001',
numberTuples=0, direction=ForwardScanDirection, dest=0x10444c8) at
execMain.c:1567
#12 0x0000000000679527 in standard_ExecutorRun (queryDesc=0xfac778,
direction=ForwardScanDirection, count=0) at execMain.c:338
#13 0x00000000006793ab in ExecutorRun (queryDesc=0xfac778,
direction=ForwardScanDirection, count=0) at execMain.c:286
#14 0x0000000000816b3e in PortalRunSelect (portal=0xfa49e8, forward=1
'\001', count=0, dest=0x10444c8) at pquery.c:948
#15 0x00000000008167d1 in PortalRun (portal=0xfa49e8,
count=9223372036854775807, isTopLevel=1 '\001', dest=0x10444c8,
altdest=0x10444c8,
completionTag=0x7ffd76846c60 "") at pquery.c:789
#16 0x0000000000810a27 in exec_simple_query (query_string=0x1007018
"SELECT * FROM hash_page_stats(get_raw_page('hash_i4_index', 0));") at
postgres.c:1094
#17 0x0000000000814b5e in PostgresMain (argc=1, argv=0xfb1d08,
dbname=0xf873d8 "postgres", username=0xfb1b70 "edb") at
postgres.c:4070
#18 0x000000000078990c in BackendRun (port=0xfacb50) at postmaster.c:4260
Same deal here.
3). Could you please let me know, what does the hard coded values '*5'
and '+1' represents in the below lines of code. You have used them
when allocating memory for storing spare pages allocated before
certain split point and block number of bitmap pages inside
hash_metap().spares = palloc0(HASH_MAX_SPLITPOINTS * 5 + 1);
mapp = palloc0(HASH_MAX_BITMAPS * 5 + 1);
I guess it would be better to add some comments if using any hard coded values.
It is the space needed to output the values.
4). Inside hash_page_stats(), you are first calling verify_hash_page()
to verify if it is a hash page or not and
No, we assume that the page is a valid hash page structure, since there
is an explicit cast w/o any further checks.
then calling
GetHashPageStatistics() to get the page stats wherein you are trying
to check what is the type of hash page. Below is the code snippet for
this,+ /* page type (flags) */ + if (opaque->hasho_flag & LH_META_PAGE) + stat->type = 'm'; + else if (opaque->hasho_flag & LH_OVERFLOW_PAGE) + stat->type = 'v'; + else if (opaque->hasho_flag & LH_BUCKET_PAGE) + stat->type = 'b'; + else if (opaque->hasho_flag & LH_BITMAP_PAGE) + stat->type = 'i'; + else + stat->type = 'u';My question is, can we have a hash page that does not fall under the
category of Metapage, overflow page, bitmap page, bucket page? I guess
we can't have such type of hash page and incase if we found such type
of undefined page i guess we should error out instead of reading the
page because it is quite possible that the page would be corrupted.
Please let me know your thoughts on this.
The "if" statement will need updating once the CHI patch goes in, as it
defines new constants for page types.
However, as the other pageinspect function it assume that the operator
is passing valid data.
5). I think we have added enough functions to show the page level
statistics but not the index level statistics like the total number of
overflow pages , bucket pages, number of free overflow pages, number
of bitmap pages etc. in the hash index. How about adding a function
that would store the index level statistics?
Sure, additional functions could be of benefit later on. Feel free to
investigate that possibility for a future CommitFest.
Thanks for your feedback !
Best regards,
Jesper
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 09/21/2016 02:14 AM, Michael Paquier wrote:
Adjusted in v4.
Thanks for the new version.
Code/doc will need an update once the CHI patch goes in.
If you are referring to the WAL support, I guess that this patch or
the other patch could just rebase and adjust as needed.
It is the main patch [1] that defines the new constants for page type.
But I'll submit an update for pageinspect when needed.
hash_page_items and hash_page_stats share a lot of common points with
their btree equivalents, still doing a refactoring would just impact
the visibility of the code, and it is wanted as educational in this
module, so let's go with things the way you suggest.
Ok.
+ <para> + The type information will be '<literal>m</literal>' for a metadata page, + '<literal>v</literal>' for an overflow page, '<literal>b</literal>' for a bucket page, + '<literal>i</literal>' for a bitmap page, and '<literal>u</literal>' for an unused page. + </para> Other functions don't go into this level of details, so I would just rip out this paragraph.
I'll add an annotation for this part, and leave it for the committer to
decide, since Jeff wanted documentation for the 'type' information.
The patch looks in pretty good shape to me, so I am switching it as
ready for committer.
Thanks for your feedback !
Best regards,
Jesper
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Sep 21, 2016 at 9:21 PM, Jesper Pedersen
<jesper.pedersen@redhat.com> wrote:
On 09/21/2016 07:24 AM, Ashutosh Sharma wrote:
git apply w/ v4 works here, so you will have to investigate what happens on
your side.
Works here as well.
I continued reviewing your v4 patch and here are some more comments:
1). Got below error if i pass meta page to hash_page_items(). Does
hash_page_items() accept only bucket or bitmap page as input?postgres=# SELECT * FROM hash_page_items(get_raw_page('hash_i4_index',
0));
ERROR: invalid memory alloc request size 18446744073709551593page_stats / page_items should not be used on the metadata page.
As these functions are marked as superuser only it is expected that people
provides the correct input, especially since the raw page structure is used
as the input.
btree functions use the block number to do some sanity checks. You
cannot do that here as only bytea functions are available, but you
could do it in verify_hash_page by looking at the opaque data and look
at LH_META_PAGE. Then add a boolean argument into verify_hash_page to
see if the caller expects a meta page or not and just issue an error.
Actually it would be a good idea to put in those safeguards, even if I
agree with you that calling those functions is at the risk of the
user... Could you update the patch in this sense?
I had fun doing the same tests, aka running the items and stats
functions on a meta page, and the meta function on a non-meta page,
but at my surprise I did not see a crash, so perhaps I was lucky and
perhaps that was because of OSX.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi,
git apply w/ v4 works here, so you will have to investigate what happens on
your side.
Thanks, It works with v4 patch.
As these functions are marked as superuser only it is expected that people
provides the correct input, especially since the raw page structure is used
as the input.
Well, page_stats / page_items does accept bitmap page as input but
these function are not defined to read bitmap page as bitmap page
doesnot have a standard page layout. Infact if we pass bitmap page as
an input to page_stats / page_items, the output is always same. I
think we need to have a separete function to read bitmap page. And i
am not sure why should a server crash if i pass meta page to
hash_page_stats or hash_page_items.
The "if" statement will need updating once the CHI patch goes in, as it
defines new constants for page types.
Not sure if CHI patch is adding a new type of hash page other than
holding an information about split in progress. Basically my point was
can we have hash page of types other than meta page, bucket page,
overflow and bitmap page. If pageinspect tool finds a page that
doesnot fall under any of these category shouldn't it error out.
With Regards,
Ashutosh Sharma
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 09/21/2016 08:43 AM, Michael Paquier wrote:
page_stats / page_items should not be used on the metadata page.
As these functions are marked as superuser only it is expected that people
provides the correct input, especially since the raw page structure is used
as the input.btree functions use the block number to do some sanity checks. You
cannot do that here as only bytea functions are available, but you
could do it in verify_hash_page by looking at the opaque data and look
at LH_META_PAGE. Then add a boolean argument into verify_hash_page to
see if the caller expects a meta page or not and just issue an error.
Actually it would be a good idea to put in those safeguards, even if I
agree with you that calling those functions is at the risk of the
user... Could you update the patch in this sense?I had fun doing the same tests, aka running the items and stats
functions on a meta page, and the meta function on a non-meta page,
but at my surprise I did not see a crash, so perhaps I was lucky and
perhaps that was because of OSX.
Attached is v5, which add basic page verification.
Thanks for the feedback !
Best regards,
Jesper
Attachments:
0001-pageinspect-Hash-index-support_v5.patchtext/x-patch; name=0001-pageinspect-Hash-index-support_v5.patchDownload
From 05fe7b9056bcbb2f29809798229640499576c3a9 Mon Sep 17 00:00:00 2001
From: jesperpedersen <jesper.pedersen@redhat.com>
Date: Fri, 5 Aug 2016 10:16:32 -0400
Subject: [PATCH] pageinspect: Hash index support
---
contrib/pageinspect/Makefile | 10 +-
contrib/pageinspect/hashfuncs.c | 422 ++++++++++++++++++++++++++
contrib/pageinspect/pageinspect--1.5--1.6.sql | 63 ++++
contrib/pageinspect/pageinspect--1.5.sql | 279 -----------------
contrib/pageinspect/pageinspect--1.6.sql | 338 +++++++++++++++++++++
contrib/pageinspect/pageinspect.control | 2 +-
doc/src/sgml/pageinspect.sgml | 104 +++++++
7 files changed, 933 insertions(+), 285 deletions(-)
create mode 100644 contrib/pageinspect/hashfuncs.c
create mode 100644 contrib/pageinspect/pageinspect--1.5--1.6.sql
delete mode 100644 contrib/pageinspect/pageinspect--1.5.sql
create mode 100644 contrib/pageinspect/pageinspect--1.6.sql
diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index a98237e..c73d728 100644
--- a/contrib/pageinspect/Makefile
+++ b/contrib/pageinspect/Makefile
@@ -2,13 +2,13 @@
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.6.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"
ifdef USE_PGXS
diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c
new file mode 100644
index 0000000..3ee57dc
--- /dev/null
+++ b/contrib/pageinspect/hashfuncs.c
@@ -0,0 +1,422 @@
+/*
+ * hashfuncs.c
+ * Functions to investigate the content of HASH indexes
+ *
+ * Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * contrib/pageinspect/hashfuncs.c
+ */
+
+#include "postgres.h"
+
+#include "access/hash.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+
+PG_FUNCTION_INFO_V1(hash_metap);
+PG_FUNCTION_INFO_V1(hash_page_items);
+PG_FUNCTION_INFO_V1(hash_page_stats);
+
+/* ------------------------------------------------
+ * structure for single hash page statistics
+ * ------------------------------------------------
+ */
+typedef struct HashPageStat
+{
+ uint32 live_items;
+ uint32 dead_items;
+ uint32 page_size;
+ uint32 max_avail;
+ uint32 free_size;
+ uint32 avg_item_size;
+ char type;
+
+ /* 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, bool metap)
+{
+ Page page;
+ int raw_page_size;
+ HashPageOpaque pageopaque;
+
+ raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
+
+ if (raw_page_size != BLCKSZ)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("input page too small"),
+ errdetail("Expected size %d, got %d",
+ BLCKSZ, raw_page_size)));
+
+ page = VARDATA(raw_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)));
+
+ if (metap)
+ {
+ if (pageopaque->hasho_flag != LH_META_PAGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("page is not a metadata page"),
+ errdetail("Expected %08x, got %08x.",
+ LH_META_PAGE, pageopaque->hasho_flag)));
+ }
+ else
+ {
+ if (pageopaque->hasho_flag == LH_META_PAGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("page is a metadata page"),
+ errdetail("Flags %08x.",
+ pageopaque->hasho_flag)));
+ }
+
+ return page;
+}
+
+/* -------------------------------------------------
+ * GetHashPageStatistics()
+ *
+ * Collect statistics of single hash page
+ * -------------------------------------------------
+ */
+static void
+GetHashPageStatistics(Page page, HashPageStat *stat)
+{
+ PageHeader phdr = (PageHeader) page;
+ OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
+ HashPageOpaque opaque = (HashPageOpaque) PageGetSpecialPointer(page);
+ int item_size = 0;
+ int off;
+
+ stat->max_avail = BLCKSZ - (BLCKSZ - phdr->pd_special + SizeOfPageHeaderData);
+
+ stat->dead_items = stat->live_items = 0;
+
+ stat->page_size = PageGetPageSize(page);
+
+ /* page type (flags) */
+ if (opaque->hasho_flag & LH_META_PAGE)
+ stat->type = 'm';
+ else if (opaque->hasho_flag & LH_OVERFLOW_PAGE)
+ stat->type = 'v';
+ else if (opaque->hasho_flag & LH_BUCKET_PAGE)
+ stat->type = 'b';
+ else if (opaque->hasho_flag & LH_BITMAP_PAGE)
+ stat->type = 'i';
+ else
+ stat->type = 'u';
+
+ /* 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++)
+ {
+ IndexTuple itup;
+
+ ItemId id = PageGetItemId(page, off);
+
+ itup = (IndexTuple) PageGetItem(page, id);
+
+ item_size += IndexTupleSize(itup);
+
+ if (!ItemIdIsDead(id))
+ stat->live_items++;
+ else
+ stat->dead_items++;
+ }
+ stat->free_size = PageGetFreeSpace(page);
+
+ if ((stat->live_items + stat->dead_items) > 0)
+ stat->avg_item_size = item_size / (stat->live_items + stat->dead_items);
+ else
+ stat->avg_item_size = 0;
+}
+
+/* ---------------------------------------------------
+ * hash_page_stats()
+ *
+ * Usage: SELECT * FROM hash_page_stats(get_raw_page('t1_hash', 1));
+ * ---------------------------------------------------
+ */
+Datum
+hash_page_stats(PG_FUNCTION_ARGS)
+{
+ bytea *raw_page = PG_GETARG_BYTEA_P(0);
+ Page page;
+ int j;
+ char *values[11];
+ HashPageStat stat;
+ Datum result;
+ HeapTuple tuple;
+ TupleDesc tupleDesc;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ page = verify_hash_page(raw_page, false);
+
+ /* keep compiler quiet */
+ stat.hasho_prevblkno = stat.hasho_nextblkno = InvalidBlockNumber;
+ stat.hasho_flag = stat.hasho_page_id = stat.free_size = stat.avg_item_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);
+
+ j = 0;
+ values[j++] = psprintf("%c", stat.type);
+ values[j++] = psprintf("%d", stat.live_items);
+ values[j++] = psprintf("%d", stat.dead_items);
+ values[j++] = psprintf("%d", stat.avg_item_size);
+ values[j++] = psprintf("%d", stat.page_size);
+ values[j++] = psprintf("%d", stat.free_size);
+ values[j++] = psprintf("%d", stat.hasho_prevblkno);
+ values[j++] = psprintf("%d", stat.hasho_nextblkno);
+ values[j++] = psprintf("%d", stat.hasho_bucket);
+ values[j++] = psprintf("%d", stat.hasho_flag);
+ values[j++] = psprintf("%d", stat.hasho_page_id);
+
+ tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
+ values);
+
+ result = HeapTupleGetDatum(tuple);
+
+ PG_RETURN_DATUM(result);
+}
+
+/*
+ * 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('t1_hash', 1));
+ *-------------------------------------------------------
+ */
+Datum
+hash_page_items(PG_FUNCTION_ARGS)
+{
+ bytea *raw_page = PG_GETARG_BYTEA_P(0);
+ Page page;
+ Datum result;
+ char *values[6];
+ HeapTuple tuple;
+ FuncCallContext *fctx;
+ MemoryContext mctx;
+ struct user_args *uargs;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ TupleDesc tupleDesc;
+
+ fctx = SRF_FIRSTCALL_INIT();
+
+ page = verify_hash_page(raw_page, false);
+
+ /*
+ * We copy the page into local storage to avoid holding pin on the
+ * buffer longer than we must, and possibly failing to release it at
+ * all if the calling query doesn't fetch all rows.
+ */
+ mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
+
+ uargs = palloc(sizeof(struct user_args));
+
+ uargs->page = palloc(BLCKSZ);
+ memcpy(uargs->page, page, BLCKSZ);
+
+ 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;
+ int off;
+ int dlen;
+ char *dump;
+ char *ptr;
+
+ id = PageGetItemId(uargs->page, uargs->offset);
+
+ if (!ItemIdIsValid(id))
+ elog(ERROR, "invalid ItemId");
+
+ itup = (IndexTuple) PageGetItem(uargs->page, id);
+
+ j = 0;
+ values[j++] = psprintf("%d", uargs->offset);
+ values[j++] = psprintf("(%u,%u)",
+ BlockIdGetBlockNumber(&(itup->t_tid.ip_blkid)),
+ itup->t_tid.ip_posid);
+ values[j++] = psprintf("%d", (int) IndexTupleSize(itup));
+ values[j++] = psprintf("%c", IndexTupleHasNulls(itup) ? 't' : 'f');
+ values[j++] = psprintf("%c", IndexTupleHasVarwidths(itup) ? 't' : 'f');
+
+ ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
+ dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info);
+ dump = palloc0(dlen * 3 + 1);
+ values[j] = dump;
+ for (off = 0; off < dlen; off++)
+ {
+ if (off > 0)
+ *dump++ = ' ';
+ sprintf(dump, "%02x", *(ptr + off) & 0xff);
+ dump += 2;
+ }
+
+ tuple = BuildTupleFromCStrings(fctx->attinmeta, values);
+ result = HeapTupleGetDatum(tuple);
+
+ uargs->offset = uargs->offset + 1;
+
+ SRF_RETURN_NEXT(fctx, result);
+ }
+ else
+ {
+ pfree(uargs->page);
+ pfree(uargs);
+ SRF_RETURN_DONE(fctx);
+ }
+}
+
+/* ------------------------------------------------
+ * hash_metap()
+ *
+ * Get a hash's meta-page information
+ *
+ * Usage: SELECT * FROM hash_metap(get_raw_page('t1_hash', 0))
+ * ------------------------------------------------
+ */
+Datum
+hash_metap(PG_FUNCTION_ARGS)
+{
+ bytea *raw_page = PG_GETARG_BYTEA_P(0);
+ Datum result;
+ Page page;
+ HashMetaPageData *metad;
+ TupleDesc tupleDesc;
+ HeapTuple tuple;
+ int i,j;
+ char *values[16];
+ char *spares;
+ char *mapp;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ page = verify_hash_page(raw_page, true);
+
+ /* 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);
+
+ j = 0;
+ values[j++] = psprintf("%d", metad->hashm_magic);
+ values[j++] = psprintf("%d", metad->hashm_version);
+ values[j++] = psprintf("%f", metad->hashm_ntuples);
+ values[j++] = psprintf("%d", metad->hashm_ffactor);
+ values[j++] = psprintf("%d", metad->hashm_bsize);
+ values[j++] = psprintf("%d", metad->hashm_bmsize);
+ values[j++] = psprintf("%d", metad->hashm_bmshift);
+ values[j++] = psprintf("%d", metad->hashm_maxbucket);
+ values[j++] = psprintf("%d", metad->hashm_highmask);
+ values[j++] = psprintf("%d", metad->hashm_lowmask);
+ values[j++] = psprintf("%d", metad->hashm_ovflpoint);
+ values[j++] = psprintf("%d", metad->hashm_firstfree);
+ values[j++] = psprintf("%d", metad->hashm_nmaps);
+ values[j++] = psprintf("%d", metad->hashm_procid);
+
+ spares = palloc0(HASH_MAX_SPLITPOINTS * 5 + 1);
+ values[j++] = spares;
+ for (i = 0; i < HASH_MAX_SPLITPOINTS; i++)
+ {
+ if (i > 0)
+ *spares++ = ' ';
+
+ sprintf(spares, "%04x", metad->hashm_spares[i]);
+ spares += 4;
+ }
+
+ mapp = palloc0(HASH_MAX_BITMAPS * 5 + 1);
+ values[j++] = mapp;
+ for (i = 0; i < HASH_MAX_BITMAPS; i++)
+ {
+ if (i > 0)
+ *mapp++ = ' ';
+
+ sprintf(mapp, "%04x", metad->hashm_mapp[i]);
+ mapp += 4;
+ }
+
+ tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
+ values);
+
+ result = HeapTupleGetDatum(tuple);
+
+ PG_RETURN_DATUM(result);
+}
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 0000000..a60df65
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.5--1.6.sql
@@ -0,0 +1,63 @@
+/* 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_metap()
+--
+CREATE FUNCTION hash_metap(IN page bytea,
+ OUT magic int4,
+ OUT version int4,
+ OUT ntuples double precision,
+ OUT ffactor int2,
+ OUT bsize int2,
+ OUT bmsize int2,
+ OUT bmshift int2,
+ OUT maxbucket int4,
+ OUT highmask int4,
+ OUT lowmask int4,
+ OUT ovflpoint int4,
+ OUT firstfree int4,
+ OUT nmaps int4,
+ OUT procid int4,
+ OUT spares text,
+ OUT mapp text)
+AS 'MODULE_PATHNAME', 'hash_metap'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- hash_page_stats()
+--
+CREATE FUNCTION hash_page_stats(IN page bytea,
+ OUT type "char",
+ OUT live_items int4,
+ OUT dead_items int4,
+ OUT avg_item_size int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT hasho_prevblkno int4,
+ OUT hasho_nextblkno int4,
+ OUT hasho_bucket int4,
+ OUT hasho_flag int4,
+ 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 itemlen smallint,
+ OUT nulls bool,
+ OUT vars bool,
+ OUT data text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'hash_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect--1.5.sql b/contrib/pageinspect/pageinspect--1.5.sql
deleted file mode 100644
index 1e40c3c..0000000
--- a/contrib/pageinspect/pageinspect--1.5.sql
+++ /dev/null
@@ -1,279 +0,0 @@
-/* contrib/pageinspect/pageinspect--1.5.sql */
-
--- complain if script is sourced in psql, rather than via CREATE EXTENSION
-\echo Use "CREATE EXTENSION pageinspect" to load this file. \quit
-
---
--- get_raw_page()
---
-CREATE FUNCTION get_raw_page(text, int4)
-RETURNS bytea
-AS 'MODULE_PATHNAME', 'get_raw_page'
-LANGUAGE C STRICT PARALLEL SAFE;
-
-CREATE FUNCTION get_raw_page(text, text, int4)
-RETURNS bytea
-AS 'MODULE_PATHNAME', 'get_raw_page_fork'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- page_header()
---
-CREATE FUNCTION page_header(IN page bytea,
- OUT lsn pg_lsn,
- OUT checksum smallint,
- OUT flags smallint,
- OUT lower smallint,
- OUT upper smallint,
- OUT special smallint,
- OUT pagesize smallint,
- OUT version smallint,
- OUT prune_xid xid)
-AS 'MODULE_PATHNAME', 'page_header'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- heap_page_items()
---
-CREATE FUNCTION heap_page_items(IN page bytea,
- OUT lp smallint,
- OUT lp_off smallint,
- OUT lp_flags smallint,
- OUT lp_len smallint,
- OUT t_xmin xid,
- OUT t_xmax xid,
- OUT t_field3 int4,
- OUT t_ctid tid,
- OUT t_infomask2 integer,
- OUT t_infomask integer,
- OUT t_hoff smallint,
- OUT t_bits text,
- OUT t_oid oid,
- OUT t_data bytea)
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'heap_page_items'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- tuple_data_split()
---
-CREATE FUNCTION tuple_data_split(rel_oid oid,
- t_data bytea,
- t_infomask integer,
- t_infomask2 integer,
- t_bits text)
-RETURNS bytea[]
-AS 'MODULE_PATHNAME','tuple_data_split'
-LANGUAGE C PARALLEL SAFE;
-
-CREATE FUNCTION tuple_data_split(rel_oid oid,
- t_data bytea,
- t_infomask integer,
- t_infomask2 integer,
- t_bits text,
- do_detoast bool)
-RETURNS bytea[]
-AS 'MODULE_PATHNAME','tuple_data_split'
-LANGUAGE C PARALLEL SAFE;
-
---
--- heap_page_item_attrs()
---
-CREATE FUNCTION heap_page_item_attrs(
- IN page bytea,
- IN rel_oid regclass,
- IN do_detoast bool,
- OUT lp smallint,
- OUT lp_off smallint,
- OUT lp_flags smallint,
- OUT lp_len smallint,
- OUT t_xmin xid,
- OUT t_xmax xid,
- OUT t_field3 int4,
- OUT t_ctid tid,
- OUT t_infomask2 integer,
- OUT t_infomask integer,
- OUT t_hoff smallint,
- OUT t_bits text,
- OUT t_oid oid,
- OUT t_attrs bytea[]
- )
-RETURNS SETOF record AS $$
-SELECT lp,
- lp_off,
- lp_flags,
- lp_len,
- t_xmin,
- t_xmax,
- t_field3,
- t_ctid,
- t_infomask2,
- t_infomask,
- t_hoff,
- t_bits,
- t_oid,
- tuple_data_split(
- rel_oid,
- t_data,
- t_infomask,
- t_infomask2,
- t_bits,
- do_detoast)
- AS t_attrs
- FROM heap_page_items(page);
-$$ LANGUAGE SQL PARALLEL SAFE;
-
-CREATE FUNCTION heap_page_item_attrs(IN page bytea, IN rel_oid regclass,
- OUT lp smallint,
- OUT lp_off smallint,
- OUT lp_flags smallint,
- OUT lp_len smallint,
- OUT t_xmin xid,
- OUT t_xmax xid,
- OUT t_field3 int4,
- OUT t_ctid tid,
- OUT t_infomask2 integer,
- OUT t_infomask integer,
- OUT t_hoff smallint,
- OUT t_bits text,
- OUT t_oid oid,
- OUT t_attrs bytea[]
- )
-RETURNS SETOF record AS $$
-SELECT * from heap_page_item_attrs(page, rel_oid, false);
-$$ LANGUAGE SQL PARALLEL SAFE;
-
---
--- bt_metap()
---
-CREATE FUNCTION bt_metap(IN relname text,
- OUT magic int4,
- OUT version int4,
- OUT root int4,
- OUT level int4,
- OUT fastroot int4,
- OUT fastlevel int4)
-AS 'MODULE_PATHNAME', 'bt_metap'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- bt_page_stats()
---
-CREATE FUNCTION bt_page_stats(IN relname text, IN blkno int4,
- OUT blkno int4,
- OUT type "char",
- OUT live_items int4,
- OUT dead_items int4,
- OUT avg_item_size int4,
- OUT page_size int4,
- OUT free_size int4,
- OUT btpo_prev int4,
- OUT btpo_next int4,
- OUT btpo int4,
- OUT btpo_flags int4)
-AS 'MODULE_PATHNAME', 'bt_page_stats'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- bt_page_items()
---
-CREATE FUNCTION bt_page_items(IN relname text, IN blkno int4,
- OUT itemoffset smallint,
- OUT ctid tid,
- OUT itemlen smallint,
- OUT nulls bool,
- OUT vars bool,
- OUT data text)
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'bt_page_items'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_page_type()
---
-CREATE FUNCTION brin_page_type(IN page bytea)
-RETURNS text
-AS 'MODULE_PATHNAME', 'brin_page_type'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_metapage_info()
---
-CREATE FUNCTION brin_metapage_info(IN page bytea, OUT magic text,
- OUT version integer, OUT pagesperrange integer, OUT lastrevmappage bigint)
-AS 'MODULE_PATHNAME', 'brin_metapage_info'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_revmap_data()
---
-CREATE FUNCTION brin_revmap_data(IN page bytea,
- OUT pages tid)
-RETURNS SETOF tid
-AS 'MODULE_PATHNAME', 'brin_revmap_data'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_page_items()
---
-CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass,
- OUT itemoffset int,
- OUT blknum int,
- OUT attnum int,
- OUT allnulls bool,
- OUT hasnulls bool,
- OUT placeholder bool,
- OUT value text)
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'brin_page_items'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- fsm_page_contents()
---
-CREATE FUNCTION fsm_page_contents(IN page bytea)
-RETURNS text
-AS 'MODULE_PATHNAME', 'fsm_page_contents'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- GIN functions
---
-
---
--- gin_metapage_info()
---
-CREATE FUNCTION gin_metapage_info(IN page bytea,
- OUT pending_head bigint,
- OUT pending_tail bigint,
- OUT tail_free_size int4,
- OUT n_pending_pages bigint,
- OUT n_pending_tuples bigint,
- OUT n_total_pages bigint,
- OUT n_entry_pages bigint,
- OUT n_data_pages bigint,
- OUT n_entries bigint,
- OUT version int4)
-AS 'MODULE_PATHNAME', 'gin_metapage_info'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- gin_page_opaque_info()
---
-CREATE FUNCTION gin_page_opaque_info(IN page bytea,
- OUT rightlink bigint,
- OUT maxoff int4,
- OUT flags text[])
-AS 'MODULE_PATHNAME', 'gin_page_opaque_info'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- gin_leafpage_items()
---
-CREATE FUNCTION gin_leafpage_items(IN page bytea,
- OUT first_tid tid,
- OUT nbytes int2,
- OUT tids tid[])
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'gin_leafpage_items'
-LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect--1.6.sql b/contrib/pageinspect/pageinspect--1.6.sql
new file mode 100644
index 0000000..65e07a9
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.6.sql
@@ -0,0 +1,338 @@
+/* contrib/pageinspect/pageinspect--1.6.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION pageinspect" to load this file. \quit
+
+--
+-- get_raw_page()
+--
+CREATE FUNCTION get_raw_page(text, int4)
+RETURNS bytea
+AS 'MODULE_PATHNAME', 'get_raw_page'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+CREATE FUNCTION get_raw_page(text, text, int4)
+RETURNS bytea
+AS 'MODULE_PATHNAME', 'get_raw_page_fork'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- page_header()
+--
+CREATE FUNCTION page_header(IN page bytea,
+ OUT lsn pg_lsn,
+ OUT checksum smallint,
+ OUT flags smallint,
+ OUT lower smallint,
+ OUT upper smallint,
+ OUT special smallint,
+ OUT pagesize smallint,
+ OUT version smallint,
+ OUT prune_xid xid)
+AS 'MODULE_PATHNAME', 'page_header'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- heap_page_items()
+--
+CREATE FUNCTION heap_page_items(IN page bytea,
+ OUT lp smallint,
+ OUT lp_off smallint,
+ OUT lp_flags smallint,
+ OUT lp_len smallint,
+ OUT t_xmin xid,
+ OUT t_xmax xid,
+ OUT t_field3 int4,
+ OUT t_ctid tid,
+ OUT t_infomask2 integer,
+ OUT t_infomask integer,
+ OUT t_hoff smallint,
+ OUT t_bits text,
+ OUT t_oid oid,
+ OUT t_data bytea)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'heap_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- tuple_data_split()
+--
+CREATE FUNCTION tuple_data_split(rel_oid oid,
+ t_data bytea,
+ t_infomask integer,
+ t_infomask2 integer,
+ t_bits text)
+RETURNS bytea[]
+AS 'MODULE_PATHNAME','tuple_data_split'
+LANGUAGE C PARALLEL SAFE;
+
+CREATE FUNCTION tuple_data_split(rel_oid oid,
+ t_data bytea,
+ t_infomask integer,
+ t_infomask2 integer,
+ t_bits text,
+ do_detoast bool)
+RETURNS bytea[]
+AS 'MODULE_PATHNAME','tuple_data_split'
+LANGUAGE C PARALLEL SAFE;
+
+--
+-- heap_page_item_attrs()
+--
+CREATE FUNCTION heap_page_item_attrs(
+ IN page bytea,
+ IN rel_oid regclass,
+ IN do_detoast bool,
+ OUT lp smallint,
+ OUT lp_off smallint,
+ OUT lp_flags smallint,
+ OUT lp_len smallint,
+ OUT t_xmin xid,
+ OUT t_xmax xid,
+ OUT t_field3 int4,
+ OUT t_ctid tid,
+ OUT t_infomask2 integer,
+ OUT t_infomask integer,
+ OUT t_hoff smallint,
+ OUT t_bits text,
+ OUT t_oid oid,
+ OUT t_attrs bytea[]
+ )
+RETURNS SETOF record AS $$
+SELECT lp,
+ lp_off,
+ lp_flags,
+ lp_len,
+ t_xmin,
+ t_xmax,
+ t_field3,
+ t_ctid,
+ t_infomask2,
+ t_infomask,
+ t_hoff,
+ t_bits,
+ t_oid,
+ tuple_data_split(
+ rel_oid,
+ t_data,
+ t_infomask,
+ t_infomask2,
+ t_bits,
+ do_detoast)
+ AS t_attrs
+ FROM heap_page_items(page);
+$$ LANGUAGE SQL PARALLEL SAFE;
+
+CREATE FUNCTION heap_page_item_attrs(IN page bytea, IN rel_oid regclass,
+ OUT lp smallint,
+ OUT lp_off smallint,
+ OUT lp_flags smallint,
+ OUT lp_len smallint,
+ OUT t_xmin xid,
+ OUT t_xmax xid,
+ OUT t_field3 int4,
+ OUT t_ctid tid,
+ OUT t_infomask2 integer,
+ OUT t_infomask integer,
+ OUT t_hoff smallint,
+ OUT t_bits text,
+ OUT t_oid oid,
+ OUT t_attrs bytea[]
+ )
+RETURNS SETOF record AS $$
+SELECT * from heap_page_item_attrs(page, rel_oid, false);
+$$ LANGUAGE SQL PARALLEL SAFE;
+
+--
+-- bt_metap()
+--
+CREATE FUNCTION bt_metap(IN relname text,
+ OUT magic int4,
+ OUT version int4,
+ OUT root int4,
+ OUT level int4,
+ OUT fastroot int4,
+ OUT fastlevel int4)
+AS 'MODULE_PATHNAME', 'bt_metap'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- bt_page_stats()
+--
+CREATE FUNCTION bt_page_stats(IN relname text, IN blkno int4,
+ OUT blkno int4,
+ OUT type "char",
+ OUT live_items int4,
+ OUT dead_items int4,
+ OUT avg_item_size int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT btpo_prev int4,
+ OUT btpo_next int4,
+ OUT btpo int4,
+ OUT btpo_flags int4)
+AS 'MODULE_PATHNAME', 'bt_page_stats'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- bt_page_items()
+--
+CREATE FUNCTION bt_page_items(IN relname text, IN blkno int4,
+ OUT itemoffset smallint,
+ OUT ctid tid,
+ OUT itemlen smallint,
+ OUT nulls bool,
+ OUT vars bool,
+ OUT data text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'bt_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_page_type()
+--
+CREATE FUNCTION brin_page_type(IN page bytea)
+RETURNS text
+AS 'MODULE_PATHNAME', 'brin_page_type'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_metapage_info()
+--
+CREATE FUNCTION brin_metapage_info(IN page bytea, OUT magic text,
+ OUT version integer, OUT pagesperrange integer, OUT lastrevmappage bigint)
+AS 'MODULE_PATHNAME', 'brin_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_revmap_data()
+--
+CREATE FUNCTION brin_revmap_data(IN page bytea,
+ OUT pages tid)
+RETURNS SETOF tid
+AS 'MODULE_PATHNAME', 'brin_revmap_data'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_page_items()
+--
+CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass,
+ OUT itemoffset int,
+ OUT blknum int,
+ OUT attnum int,
+ OUT allnulls bool,
+ OUT hasnulls bool,
+ OUT placeholder bool,
+ OUT value text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'brin_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- fsm_page_contents()
+--
+CREATE FUNCTION fsm_page_contents(IN page bytea)
+RETURNS text
+AS 'MODULE_PATHNAME', 'fsm_page_contents'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- GIN functions
+--
+
+--
+-- gin_metapage_info()
+--
+CREATE FUNCTION gin_metapage_info(IN page bytea,
+ OUT pending_head bigint,
+ OUT pending_tail bigint,
+ OUT tail_free_size int4,
+ OUT n_pending_pages bigint,
+ OUT n_pending_tuples bigint,
+ OUT n_total_pages bigint,
+ OUT n_entry_pages bigint,
+ OUT n_data_pages bigint,
+ OUT n_entries bigint,
+ OUT version int4)
+AS 'MODULE_PATHNAME', 'gin_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- gin_page_opaque_info()
+--
+CREATE FUNCTION gin_page_opaque_info(IN page bytea,
+ OUT rightlink bigint,
+ OUT maxoff int4,
+ OUT flags text[])
+AS 'MODULE_PATHNAME', 'gin_page_opaque_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- gin_leafpage_items()
+--
+CREATE FUNCTION gin_leafpage_items(IN page bytea,
+ OUT first_tid tid,
+ OUT nbytes int2,
+ OUT tids tid[])
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'gin_leafpage_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- HASH functions
+--
+
+--
+-- hash_metap()
+--
+CREATE FUNCTION hash_metap(IN page bytea,
+ OUT magic int4,
+ OUT version int4,
+ OUT ntuples double precision,
+ OUT ffactor int2,
+ OUT bsize int2,
+ OUT bmsize int2,
+ OUT bmshift int2,
+ OUT maxbucket int4,
+ OUT highmask int4,
+ OUT lowmask int4,
+ OUT ovflpoint int4,
+ OUT firstfree int4,
+ OUT nmaps int4,
+ OUT procid int4,
+ OUT spares text,
+ OUT mapp text)
+AS 'MODULE_PATHNAME', 'hash_metap'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- hash_page_stats()
+--
+CREATE FUNCTION hash_page_stats(IN page bytea,
+ OUT type "char",
+ OUT live_items int4,
+ OUT dead_items int4,
+ OUT avg_item_size int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT hasho_prevblkno int4,
+ OUT hasho_nextblkno int4,
+ OUT hasho_bucket int4,
+ OUT hasho_flag int4,
+ 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 itemlen smallint,
+ OUT nulls bool,
+ OUT vars bool,
+ OUT data text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'hash_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control
index 23c8eff..1a61c9f 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/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml
index 5d187ed..8923842 100644
--- a/doc/src/sgml/pageinspect.sgml
+++ b/doc/src/sgml/pageinspect.sgml
@@ -452,6 +452,110 @@ test=# SELECT first_tid, nbytes, tids[0:5] as some_tids
<varlistentry>
<term>
+ <function>hash_metap(page bytes) returns record</function>
+ <indexterm>
+ <primary>hash_metap</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+ <para>
+ <function>hash_metap</function> returns information about a hash
+ index's metapage. For example:
+<screen>
+test=# SELECT * FROM hash_metap(get_raw_page('mytab_index', 0));
+-[ RECORD 1 ]-----
+magic | 105121344
+version | 2
+ntuples | 1000000
+ffactor | 307
+bsize | 8152
+bmsize | 4096
+bmshift | 15
+maxbucket | 4095
+highmask | 8191
+lowmask | 4095
+ovflpoint | 12
+firstfree | 0
+nmaps | 1
+procid | 400
+spares | 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 0000 ...
+mapp | 1001 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 ...
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <function>hash_page_stats(page bytea, blkno int) returns record</function>
+ <indexterm>
+ <primary>hash_page_stats</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+ <para>
+ <function>hash_page_stats</function> returns summary information about
+ single pages of hash indexes. For example:
+<screen>
+test=# SELECT * FROM hash_page_stats(get_raw_page('mytab_index', 1));
+-[ RECORD 1 ]-+-----
+type | b
+live_items | 211
+dead_items | 0
+avg_item_size | 16
+page_size | 8192
+free_size | 3928
+hasho_prevblkno | -1
+hasho_nextblkno | -1
+hasho_bucket | 0
+hasho_flag | 2
+hasho_page_id | 65408
+</screen>
+ </para>
+ <para>
+ The type information will be '<literal>m</literal>' for a metadata page,
+ '<literal>v</literal>' for an overflow page, '<literal>b</literal>' for a bucket page,
+ '<literal>i</literal>' for a bitmap page, and '<literal>u</literal>' for an unused page.
+ </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 detailed information about
+ all of the items on a hash index page. For example:
+<screen>
+test=# SELECT * FROM hash_page_items(get_raw_page('mytab_index', 1));
+ itemoffset | ctid | itemlen | nulls | vars | data
+------------+------------+---------+-------+------+-------------------------
+ 1 | (2057,114) | 16 | f | f | 00 a0 25 02 00 00 00 00
+ 2 | (4033,38) | 16 | f | f | 00 20 8d 02 00 00 00 00
+ 3 | (4223,130) | 16 | f | f | 00 80 f7 02 00 00 00 00
+ 4 | (1524,126) | 16 | f | f | 00 70 de 04 00 00 00 00
+ 5 | (5850,86) | 16 | f | f | 00 70 1a 07 00 00 00 00
+...
+ 210 | (5541,140) | 16 | f | f | 00 90 a9 ff 00 00 00 00
+ 211 | (4654,93) | 16 | f | f | 00 d0 fb ff 00 00 00 00
+
+</screen>
+ The <structfield>ctid</> column points to a heap tuple
+ (BlockIdData,OffsetNumber).
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
<function>fsm_page_contents(page bytea) returns text</function>
<indexterm>
<primary>fsm_page_contents</primary>
--
2.7.4
On Tue, Sep 20, 2016 at 11:14 PM, Michael Paquier <michael.paquier@gmail.com
wrote:
+ <para> + The type information will be '<literal>m</literal>' for a metadata page, + '<literal>v</literal>' for an overflow page, '<literal>b</literal>' for a bucket page, + '<literal>i</literal>' for a bitmap page, and '<literal>u</literal>' for an unused page. + </para>
Other functions don't go into this level of details, so I would just
rip out this paragraph.
I'd argue that the other functions should go into that level detail in some
places. Pageinspect is needlessly hard to use; not all precedent is good
precedent. Some of them do refer you to header files or README files,
which can be useful. But the abbreviations used here are not explained in
any header file or README file, so I think the right place to explain them
is the documentation in that case. Or change from the single-letter strings
to full name strings so they are self-documenting.
Cheers,
Jeff
On 9/21/16 9:30 AM, Jesper Pedersen wrote:
Attached is v5, which add basic page verification.
There are still some things that I found that appear to follow the old
style (btree) conventions instead the new style (brin, gin) conventions.
- Rename hash_metap to hash_metapage_info.
- Don't use BuildTupleFromCStrings(). (There is nothing inherently
wrong with it, but why convert numbers to strings and back again when it
can be done more directly.)
- Documentation for hash_page_stats still has blkno argument.
- In hash_metap, the magic field could be type bytea, so that it
displays in hex. Or text like the brin functions.
Some other comments:
- hash_metap result fields spares and mapp should be arrays of integer.
(Incidentally, the comment in hash.h talks about bitmaps[] but I think
it means hashm_mapp[].)
- As of very recently, we don't need to move pageinspect--1.5.sql to
pageinspect--1.6.sql anymore. Just add pageinspect--1.5--1.6.sql.
- In the documentation for hash_page_stats(), the "+" sign is misaligned.
- In hash_page_items, the fields itemlen, nulls, vars are always 16,
false, false. So perhaps there is no need for them. Similarly, the
hash_page_stats in hash_page_stats is always 16.
- The data field could be of type bytea.
- Add a pointer in the documentation to the relevant header file.
Bonus:
- Add subsections in the documentation (general functions, B-tree
functions, etc.)
- Add tests.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Sep 23, 2016 at 9:40 AM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:
On 9/21/16 9:30 AM, Jesper Pedersen wrote:
Attached is v5, which add basic page verification.
There are still some things that I found that appear to follow the old
style (btree) conventions instead the new style (brin, gin) conventions.- Rename hash_metap to hash_metapage_info.
- Don't use BuildTupleFromCStrings(). (There is nothing inherently
wrong with it, but why convert numbers to strings and back again when it
can be done more directly.)- Documentation for hash_page_stats still has blkno argument.
- In hash_metap, the magic field could be type bytea, so that it
displays in hex. Or text like the brin functions.Some other comments:
- hash_metap result fields spares and mapp should be arrays of integer.
how would you like to see both those arrays in tuple, right now, I
think Jesper's code is showing it as string.
(Incidentally, the comment in hash.h talks about bitmaps[] but I think
it means hashm_mapp[].)
which comment are you referring here? hashm_mapp contains block
numbers of bitmap pages.
While looking at patch, I noticed below code which seems somewhat problematic:
+ stat->max_avail = BLCKSZ - (BLCKSZ - phdr->pd_special + SizeOfPageHeaderData);
+
+ /* page type (flags) */
+ if (opaque->hasho_flag & LH_META_PAGE)
+ stat->type = 'm';
+ else if (opaque->hasho_flag & LH_OVERFLOW_PAGE)
+ stat->type = 'v';
+ else if (opaque->hasho_flag & LH_BUCKET_PAGE)
+ stat->type = 'b';
+ else if (opaque->hasho_flag & LH_BITMAP_PAGE)
+ stat->type = 'i';
In the above code, it appears that you are trying to calculate
max_avail space for all pages in same way. Don't you need to
calculate it differently for bitmap page or meta page as they don't
share the same format as other type of pages?
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 9/23/16 1:56 AM, Amit Kapila wrote:
On Fri, Sep 23, 2016 at 9:40 AM, Peter Eisentraut
- hash_metap result fields spares and mapp should be arrays of integer.
how would you like to see both those arrays in tuple, right now, I
think Jesper's code is showing it as string.
I'm not sure what you are asking here.
(Incidentally, the comment in hash.h talks about bitmaps[] but I think
it means hashm_mapp[].)which comment are you referring here? hashm_mapp contains block
numbers of bitmap pages.
The comment I'm referring to says
The blknos of these bitmap pages are kept in bitmaps[]; nmaps is the
number of currently existing bitmaps.
But there is no "bitmaps" field anywhere.
In the above code, it appears that you are trying to calculate
max_avail space for all pages in same way. Don't you need to
calculate it differently for bitmap page or meta page as they don't
share the same format as other type of pages?
Is this even useful for hash indexes? This idea came from btrees.
Neither the gin nor the brin code have this.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Sep 23, 2016 at 6:11 PM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:
On 9/23/16 1:56 AM, Amit Kapila wrote:
which comment are you referring here? hashm_mapp contains block
numbers of bitmap pages.The comment I'm referring to says
The blknos of these bitmap pages are kept in bitmaps[]; nmaps is the
number of currently existing bitmaps.But there is no "bitmaps" field anywhere.
Okay. You are right, it should be hashm_mapp.
In the above code, it appears that you are trying to calculate
max_avail space for all pages in same way. Don't you need to
calculate it differently for bitmap page or meta page as they don't
share the same format as other type of pages?Is this even useful for hash indexes?
I think so. It will be helpful for bucket and overflow pages. They
store the index tuples similar to btree. Is there a reason, why you
think it won't be useful?
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi,
On 09/23/2016 12:10 AM, Peter Eisentraut wrote:
On 9/21/16 9:30 AM, Jesper Pedersen wrote:
Attached is v5, which add basic page verification.
There are still some things that I found that appear to follow the old
style (btree) conventions instead the new style (brin, gin) conventions.- Rename hash_metap to hash_metapage_info.
Done.
- Don't use BuildTupleFromCStrings(). (There is nothing inherently
wrong with it, but why convert numbers to strings and back again when it
can be done more directly.)
Left as is, since BuildTupleFromCStrings() vs. xyzGetDatum() are equally
readable in this case. But, I can change the patch if needed.
- Documentation for hash_page_stats still has blkno argument.
Fixed.
- In hash_metap, the magic field could be type bytea, so that it
displays in hex. Or text like the brin functions.
Changed to 'text'.
Some other comments:
- hash_metap result fields spares and mapp should be arrays of integer.
B-tree and BRIN uses a 'text' field as output, so left as is.
(Incidentally, the comment in hash.h talks about bitmaps[] but I think
it means hashm_mapp[].)- As of very recently, we don't need to move pageinspect--1.5.sql to
pageinspect--1.6.sql anymore. Just add pageinspect--1.5--1.6.sql.
We need to rename the pageinspect--1.5.sql file and add the new methods,
right ? Or ?
- In the documentation for hash_page_stats(), the "+" sign is misaligned.
Fixed.
- In hash_page_items, the fields itemlen, nulls, vars are always 16,
false, false. So perhaps there is no need for them. Similarly, the
hash_page_stats in hash_page_stats is always 16.
Fixed, by removing them. We can add them back later if needed.
- The data field could be of type bytea.
Left as is, for same reasons as 'spares' and 'mapp'.
- Add a pointer in the documentation to the relevant header file.
Done.
Bonus:
- Add subsections in the documentation (general functions, B-tree
functions, etc.)
Done.
- Add tests.
Thanks for the review !
Best regards,
Jesper
Attachments:
0001-pageinspect-Hash-index-support_v6.patchtext/x-patch; name=0001-pageinspect-Hash-index-support_v6.patchDownload
From 24c3ff3cb48ca2076d589eda9027a0abe73b5aad Mon Sep 17 00:00:00 2001
From: jesperpedersen <jesper.pedersen@redhat.com>
Date: Fri, 5 Aug 2016 10:16:32 -0400
Subject: [PATCH] pageinspect: Hash index support
---
contrib/pageinspect/Makefile | 10 +-
contrib/pageinspect/hashfuncs.c | 400 ++++++++++++++++++++++++++
contrib/pageinspect/pageinspect--1.5--1.6.sql | 59 ++++
contrib/pageinspect/pageinspect--1.5.sql | 279 ------------------
contrib/pageinspect/pageinspect--1.6.sql | 346 ++++++++++++++++++++++
contrib/pageinspect/pageinspect.control | 2 +-
doc/src/sgml/pageinspect.sgml | 151 +++++++++-
7 files changed, 952 insertions(+), 295 deletions(-)
create mode 100644 contrib/pageinspect/hashfuncs.c
create mode 100644 contrib/pageinspect/pageinspect--1.5--1.6.sql
delete mode 100644 contrib/pageinspect/pageinspect--1.5.sql
create mode 100644 contrib/pageinspect/pageinspect--1.6.sql
diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index a98237e..c73d728 100644
--- a/contrib/pageinspect/Makefile
+++ b/contrib/pageinspect/Makefile
@@ -2,13 +2,13 @@
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.6.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"
ifdef USE_PGXS
diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c
new file mode 100644
index 0000000..96e72a9
--- /dev/null
+++ b/contrib/pageinspect/hashfuncs.c
@@ -0,0 +1,400 @@
+/*
+ * hashfuncs.c
+ * Functions to investigate the content of HASH indexes
+ *
+ * Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * contrib/pageinspect/hashfuncs.c
+ */
+
+#include "postgres.h"
+
+#include "access/hash.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+
+PG_FUNCTION_INFO_V1(hash_metapage_info);
+PG_FUNCTION_INFO_V1(hash_page_items);
+PG_FUNCTION_INFO_V1(hash_page_stats);
+
+/* ------------------------------------------------
+ * structure for single hash page statistics
+ * ------------------------------------------------
+ */
+typedef struct HashPageStat
+{
+ uint32 live_items;
+ uint32 dead_items;
+ uint32 page_size;
+ uint32 free_size;
+ char type;
+
+ /* 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, bool metap)
+{
+ Page page;
+ int raw_page_size;
+ HashPageOpaque pageopaque;
+
+ raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
+
+ if (raw_page_size != BLCKSZ)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("input page too small"),
+ errdetail("Expected size %d, got %d",
+ BLCKSZ, raw_page_size)));
+
+ page = VARDATA(raw_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)));
+
+ if (metap)
+ {
+ if (pageopaque->hasho_flag != LH_META_PAGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("page is not a metadata page"),
+ errdetail("Expected %08x, got %08x.",
+ LH_META_PAGE, pageopaque->hasho_flag)));
+ }
+ else
+ {
+ if (pageopaque->hasho_flag == LH_META_PAGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("page is a metadata page"),
+ errdetail("Flags %08x.",
+ pageopaque->hasho_flag)));
+ }
+
+ 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);
+
+ /* page type (flags) */
+ if (opaque->hasho_flag & LH_META_PAGE)
+ stat->type = 'm';
+ else if (opaque->hasho_flag & LH_OVERFLOW_PAGE)
+ stat->type = 'v';
+ else if (opaque->hasho_flag & LH_BUCKET_PAGE)
+ stat->type = 'b';
+ else if (opaque->hasho_flag & LH_BITMAP_PAGE)
+ stat->type = 'i';
+ else
+ stat->type = 'u';
+
+ /* 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_stats()
+ *
+ * Usage: SELECT * FROM hash_page_stats(get_raw_page('t1_hash', 1));
+ * ---------------------------------------------------
+ */
+Datum
+hash_page_stats(PG_FUNCTION_ARGS)
+{
+ bytea *raw_page = PG_GETARG_BYTEA_P(0);
+ Page page;
+ int j;
+ char *values[10];
+ HashPageStat stat;
+ Datum result;
+ HeapTuple tuple;
+ TupleDesc tupleDesc;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ page = verify_hash_page(raw_page, false);
+
+ /* 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);
+
+ j = 0;
+ values[j++] = psprintf("%c", stat.type);
+ values[j++] = psprintf("%d", stat.live_items);
+ values[j++] = psprintf("%d", stat.dead_items);
+ values[j++] = psprintf("%d", stat.page_size);
+ values[j++] = psprintf("%d", stat.free_size);
+ values[j++] = psprintf("%d", stat.hasho_prevblkno);
+ values[j++] = psprintf("%d", stat.hasho_nextblkno);
+ values[j++] = psprintf("%d", stat.hasho_bucket);
+ values[j++] = psprintf("%d", stat.hasho_flag);
+ values[j++] = psprintf("%d", stat.hasho_page_id);
+
+ tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
+ values);
+
+ result = HeapTupleGetDatum(tuple);
+
+ PG_RETURN_DATUM(result);
+}
+
+/*
+ * 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('t1_hash', 1));
+ *-------------------------------------------------------
+ */
+Datum
+hash_page_items(PG_FUNCTION_ARGS)
+{
+ bytea *raw_page = PG_GETARG_BYTEA_P(0);
+ Page page;
+ Datum result;
+ char *values[3];
+ HeapTuple tuple;
+ FuncCallContext *fctx;
+ MemoryContext mctx;
+ struct user_args *uargs;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ TupleDesc tupleDesc;
+
+ fctx = SRF_FIRSTCALL_INIT();
+
+ page = verify_hash_page(raw_page, false);
+
+ /*
+ * We copy the page into local storage to avoid holding pin on the
+ * buffer longer than we must, and possibly failing to release it at
+ * all if the calling query doesn't fetch all rows.
+ */
+ mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
+
+ uargs = palloc(sizeof(struct user_args));
+
+ uargs->page = palloc(BLCKSZ);
+ memcpy(uargs->page, page, BLCKSZ);
+
+ 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;
+ int off;
+ int dlen;
+ char *dump;
+ char *ptr;
+
+ id = PageGetItemId(uargs->page, uargs->offset);
+
+ if (!ItemIdIsValid(id))
+ elog(ERROR, "invalid ItemId");
+
+ itup = (IndexTuple) PageGetItem(uargs->page, id);
+
+ j = 0;
+ values[j++] = psprintf("%d", uargs->offset);
+ values[j++] = psprintf("(%u,%u)",
+ BlockIdGetBlockNumber(&(itup->t_tid.ip_blkid)),
+ itup->t_tid.ip_posid);
+
+ ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
+ dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info);
+ dump = palloc0(dlen * 3 + 1);
+ values[j] = dump;
+ for (off = 0; off < dlen; off++)
+ {
+ if (off > 0)
+ *dump++ = ' ';
+ sprintf(dump, "%02x", *(ptr + off) & 0xff);
+ dump += 2;
+ }
+
+ tuple = BuildTupleFromCStrings(fctx->attinmeta, values);
+ result = HeapTupleGetDatum(tuple);
+
+ uargs->offset = uargs->offset + 1;
+
+ SRF_RETURN_NEXT(fctx, result);
+ }
+ else
+ {
+ pfree(uargs->page);
+ pfree(uargs);
+ SRF_RETURN_DONE(fctx);
+ }
+}
+
+/* ------------------------------------------------
+ * hash_metapage_info()
+ *
+ * Get a hash's meta-page information
+ *
+ * Usage: SELECT * FROM hash_metapage_info(get_raw_page('t1_hash', 0))
+ * ------------------------------------------------
+ */
+Datum
+hash_metapage_info(PG_FUNCTION_ARGS)
+{
+ bytea *raw_page = PG_GETARG_BYTEA_P(0);
+ Datum result;
+ Page page;
+ HashMetaPageData *metad;
+ TupleDesc tupleDesc;
+ HeapTuple tuple;
+ int i,j;
+ char *values[16];
+ char *spares;
+ char *mapp;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ page = verify_hash_page(raw_page, true);
+
+ /* 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);
+
+ j = 0;
+ values[j++] = psprintf("0x%08X", metad->hashm_magic);
+ values[j++] = psprintf("%d", metad->hashm_version);
+ values[j++] = psprintf("%f", metad->hashm_ntuples);
+ values[j++] = psprintf("%d", metad->hashm_ffactor);
+ values[j++] = psprintf("%d", metad->hashm_bsize);
+ values[j++] = psprintf("%d", metad->hashm_bmsize);
+ values[j++] = psprintf("%d", metad->hashm_bmshift);
+ values[j++] = psprintf("%d", metad->hashm_maxbucket);
+ values[j++] = psprintf("%d", metad->hashm_highmask);
+ values[j++] = psprintf("%d", metad->hashm_lowmask);
+ values[j++] = psprintf("%d", metad->hashm_ovflpoint);
+ values[j++] = psprintf("%d", metad->hashm_firstfree);
+ values[j++] = psprintf("%d", metad->hashm_nmaps);
+ values[j++] = psprintf("%d", metad->hashm_procid);
+
+ spares = palloc0(HASH_MAX_SPLITPOINTS * 5 + 1);
+ values[j++] = spares;
+ for (i = 0; i < HASH_MAX_SPLITPOINTS; i++)
+ {
+ if (i > 0)
+ *spares++ = ' ';
+
+ sprintf(spares, "%04x", metad->hashm_spares[i]);
+ spares += 4;
+ }
+
+ mapp = palloc0(HASH_MAX_BITMAPS * 5 + 1);
+ values[j++] = mapp;
+ for (i = 0; i < HASH_MAX_BITMAPS; i++)
+ {
+ if (i > 0)
+ *mapp++ = ' ';
+
+ sprintf(mapp, "%04x", metad->hashm_mapp[i]);
+ mapp += 4;
+ }
+
+ tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
+ values);
+
+ result = HeapTupleGetDatum(tuple);
+
+ PG_RETURN_DATUM(result);
+}
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 0000000..f57a213
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.5--1.6.sql
@@ -0,0 +1,59 @@
+/* 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_metapage_info()
+--
+CREATE FUNCTION hash_metapage_info(IN page bytea,
+ OUT magic text,
+ OUT version int4,
+ OUT ntuples double precision,
+ OUT ffactor int2,
+ OUT bsize int2,
+ OUT bmsize int2,
+ OUT bmshift int2,
+ OUT maxbucket int4,
+ OUT highmask int4,
+ OUT lowmask int4,
+ OUT ovflpoint int4,
+ OUT firstfree int4,
+ OUT nmaps int4,
+ OUT procid int4,
+ OUT spares text,
+ OUT mapp text)
+AS 'MODULE_PATHNAME', 'hash_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- hash_page_stats()
+--
+CREATE FUNCTION hash_page_stats(IN page bytea,
+ OUT type "char",
+ OUT live_items int4,
+ OUT dead_items int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT hasho_prevblkno int4,
+ OUT hasho_nextblkno int4,
+ OUT hasho_bucket int4,
+ OUT hasho_flag int4,
+ 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 text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'hash_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect--1.5.sql b/contrib/pageinspect/pageinspect--1.5.sql
deleted file mode 100644
index 1e40c3c..0000000
--- a/contrib/pageinspect/pageinspect--1.5.sql
+++ /dev/null
@@ -1,279 +0,0 @@
-/* contrib/pageinspect/pageinspect--1.5.sql */
-
--- complain if script is sourced in psql, rather than via CREATE EXTENSION
-\echo Use "CREATE EXTENSION pageinspect" to load this file. \quit
-
---
--- get_raw_page()
---
-CREATE FUNCTION get_raw_page(text, int4)
-RETURNS bytea
-AS 'MODULE_PATHNAME', 'get_raw_page'
-LANGUAGE C STRICT PARALLEL SAFE;
-
-CREATE FUNCTION get_raw_page(text, text, int4)
-RETURNS bytea
-AS 'MODULE_PATHNAME', 'get_raw_page_fork'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- page_header()
---
-CREATE FUNCTION page_header(IN page bytea,
- OUT lsn pg_lsn,
- OUT checksum smallint,
- OUT flags smallint,
- OUT lower smallint,
- OUT upper smallint,
- OUT special smallint,
- OUT pagesize smallint,
- OUT version smallint,
- OUT prune_xid xid)
-AS 'MODULE_PATHNAME', 'page_header'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- heap_page_items()
---
-CREATE FUNCTION heap_page_items(IN page bytea,
- OUT lp smallint,
- OUT lp_off smallint,
- OUT lp_flags smallint,
- OUT lp_len smallint,
- OUT t_xmin xid,
- OUT t_xmax xid,
- OUT t_field3 int4,
- OUT t_ctid tid,
- OUT t_infomask2 integer,
- OUT t_infomask integer,
- OUT t_hoff smallint,
- OUT t_bits text,
- OUT t_oid oid,
- OUT t_data bytea)
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'heap_page_items'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- tuple_data_split()
---
-CREATE FUNCTION tuple_data_split(rel_oid oid,
- t_data bytea,
- t_infomask integer,
- t_infomask2 integer,
- t_bits text)
-RETURNS bytea[]
-AS 'MODULE_PATHNAME','tuple_data_split'
-LANGUAGE C PARALLEL SAFE;
-
-CREATE FUNCTION tuple_data_split(rel_oid oid,
- t_data bytea,
- t_infomask integer,
- t_infomask2 integer,
- t_bits text,
- do_detoast bool)
-RETURNS bytea[]
-AS 'MODULE_PATHNAME','tuple_data_split'
-LANGUAGE C PARALLEL SAFE;
-
---
--- heap_page_item_attrs()
---
-CREATE FUNCTION heap_page_item_attrs(
- IN page bytea,
- IN rel_oid regclass,
- IN do_detoast bool,
- OUT lp smallint,
- OUT lp_off smallint,
- OUT lp_flags smallint,
- OUT lp_len smallint,
- OUT t_xmin xid,
- OUT t_xmax xid,
- OUT t_field3 int4,
- OUT t_ctid tid,
- OUT t_infomask2 integer,
- OUT t_infomask integer,
- OUT t_hoff smallint,
- OUT t_bits text,
- OUT t_oid oid,
- OUT t_attrs bytea[]
- )
-RETURNS SETOF record AS $$
-SELECT lp,
- lp_off,
- lp_flags,
- lp_len,
- t_xmin,
- t_xmax,
- t_field3,
- t_ctid,
- t_infomask2,
- t_infomask,
- t_hoff,
- t_bits,
- t_oid,
- tuple_data_split(
- rel_oid,
- t_data,
- t_infomask,
- t_infomask2,
- t_bits,
- do_detoast)
- AS t_attrs
- FROM heap_page_items(page);
-$$ LANGUAGE SQL PARALLEL SAFE;
-
-CREATE FUNCTION heap_page_item_attrs(IN page bytea, IN rel_oid regclass,
- OUT lp smallint,
- OUT lp_off smallint,
- OUT lp_flags smallint,
- OUT lp_len smallint,
- OUT t_xmin xid,
- OUT t_xmax xid,
- OUT t_field3 int4,
- OUT t_ctid tid,
- OUT t_infomask2 integer,
- OUT t_infomask integer,
- OUT t_hoff smallint,
- OUT t_bits text,
- OUT t_oid oid,
- OUT t_attrs bytea[]
- )
-RETURNS SETOF record AS $$
-SELECT * from heap_page_item_attrs(page, rel_oid, false);
-$$ LANGUAGE SQL PARALLEL SAFE;
-
---
--- bt_metap()
---
-CREATE FUNCTION bt_metap(IN relname text,
- OUT magic int4,
- OUT version int4,
- OUT root int4,
- OUT level int4,
- OUT fastroot int4,
- OUT fastlevel int4)
-AS 'MODULE_PATHNAME', 'bt_metap'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- bt_page_stats()
---
-CREATE FUNCTION bt_page_stats(IN relname text, IN blkno int4,
- OUT blkno int4,
- OUT type "char",
- OUT live_items int4,
- OUT dead_items int4,
- OUT avg_item_size int4,
- OUT page_size int4,
- OUT free_size int4,
- OUT btpo_prev int4,
- OUT btpo_next int4,
- OUT btpo int4,
- OUT btpo_flags int4)
-AS 'MODULE_PATHNAME', 'bt_page_stats'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- bt_page_items()
---
-CREATE FUNCTION bt_page_items(IN relname text, IN blkno int4,
- OUT itemoffset smallint,
- OUT ctid tid,
- OUT itemlen smallint,
- OUT nulls bool,
- OUT vars bool,
- OUT data text)
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'bt_page_items'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_page_type()
---
-CREATE FUNCTION brin_page_type(IN page bytea)
-RETURNS text
-AS 'MODULE_PATHNAME', 'brin_page_type'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_metapage_info()
---
-CREATE FUNCTION brin_metapage_info(IN page bytea, OUT magic text,
- OUT version integer, OUT pagesperrange integer, OUT lastrevmappage bigint)
-AS 'MODULE_PATHNAME', 'brin_metapage_info'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_revmap_data()
---
-CREATE FUNCTION brin_revmap_data(IN page bytea,
- OUT pages tid)
-RETURNS SETOF tid
-AS 'MODULE_PATHNAME', 'brin_revmap_data'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_page_items()
---
-CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass,
- OUT itemoffset int,
- OUT blknum int,
- OUT attnum int,
- OUT allnulls bool,
- OUT hasnulls bool,
- OUT placeholder bool,
- OUT value text)
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'brin_page_items'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- fsm_page_contents()
---
-CREATE FUNCTION fsm_page_contents(IN page bytea)
-RETURNS text
-AS 'MODULE_PATHNAME', 'fsm_page_contents'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- GIN functions
---
-
---
--- gin_metapage_info()
---
-CREATE FUNCTION gin_metapage_info(IN page bytea,
- OUT pending_head bigint,
- OUT pending_tail bigint,
- OUT tail_free_size int4,
- OUT n_pending_pages bigint,
- OUT n_pending_tuples bigint,
- OUT n_total_pages bigint,
- OUT n_entry_pages bigint,
- OUT n_data_pages bigint,
- OUT n_entries bigint,
- OUT version int4)
-AS 'MODULE_PATHNAME', 'gin_metapage_info'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- gin_page_opaque_info()
---
-CREATE FUNCTION gin_page_opaque_info(IN page bytea,
- OUT rightlink bigint,
- OUT maxoff int4,
- OUT flags text[])
-AS 'MODULE_PATHNAME', 'gin_page_opaque_info'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- gin_leafpage_items()
---
-CREATE FUNCTION gin_leafpage_items(IN page bytea,
- OUT first_tid tid,
- OUT nbytes int2,
- OUT tids tid[])
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'gin_leafpage_items'
-LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect--1.6.sql b/contrib/pageinspect/pageinspect--1.6.sql
new file mode 100644
index 0000000..b4a918f
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.6.sql
@@ -0,0 +1,346 @@
+/* contrib/pageinspect/pageinspect--1.6.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION pageinspect" to load this file. \quit
+
+--
+-- General functions
+--
+
+--
+-- get_raw_page()
+--
+CREATE FUNCTION get_raw_page(text, int4)
+RETURNS bytea
+AS 'MODULE_PATHNAME', 'get_raw_page'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+CREATE FUNCTION get_raw_page(text, text, int4)
+RETURNS bytea
+AS 'MODULE_PATHNAME', 'get_raw_page_fork'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- page_header()
+--
+CREATE FUNCTION page_header(IN page bytea,
+ OUT lsn pg_lsn,
+ OUT checksum smallint,
+ OUT flags smallint,
+ OUT lower smallint,
+ OUT upper smallint,
+ OUT special smallint,
+ OUT pagesize smallint,
+ OUT version smallint,
+ OUT prune_xid xid)
+AS 'MODULE_PATHNAME', 'page_header'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- heap_page_items()
+--
+CREATE FUNCTION heap_page_items(IN page bytea,
+ OUT lp smallint,
+ OUT lp_off smallint,
+ OUT lp_flags smallint,
+ OUT lp_len smallint,
+ OUT t_xmin xid,
+ OUT t_xmax xid,
+ OUT t_field3 int4,
+ OUT t_ctid tid,
+ OUT t_infomask2 integer,
+ OUT t_infomask integer,
+ OUT t_hoff smallint,
+ OUT t_bits text,
+ OUT t_oid oid,
+ OUT t_data bytea)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'heap_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- tuple_data_split()
+--
+CREATE FUNCTION tuple_data_split(rel_oid oid,
+ t_data bytea,
+ t_infomask integer,
+ t_infomask2 integer,
+ t_bits text)
+RETURNS bytea[]
+AS 'MODULE_PATHNAME','tuple_data_split'
+LANGUAGE C PARALLEL SAFE;
+
+CREATE FUNCTION tuple_data_split(rel_oid oid,
+ t_data bytea,
+ t_infomask integer,
+ t_infomask2 integer,
+ t_bits text,
+ do_detoast bool)
+RETURNS bytea[]
+AS 'MODULE_PATHNAME','tuple_data_split'
+LANGUAGE C PARALLEL SAFE;
+
+--
+-- heap_page_item_attrs()
+--
+CREATE FUNCTION heap_page_item_attrs(
+ IN page bytea,
+ IN rel_oid regclass,
+ IN do_detoast bool,
+ OUT lp smallint,
+ OUT lp_off smallint,
+ OUT lp_flags smallint,
+ OUT lp_len smallint,
+ OUT t_xmin xid,
+ OUT t_xmax xid,
+ OUT t_field3 int4,
+ OUT t_ctid tid,
+ OUT t_infomask2 integer,
+ OUT t_infomask integer,
+ OUT t_hoff smallint,
+ OUT t_bits text,
+ OUT t_oid oid,
+ OUT t_attrs bytea[]
+ )
+RETURNS SETOF record AS $$
+SELECT lp,
+ lp_off,
+ lp_flags,
+ lp_len,
+ t_xmin,
+ t_xmax,
+ t_field3,
+ t_ctid,
+ t_infomask2,
+ t_infomask,
+ t_hoff,
+ t_bits,
+ t_oid,
+ tuple_data_split(
+ rel_oid,
+ t_data,
+ t_infomask,
+ t_infomask2,
+ t_bits,
+ do_detoast)
+ AS t_attrs
+ FROM heap_page_items(page);
+$$ LANGUAGE SQL PARALLEL SAFE;
+
+CREATE FUNCTION heap_page_item_attrs(IN page bytea, IN rel_oid regclass,
+ OUT lp smallint,
+ OUT lp_off smallint,
+ OUT lp_flags smallint,
+ OUT lp_len smallint,
+ OUT t_xmin xid,
+ OUT t_xmax xid,
+ OUT t_field3 int4,
+ OUT t_ctid tid,
+ OUT t_infomask2 integer,
+ OUT t_infomask integer,
+ OUT t_hoff smallint,
+ OUT t_bits text,
+ OUT t_oid oid,
+ OUT t_attrs bytea[]
+ )
+RETURNS SETOF record AS $$
+SELECT * from heap_page_item_attrs(page, rel_oid, false);
+$$ LANGUAGE SQL PARALLEL SAFE;
+
+--
+-- fsm_page_contents()
+--
+CREATE FUNCTION fsm_page_contents(IN page bytea)
+RETURNS text
+AS 'MODULE_PATHNAME', 'fsm_page_contents'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- B-tree functions
+--
+
+--
+-- bt_metap()
+--
+CREATE FUNCTION bt_metap(IN relname text,
+ OUT magic int4,
+ OUT version int4,
+ OUT root int4,
+ OUT level int4,
+ OUT fastroot int4,
+ OUT fastlevel int4)
+AS 'MODULE_PATHNAME', 'bt_metap'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- bt_page_stats()
+--
+CREATE FUNCTION bt_page_stats(IN relname text, IN blkno int4,
+ OUT blkno int4,
+ OUT type "char",
+ OUT live_items int4,
+ OUT dead_items int4,
+ OUT avg_item_size int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT btpo_prev int4,
+ OUT btpo_next int4,
+ OUT btpo int4,
+ OUT btpo_flags int4)
+AS 'MODULE_PATHNAME', 'bt_page_stats'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- bt_page_items()
+--
+CREATE FUNCTION bt_page_items(IN relname text, IN blkno int4,
+ OUT itemoffset smallint,
+ OUT ctid tid,
+ OUT itemlen smallint,
+ OUT nulls bool,
+ OUT vars bool,
+ OUT data text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'bt_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- BRIN functions
+--
+
+--
+-- brin_page_type()
+--
+CREATE FUNCTION brin_page_type(IN page bytea)
+RETURNS text
+AS 'MODULE_PATHNAME', 'brin_page_type'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_metapage_info()
+--
+CREATE FUNCTION brin_metapage_info(IN page bytea, OUT magic text,
+ OUT version integer, OUT pagesperrange integer, OUT lastrevmappage bigint)
+AS 'MODULE_PATHNAME', 'brin_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_revmap_data()
+--
+CREATE FUNCTION brin_revmap_data(IN page bytea,
+ OUT pages tid)
+RETURNS SETOF tid
+AS 'MODULE_PATHNAME', 'brin_revmap_data'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_page_items()
+--
+CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass,
+ OUT itemoffset int,
+ OUT blknum int,
+ OUT attnum int,
+ OUT allnulls bool,
+ OUT hasnulls bool,
+ OUT placeholder bool,
+ OUT value text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'brin_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- GIN functions
+--
+
+--
+-- gin_metapage_info()
+--
+CREATE FUNCTION gin_metapage_info(IN page bytea,
+ OUT pending_head bigint,
+ OUT pending_tail bigint,
+ OUT tail_free_size int4,
+ OUT n_pending_pages bigint,
+ OUT n_pending_tuples bigint,
+ OUT n_total_pages bigint,
+ OUT n_entry_pages bigint,
+ OUT n_data_pages bigint,
+ OUT n_entries bigint,
+ OUT version int4)
+AS 'MODULE_PATHNAME', 'gin_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- gin_page_opaque_info()
+--
+CREATE FUNCTION gin_page_opaque_info(IN page bytea,
+ OUT rightlink bigint,
+ OUT maxoff int4,
+ OUT flags text[])
+AS 'MODULE_PATHNAME', 'gin_page_opaque_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- gin_leafpage_items()
+--
+CREATE FUNCTION gin_leafpage_items(IN page bytea,
+ OUT first_tid tid,
+ OUT nbytes int2,
+ OUT tids tid[])
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'gin_leafpage_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- Hash functions
+--
+
+--
+-- hash_metapage_info()
+--
+CREATE FUNCTION hash_metapage_info(IN page bytea,
+ OUT magic text,
+ OUT version int4,
+ OUT ntuples double precision,
+ OUT ffactor int2,
+ OUT bsize int2,
+ OUT bmsize int2,
+ OUT bmshift int2,
+ OUT maxbucket int4,
+ OUT highmask int4,
+ OUT lowmask int4,
+ OUT ovflpoint int4,
+ OUT firstfree int4,
+ OUT nmaps int4,
+ OUT procid int4,
+ OUT spares text,
+ OUT mapp text)
+AS 'MODULE_PATHNAME', 'hash_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- hash_page_stats()
+--
+CREATE FUNCTION hash_page_stats(IN page bytea,
+ OUT type "char",
+ OUT live_items int4,
+ OUT dead_items int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT hasho_prevblkno int4,
+ OUT hasho_nextblkno int4,
+ OUT hasho_bucket int4,
+ OUT hasho_flag int4,
+ 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 text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'hash_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control
index 23c8eff..1a61c9f 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/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml
index 5d187ed..ddad944 100644
--- a/doc/src/sgml/pageinspect.sgml
+++ b/doc/src/sgml/pageinspect.sgml
@@ -14,7 +14,7 @@
</para>
<sect2>
- <title>Functions</title>
+ <title>General functions</title>
<variablelist>
<varlistentry>
@@ -160,7 +160,36 @@ test=# SELECT * FROM heap_page_item_attrs(get_raw_page('pg_class', 0), 'pg_class
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term>
+ <function>fsm_page_contents(page bytea) returns text</function>
+ <indexterm>
+ <primary>fsm_page_contents</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+ <para>
+ <function>fsm_page_contents</function> shows the internal node structure
+ of a FSM page. The output is a multiline string, with one line per
+ node in the binary tree within the page. Only those nodes that are not
+ zero are printed. The so-called "next" pointer, which points to the
+ next slot to be returned from the page, is also printed.
+ </para>
+ <para>
+ See <filename>src/backend/storage/freespace/README</> for more
+ information on the structure of an FSM page.
+ </para>
+ </listitem>
+ </varlistentry>
+</variablelist>
+</sect2>
+ <sect2>
+ <title>B-tree functions</title>
+
+ <variablelist>
<varlistentry>
<term>
<function>bt_metap(relname text) returns record</function>
@@ -261,7 +290,13 @@ test=# SELECT * FROM bt_page_items('pg_cast_oid_index', 1);
</para>
</listitem>
</varlistentry>
+ </variablelist>
+ </sect2>
+
+ <sect2>
+ <title>BRIN functions</title>
+ <variablelist>
<varlistentry>
<term>
<function>brin_page_type(page bytea) returns text</function>
@@ -365,7 +400,13 @@ test=# SELECT * FROM brin_page_items(get_raw_page('brinidx', 5),
</para>
</listitem>
</varlistentry>
+ </variablelist>
+ </sect2>
+
+ <sect2>
+ <title>GIN functions</title>
+ <variablelist>
<varlistentry>
<term>
<function>gin_metapage_info(page bytea) returns record</function>
@@ -449,26 +490,116 @@ test=# SELECT first_tid, nbytes, tids[0:5] as some_tids
</para>
</listitem>
</varlistentry>
+ </variablelist>
+ </sect2>
+
+ <sect2>
+ <title>Hash functions</title>
+
+ <para>
+ The hash index data structures are defined in <filename>src/include/access/hash.h</>.
+ </para>
+ <variablelist>
<varlistentry>
<term>
- <function>fsm_page_contents(page bytea) returns text</function>
+ <function>hash_metapage_info(page bytes) returns record</function>
<indexterm>
- <primary>fsm_page_contents</primary>
+ <primary>hash_metapage_info</primary>
</indexterm>
</term>
<listitem>
<para>
- <function>fsm_page_contents</function> shows the internal node structure
- of a FSM page. The output is a multiline string, with one line per
- node in the binary tree within the page. Only those nodes that are not
- zero are printed. The so-called "next" pointer, which points to the
- next slot to be returned from the page, is also printed.
+ <function>hash_metapage_info</function> returns information about a hash
+ index's metapage. For example:
+<screen>
+test=# SELECT * FROM hash_metapage_info(get_raw_page('mytab_index', 0));
+-[ RECORD 1 ]-----
+magic | 0x06440640
+version | 2
+ntuples | 1000000
+ffactor | 307
+bsize | 8152
+bmsize | 4096
+bmshift | 15
+maxbucket | 4095
+highmask | 8191
+lowmask | 4095
+ovflpoint | 12
+firstfree | 0
+nmaps | 1
+procid | 400
+spares | 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 0000 ...
+mapp | 1001 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 ...
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <function>hash_page_stats(page bytea) returns record</function>
+ <indexterm>
+ <primary>hash_page_stats</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+ <para>
+ <function>hash_page_stats</function> returns summary information about
+ single pages of hash indexes. For example:
+<screen>
+test=# SELECT * FROM hash_page_stats(get_raw_page('mytab_index', 1));
+-[ RECORD 1 ]---+-----
+type | b
+live_items | 211
+dead_items | 0
+page_size | 8192
+free_size | 3928
+hasho_prevblkno | -1
+hasho_nextblkno | -1
+hasho_bucket | 0
+hasho_flag | 2
+hasho_page_id | 65408
+</screen>
</para>
<para>
- See <filename>src/backend/storage/freespace/README</> for more
- information on the structure of an FSM page.
+ The type information will be '<literal>m</literal>' for a metadata page,
+ '<literal>v</literal>' for an overflow page, '<literal>b</literal>' for a bucket page,
+ '<literal>i</literal>' for a bitmap page, and '<literal>u</literal>' for an unused page.
+ </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 detailed information about
+ all of the items on a hash index page. For example:
+<screen>
+test=# SELECT * FROM hash_page_items(get_raw_page('mytab_index', 1));
+ itemoffset | ctid | data
+------------+------------+-------------------------
+ 1 | (2057,114) | 00 a0 25 02 00 00 00 00
+ 2 | (4033,38) | 00 20 8d 02 00 00 00 00
+ 3 | (4223,130) | 00 80 f7 02 00 00 00 00
+ 4 | (1524,126) | 00 70 de 04 00 00 00 00
+ 5 | (5850,86) | 00 70 1a 07 00 00 00 00
+...
+ 210 | (5541,140) | 00 90 a9 ff 00 00 00 00
+ 211 | (4654,93) | 00 d0 fb ff 00 00 00 00
+
+</screen>
+ The <structfield>ctid</> column points to a heap tuple
+ (BlockIdData,OffsetNumber).
</para>
</listitem>
</varlistentry>
--
2.7.4
On 09/23/2016 01:56 AM, Amit Kapila wrote:
While looking at patch, I noticed below code which seems somewhat problematic:
+ stat->max_avail = BLCKSZ - (BLCKSZ - phdr->pd_special + SizeOfPageHeaderData); + + /* page type (flags) */ + if (opaque->hasho_flag & LH_META_PAGE) + stat->type = 'm'; + else if (opaque->hasho_flag & LH_OVERFLOW_PAGE) + stat->type = 'v'; + else if (opaque->hasho_flag & LH_BUCKET_PAGE) + stat->type = 'b'; + else if (opaque->hasho_flag & LH_BITMAP_PAGE) + stat->type = 'i';In the above code, it appears that you are trying to calculate
max_avail space for all pages in same way. Don't you need to
calculate it differently for bitmap page or meta page as they don't
share the same format as other type of pages?
Correct, however the max_avail calculation was removed in v6, since we
don't display average item size anymore.
Thanks for the feedback !
Best regards,
Jesper
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Sep 26, 2016 at 10:39 AM, Jesper Pedersen <
jesper.pedersen@redhat.com> wrote:
Hi,
On 09/23/2016 12:10 AM, Peter Eisentraut wrote:
- As of very recently, we don't need to move pageinspect--1.5.sql to
pageinspect--1.6.sql anymore. Just add pageinspect--1.5--1.6.sql.
We need to rename the pageinspect--1.5.sql file and add the new methods,
right ? Or ?
You just need to add pageinspect--1.5--1.6.sql. With recent changes to the
extension infrastructure, when you create the extension as version 1.6, the
infrastructure automatically figures out that it should install 1.5 and
then upgrade to 1.6. Or that is my understanding, anyway.
Cheers,
Jeff
On Tue, Sep 20, 2016 at 12:19 AM, Michael Paquier <michael.paquier@gmail.com
wrote:
Note: the patch checks if a superuser is calling the new functions,
which is a good thing.
If we only have the bytea functions and the user needs to supply the raw
pages themselves, rather than having the function go get the raw page for
you, is there any reason to restrict the interpretation function to super
users? I guess if we don't trust the C coded functions not to dereference
bogus data in harmful ways?
Cheers,
Jeff
Jeff Janes wrote:
On Tue, Sep 20, 2016 at 12:19 AM, Michael Paquier <michael.paquier@gmail.com
wrote:
Note: the patch checks if a superuser is calling the new functions,
which is a good thing.If we only have the bytea functions and the user needs to supply the raw
pages themselves, rather than having the function go get the raw page for
you, is there any reason to restrict the interpretation function to super
users? I guess if we don't trust the C coded functions not to dereference
bogus data in harmful ways?
Yeah, it'd likely be simple to manufacture a fake page that causes the
server to misbehave resulting in a security leak or at least DoS.
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 9/26/16 1:39 PM, Jesper Pedersen wrote:
Left as is, since BuildTupleFromCStrings() vs. xyzGetDatum() are equally
readable in this case. But, I can change the patch if needed.
The point is that to use BuildTupleFromCStrings() you need to convert
numbers to strings, and then they are converted back. This is not a
typical way to write row-returning functions.
- hash_metap result fields spares and mapp should be arrays of integer.
B-tree and BRIN uses a 'text' field as output, so left as is.
These fields are specific to hash, so the precedent doesn't necessarily
apply.
- The data field could be of type bytea.
Left as is, for same reasons as 'spares' and 'mapp'.
Comments from others here? Why not use bytea instead of text?
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Peter Eisentraut wrote:
On 9/26/16 1:39 PM, Jesper Pedersen wrote:
- hash_metap result fields spares and mapp should be arrays of integer.
B-tree and BRIN uses a 'text' field as output, so left as is.
These fields are specific to hash, so the precedent doesn't necessarily
apply.- The data field could be of type bytea.
Left as is, for same reasons as 'spares' and 'mapp'.
Comments from others here? Why not use bytea instead of text?
The BRIN pageinspect functions aren't a particularly well thought-out
precedent IMO. There was practically no review of that part of the BRIN
patch, and I just threw it together in the way that seemed to make the
most sense at the time. If there's some reason why some other way is
better for hash indexes, that should be followed rather than mimicking
BRIN.
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 09/26/2016 10:45 PM, Peter Eisentraut wrote:
On 9/26/16 1:39 PM, Jesper Pedersen wrote:
Left as is, since BuildTupleFromCStrings() vs. xyzGetDatum() are equally
readable in this case. But, I can change the patch if needed.The point is that to use BuildTupleFromCStrings() you need to convert
numbers to strings, and then they are converted back. This is not a
typical way to write row-returning functions.
Ok.
Changed:
* BuildTupleFromCStrings -> xyzGetDatum
* 'type' field: char -> text w/ full description
* Removed 'type' information from documentation
Best regards,
Jesper
Attachments:
0001-pageinspect-Hash-index-support_v7.patchtext/x-patch; name=0001-pageinspect-Hash-index-support_v7.patchDownload
From d6dd73ffef9deafc938b9fe7119db0928494cbf9 Mon Sep 17 00:00:00 2001
From: jesperpedersen <jesper.pedersen@redhat.com>
Date: Fri, 5 Aug 2016 10:16:32 -0400
Subject: [PATCH] pageinspect: Hash index support
---
contrib/pageinspect/Makefile | 10 +-
contrib/pageinspect/hashfuncs.c | 408 ++++++++++++++++++++++++++
contrib/pageinspect/pageinspect--1.5--1.6.sql | 59 ++++
contrib/pageinspect/pageinspect--1.5.sql | 279 ------------------
contrib/pageinspect/pageinspect--1.6.sql | 346 ++++++++++++++++++++++
contrib/pageinspect/pageinspect.control | 2 +-
doc/src/sgml/pageinspect.sgml | 146 ++++++++-
7 files changed, 955 insertions(+), 295 deletions(-)
create mode 100644 contrib/pageinspect/hashfuncs.c
create mode 100644 contrib/pageinspect/pageinspect--1.5--1.6.sql
delete mode 100644 contrib/pageinspect/pageinspect--1.5.sql
create mode 100644 contrib/pageinspect/pageinspect--1.6.sql
diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index a98237e..c73d728 100644
--- a/contrib/pageinspect/Makefile
+++ b/contrib/pageinspect/Makefile
@@ -2,13 +2,13 @@
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.6.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"
ifdef USE_PGXS
diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c
new file mode 100644
index 0000000..a76b683
--- /dev/null
+++ b/contrib/pageinspect/hashfuncs.c
@@ -0,0 +1,408 @@
+/*
+ * hashfuncs.c
+ * Functions to investigate the content of HASH indexes
+ *
+ * Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * contrib/pageinspect/hashfuncs.c
+ */
+
+#include "postgres.h"
+
+#include "access/hash.h"
+#include "access/htup_details.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+
+PG_FUNCTION_INFO_V1(hash_metapage_info);
+PG_FUNCTION_INFO_V1(hash_page_items);
+PG_FUNCTION_INFO_V1(hash_page_stats);
+
+/* ------------------------------------------------
+ * structure for single hash page statistics
+ * ------------------------------------------------
+ */
+typedef struct HashPageStat
+{
+ uint32 live_items;
+ uint32 dead_items;
+ uint32 page_size;
+ uint32 free_size;
+ char *type;
+
+ /* 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, bool metap)
+{
+ Page page;
+ int raw_page_size;
+ HashPageOpaque pageopaque;
+
+ raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
+
+ if (raw_page_size != BLCKSZ)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("input page too small"),
+ errdetail("Expected size %d, got %d",
+ BLCKSZ, raw_page_size)));
+
+ page = VARDATA(raw_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)));
+
+ if (metap)
+ {
+ if (pageopaque->hasho_flag != LH_META_PAGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("page is not a metadata page"),
+ errdetail("Expected %08x, got %08x.",
+ LH_META_PAGE, pageopaque->hasho_flag)));
+ }
+ else
+ {
+ if (pageopaque->hasho_flag == LH_META_PAGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("page is a metadata page"),
+ errdetail("Flags %08x.",
+ pageopaque->hasho_flag)));
+ }
+
+ 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);
+
+ /* page type (flags) */
+ if (opaque->hasho_flag & LH_META_PAGE)
+ stat->type = "metapage";
+ else if (opaque->hasho_flag & LH_OVERFLOW_PAGE)
+ stat->type = "overflow";
+ else if (opaque->hasho_flag & LH_BUCKET_PAGE)
+ stat->type = "bucket";
+ else if (opaque->hasho_flag & LH_BITMAP_PAGE)
+ stat->type = "bitmap";
+ else
+ stat->type = "unused";
+
+ /* 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_stats()
+ *
+ * Usage: SELECT * FROM hash_page_stats(get_raw_page('t1_hash', 1));
+ * ---------------------------------------------------
+ */
+Datum
+hash_page_stats(PG_FUNCTION_ARGS)
+{
+ bytea *raw_page = PG_GETARG_BYTEA_P(0);
+ Page page;
+ int j;
+ Datum values[10];
+ bool nulls[10];
+ HashPageStat stat;
+ HeapTuple tuple;
+ TupleDesc tupleDesc;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ page = verify_hash_page(raw_page, false);
+
+ /* 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++] = CStringGetTextDatum(stat.type);
+ values[j++] = UInt32GetDatum(stat.live_items);
+ values[j++] = UInt32GetDatum(stat.dead_items);
+ values[j++] = UInt32GetDatum(stat.page_size);
+ values[j++] = UInt32GetDatum(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('t1_hash', 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];
+ HeapTuple tuple;
+ FuncCallContext *fctx;
+ MemoryContext mctx;
+ struct user_args *uargs;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ TupleDesc tupleDesc;
+
+ fctx = SRF_FIRSTCALL_INIT();
+
+ page = verify_hash_page(raw_page, false);
+
+ /*
+ * We copy the page into local storage to avoid holding pin on the
+ * buffer longer than we must, and possibly failing to release it at
+ * all if the calling query doesn't fetch all rows.
+ */
+ mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
+
+ uargs = palloc(sizeof(struct user_args));
+
+ uargs->page = palloc(BLCKSZ);
+ memcpy(uargs->page, page, BLCKSZ);
+
+ 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;
+ int off;
+ int dlen;
+ char *dump;
+ char *s;
+ char *ptr;
+
+ 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++] = CStringGetTextDatum(psprintf("(%u,%u)",
+ BlockIdGetBlockNumber(&(itup->t_tid.ip_blkid)),
+ itup->t_tid.ip_posid));
+
+ ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
+ dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info);
+ dump = palloc0(dlen * 3 + 1);
+ s = dump;
+ for (off = 0; off < dlen; off++)
+ {
+ if (off > 0)
+ *dump++ = ' ';
+ sprintf(dump, "%02x", *(ptr + off) & 0xff);
+ dump += 2;
+ }
+ values[j] = CStringGetTextDatum(s);
+
+ 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->page);
+ pfree(uargs);
+ SRF_RETURN_DONE(fctx);
+ }
+}
+
+/* ------------------------------------------------
+ * hash_metapage_info()
+ *
+ * Get a hash's meta-page information
+ *
+ * Usage: SELECT * FROM hash_metapage_info(get_raw_page('t1_hash', 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];
+ char *spares;
+ char *mapp;
+ char *s;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ page = verify_hash_page(raw_page, true);
+
+ /* 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++] = CStringGetTextDatum(psprintf("0x%08X", 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);
+
+ spares = palloc0(HASH_MAX_SPLITPOINTS * 5 + 1);
+ s = spares;
+ for (i = 0; i < HASH_MAX_SPLITPOINTS; i++)
+ {
+ if (i > 0)
+ *spares++ = ' ';
+
+ sprintf(spares, "%04x", metad->hashm_spares[i]);
+ spares += 4;
+ }
+ values[j++] = CStringGetTextDatum(s);
+
+ mapp = palloc0(HASH_MAX_BITMAPS * 5 + 1);
+ s = mapp;
+ for (i = 0; i < HASH_MAX_BITMAPS; i++)
+ {
+ if (i > 0)
+ *mapp++ = ' ';
+
+ sprintf(mapp, "%04x", metad->hashm_mapp[i]);
+ mapp += 4;
+ }
+ values[j++] = CStringGetTextDatum(s);
+
+ 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 0000000..7f8c2d2
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.5--1.6.sql
@@ -0,0 +1,59 @@
+/* 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_metapage_info()
+--
+CREATE FUNCTION hash_metapage_info(IN page bytea,
+ OUT magic text,
+ OUT version int4,
+ OUT ntuples double precision,
+ OUT ffactor int2,
+ OUT bsize int2,
+ OUT bmsize int2,
+ OUT bmshift int2,
+ OUT maxbucket int4,
+ OUT highmask int4,
+ OUT lowmask int4,
+ OUT ovflpoint int4,
+ OUT firstfree int4,
+ OUT nmaps int4,
+ OUT procid int4,
+ OUT spares text,
+ OUT mapp text)
+AS 'MODULE_PATHNAME', 'hash_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- hash_page_stats()
+--
+CREATE FUNCTION hash_page_stats(IN page bytea,
+ OUT type text,
+ OUT live_items int4,
+ OUT dead_items int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT hasho_prevblkno int4,
+ OUT hasho_nextblkno int4,
+ OUT hasho_bucket int4,
+ OUT hasho_flag int4,
+ 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 text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'hash_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect--1.5.sql b/contrib/pageinspect/pageinspect--1.5.sql
deleted file mode 100644
index 1e40c3c..0000000
--- a/contrib/pageinspect/pageinspect--1.5.sql
+++ /dev/null
@@ -1,279 +0,0 @@
-/* contrib/pageinspect/pageinspect--1.5.sql */
-
--- complain if script is sourced in psql, rather than via CREATE EXTENSION
-\echo Use "CREATE EXTENSION pageinspect" to load this file. \quit
-
---
--- get_raw_page()
---
-CREATE FUNCTION get_raw_page(text, int4)
-RETURNS bytea
-AS 'MODULE_PATHNAME', 'get_raw_page'
-LANGUAGE C STRICT PARALLEL SAFE;
-
-CREATE FUNCTION get_raw_page(text, text, int4)
-RETURNS bytea
-AS 'MODULE_PATHNAME', 'get_raw_page_fork'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- page_header()
---
-CREATE FUNCTION page_header(IN page bytea,
- OUT lsn pg_lsn,
- OUT checksum smallint,
- OUT flags smallint,
- OUT lower smallint,
- OUT upper smallint,
- OUT special smallint,
- OUT pagesize smallint,
- OUT version smallint,
- OUT prune_xid xid)
-AS 'MODULE_PATHNAME', 'page_header'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- heap_page_items()
---
-CREATE FUNCTION heap_page_items(IN page bytea,
- OUT lp smallint,
- OUT lp_off smallint,
- OUT lp_flags smallint,
- OUT lp_len smallint,
- OUT t_xmin xid,
- OUT t_xmax xid,
- OUT t_field3 int4,
- OUT t_ctid tid,
- OUT t_infomask2 integer,
- OUT t_infomask integer,
- OUT t_hoff smallint,
- OUT t_bits text,
- OUT t_oid oid,
- OUT t_data bytea)
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'heap_page_items'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- tuple_data_split()
---
-CREATE FUNCTION tuple_data_split(rel_oid oid,
- t_data bytea,
- t_infomask integer,
- t_infomask2 integer,
- t_bits text)
-RETURNS bytea[]
-AS 'MODULE_PATHNAME','tuple_data_split'
-LANGUAGE C PARALLEL SAFE;
-
-CREATE FUNCTION tuple_data_split(rel_oid oid,
- t_data bytea,
- t_infomask integer,
- t_infomask2 integer,
- t_bits text,
- do_detoast bool)
-RETURNS bytea[]
-AS 'MODULE_PATHNAME','tuple_data_split'
-LANGUAGE C PARALLEL SAFE;
-
---
--- heap_page_item_attrs()
---
-CREATE FUNCTION heap_page_item_attrs(
- IN page bytea,
- IN rel_oid regclass,
- IN do_detoast bool,
- OUT lp smallint,
- OUT lp_off smallint,
- OUT lp_flags smallint,
- OUT lp_len smallint,
- OUT t_xmin xid,
- OUT t_xmax xid,
- OUT t_field3 int4,
- OUT t_ctid tid,
- OUT t_infomask2 integer,
- OUT t_infomask integer,
- OUT t_hoff smallint,
- OUT t_bits text,
- OUT t_oid oid,
- OUT t_attrs bytea[]
- )
-RETURNS SETOF record AS $$
-SELECT lp,
- lp_off,
- lp_flags,
- lp_len,
- t_xmin,
- t_xmax,
- t_field3,
- t_ctid,
- t_infomask2,
- t_infomask,
- t_hoff,
- t_bits,
- t_oid,
- tuple_data_split(
- rel_oid,
- t_data,
- t_infomask,
- t_infomask2,
- t_bits,
- do_detoast)
- AS t_attrs
- FROM heap_page_items(page);
-$$ LANGUAGE SQL PARALLEL SAFE;
-
-CREATE FUNCTION heap_page_item_attrs(IN page bytea, IN rel_oid regclass,
- OUT lp smallint,
- OUT lp_off smallint,
- OUT lp_flags smallint,
- OUT lp_len smallint,
- OUT t_xmin xid,
- OUT t_xmax xid,
- OUT t_field3 int4,
- OUT t_ctid tid,
- OUT t_infomask2 integer,
- OUT t_infomask integer,
- OUT t_hoff smallint,
- OUT t_bits text,
- OUT t_oid oid,
- OUT t_attrs bytea[]
- )
-RETURNS SETOF record AS $$
-SELECT * from heap_page_item_attrs(page, rel_oid, false);
-$$ LANGUAGE SQL PARALLEL SAFE;
-
---
--- bt_metap()
---
-CREATE FUNCTION bt_metap(IN relname text,
- OUT magic int4,
- OUT version int4,
- OUT root int4,
- OUT level int4,
- OUT fastroot int4,
- OUT fastlevel int4)
-AS 'MODULE_PATHNAME', 'bt_metap'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- bt_page_stats()
---
-CREATE FUNCTION bt_page_stats(IN relname text, IN blkno int4,
- OUT blkno int4,
- OUT type "char",
- OUT live_items int4,
- OUT dead_items int4,
- OUT avg_item_size int4,
- OUT page_size int4,
- OUT free_size int4,
- OUT btpo_prev int4,
- OUT btpo_next int4,
- OUT btpo int4,
- OUT btpo_flags int4)
-AS 'MODULE_PATHNAME', 'bt_page_stats'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- bt_page_items()
---
-CREATE FUNCTION bt_page_items(IN relname text, IN blkno int4,
- OUT itemoffset smallint,
- OUT ctid tid,
- OUT itemlen smallint,
- OUT nulls bool,
- OUT vars bool,
- OUT data text)
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'bt_page_items'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_page_type()
---
-CREATE FUNCTION brin_page_type(IN page bytea)
-RETURNS text
-AS 'MODULE_PATHNAME', 'brin_page_type'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_metapage_info()
---
-CREATE FUNCTION brin_metapage_info(IN page bytea, OUT magic text,
- OUT version integer, OUT pagesperrange integer, OUT lastrevmappage bigint)
-AS 'MODULE_PATHNAME', 'brin_metapage_info'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_revmap_data()
---
-CREATE FUNCTION brin_revmap_data(IN page bytea,
- OUT pages tid)
-RETURNS SETOF tid
-AS 'MODULE_PATHNAME', 'brin_revmap_data'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_page_items()
---
-CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass,
- OUT itemoffset int,
- OUT blknum int,
- OUT attnum int,
- OUT allnulls bool,
- OUT hasnulls bool,
- OUT placeholder bool,
- OUT value text)
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'brin_page_items'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- fsm_page_contents()
---
-CREATE FUNCTION fsm_page_contents(IN page bytea)
-RETURNS text
-AS 'MODULE_PATHNAME', 'fsm_page_contents'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- GIN functions
---
-
---
--- gin_metapage_info()
---
-CREATE FUNCTION gin_metapage_info(IN page bytea,
- OUT pending_head bigint,
- OUT pending_tail bigint,
- OUT tail_free_size int4,
- OUT n_pending_pages bigint,
- OUT n_pending_tuples bigint,
- OUT n_total_pages bigint,
- OUT n_entry_pages bigint,
- OUT n_data_pages bigint,
- OUT n_entries bigint,
- OUT version int4)
-AS 'MODULE_PATHNAME', 'gin_metapage_info'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- gin_page_opaque_info()
---
-CREATE FUNCTION gin_page_opaque_info(IN page bytea,
- OUT rightlink bigint,
- OUT maxoff int4,
- OUT flags text[])
-AS 'MODULE_PATHNAME', 'gin_page_opaque_info'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- gin_leafpage_items()
---
-CREATE FUNCTION gin_leafpage_items(IN page bytea,
- OUT first_tid tid,
- OUT nbytes int2,
- OUT tids tid[])
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'gin_leafpage_items'
-LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect--1.6.sql b/contrib/pageinspect/pageinspect--1.6.sql
new file mode 100644
index 0000000..f3445df
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.6.sql
@@ -0,0 +1,346 @@
+/* contrib/pageinspect/pageinspect--1.6.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION pageinspect" to load this file. \quit
+
+--
+-- General functions
+--
+
+--
+-- get_raw_page()
+--
+CREATE FUNCTION get_raw_page(text, int4)
+RETURNS bytea
+AS 'MODULE_PATHNAME', 'get_raw_page'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+CREATE FUNCTION get_raw_page(text, text, int4)
+RETURNS bytea
+AS 'MODULE_PATHNAME', 'get_raw_page_fork'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- page_header()
+--
+CREATE FUNCTION page_header(IN page bytea,
+ OUT lsn pg_lsn,
+ OUT checksum smallint,
+ OUT flags smallint,
+ OUT lower smallint,
+ OUT upper smallint,
+ OUT special smallint,
+ OUT pagesize smallint,
+ OUT version smallint,
+ OUT prune_xid xid)
+AS 'MODULE_PATHNAME', 'page_header'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- heap_page_items()
+--
+CREATE FUNCTION heap_page_items(IN page bytea,
+ OUT lp smallint,
+ OUT lp_off smallint,
+ OUT lp_flags smallint,
+ OUT lp_len smallint,
+ OUT t_xmin xid,
+ OUT t_xmax xid,
+ OUT t_field3 int4,
+ OUT t_ctid tid,
+ OUT t_infomask2 integer,
+ OUT t_infomask integer,
+ OUT t_hoff smallint,
+ OUT t_bits text,
+ OUT t_oid oid,
+ OUT t_data bytea)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'heap_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- tuple_data_split()
+--
+CREATE FUNCTION tuple_data_split(rel_oid oid,
+ t_data bytea,
+ t_infomask integer,
+ t_infomask2 integer,
+ t_bits text)
+RETURNS bytea[]
+AS 'MODULE_PATHNAME','tuple_data_split'
+LANGUAGE C PARALLEL SAFE;
+
+CREATE FUNCTION tuple_data_split(rel_oid oid,
+ t_data bytea,
+ t_infomask integer,
+ t_infomask2 integer,
+ t_bits text,
+ do_detoast bool)
+RETURNS bytea[]
+AS 'MODULE_PATHNAME','tuple_data_split'
+LANGUAGE C PARALLEL SAFE;
+
+--
+-- heap_page_item_attrs()
+--
+CREATE FUNCTION heap_page_item_attrs(
+ IN page bytea,
+ IN rel_oid regclass,
+ IN do_detoast bool,
+ OUT lp smallint,
+ OUT lp_off smallint,
+ OUT lp_flags smallint,
+ OUT lp_len smallint,
+ OUT t_xmin xid,
+ OUT t_xmax xid,
+ OUT t_field3 int4,
+ OUT t_ctid tid,
+ OUT t_infomask2 integer,
+ OUT t_infomask integer,
+ OUT t_hoff smallint,
+ OUT t_bits text,
+ OUT t_oid oid,
+ OUT t_attrs bytea[]
+ )
+RETURNS SETOF record AS $$
+SELECT lp,
+ lp_off,
+ lp_flags,
+ lp_len,
+ t_xmin,
+ t_xmax,
+ t_field3,
+ t_ctid,
+ t_infomask2,
+ t_infomask,
+ t_hoff,
+ t_bits,
+ t_oid,
+ tuple_data_split(
+ rel_oid,
+ t_data,
+ t_infomask,
+ t_infomask2,
+ t_bits,
+ do_detoast)
+ AS t_attrs
+ FROM heap_page_items(page);
+$$ LANGUAGE SQL PARALLEL SAFE;
+
+CREATE FUNCTION heap_page_item_attrs(IN page bytea, IN rel_oid regclass,
+ OUT lp smallint,
+ OUT lp_off smallint,
+ OUT lp_flags smallint,
+ OUT lp_len smallint,
+ OUT t_xmin xid,
+ OUT t_xmax xid,
+ OUT t_field3 int4,
+ OUT t_ctid tid,
+ OUT t_infomask2 integer,
+ OUT t_infomask integer,
+ OUT t_hoff smallint,
+ OUT t_bits text,
+ OUT t_oid oid,
+ OUT t_attrs bytea[]
+ )
+RETURNS SETOF record AS $$
+SELECT * from heap_page_item_attrs(page, rel_oid, false);
+$$ LANGUAGE SQL PARALLEL SAFE;
+
+--
+-- fsm_page_contents()
+--
+CREATE FUNCTION fsm_page_contents(IN page bytea)
+RETURNS text
+AS 'MODULE_PATHNAME', 'fsm_page_contents'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- B-tree functions
+--
+
+--
+-- bt_metap()
+--
+CREATE FUNCTION bt_metap(IN relname text,
+ OUT magic int4,
+ OUT version int4,
+ OUT root int4,
+ OUT level int4,
+ OUT fastroot int4,
+ OUT fastlevel int4)
+AS 'MODULE_PATHNAME', 'bt_metap'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- bt_page_stats()
+--
+CREATE FUNCTION bt_page_stats(IN relname text, IN blkno int4,
+ OUT blkno int4,
+ OUT type "char",
+ OUT live_items int4,
+ OUT dead_items int4,
+ OUT avg_item_size int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT btpo_prev int4,
+ OUT btpo_next int4,
+ OUT btpo int4,
+ OUT btpo_flags int4)
+AS 'MODULE_PATHNAME', 'bt_page_stats'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- bt_page_items()
+--
+CREATE FUNCTION bt_page_items(IN relname text, IN blkno int4,
+ OUT itemoffset smallint,
+ OUT ctid tid,
+ OUT itemlen smallint,
+ OUT nulls bool,
+ OUT vars bool,
+ OUT data text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'bt_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- BRIN functions
+--
+
+--
+-- brin_page_type()
+--
+CREATE FUNCTION brin_page_type(IN page bytea)
+RETURNS text
+AS 'MODULE_PATHNAME', 'brin_page_type'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_metapage_info()
+--
+CREATE FUNCTION brin_metapage_info(IN page bytea, OUT magic text,
+ OUT version integer, OUT pagesperrange integer, OUT lastrevmappage bigint)
+AS 'MODULE_PATHNAME', 'brin_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_revmap_data()
+--
+CREATE FUNCTION brin_revmap_data(IN page bytea,
+ OUT pages tid)
+RETURNS SETOF tid
+AS 'MODULE_PATHNAME', 'brin_revmap_data'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_page_items()
+--
+CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass,
+ OUT itemoffset int,
+ OUT blknum int,
+ OUT attnum int,
+ OUT allnulls bool,
+ OUT hasnulls bool,
+ OUT placeholder bool,
+ OUT value text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'brin_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- GIN functions
+--
+
+--
+-- gin_metapage_info()
+--
+CREATE FUNCTION gin_metapage_info(IN page bytea,
+ OUT pending_head bigint,
+ OUT pending_tail bigint,
+ OUT tail_free_size int4,
+ OUT n_pending_pages bigint,
+ OUT n_pending_tuples bigint,
+ OUT n_total_pages bigint,
+ OUT n_entry_pages bigint,
+ OUT n_data_pages bigint,
+ OUT n_entries bigint,
+ OUT version int4)
+AS 'MODULE_PATHNAME', 'gin_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- gin_page_opaque_info()
+--
+CREATE FUNCTION gin_page_opaque_info(IN page bytea,
+ OUT rightlink bigint,
+ OUT maxoff int4,
+ OUT flags text[])
+AS 'MODULE_PATHNAME', 'gin_page_opaque_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- gin_leafpage_items()
+--
+CREATE FUNCTION gin_leafpage_items(IN page bytea,
+ OUT first_tid tid,
+ OUT nbytes int2,
+ OUT tids tid[])
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'gin_leafpage_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- Hash functions
+--
+
+--
+-- hash_metapage_info()
+--
+CREATE FUNCTION hash_metapage_info(IN page bytea,
+ OUT magic text,
+ OUT version int4,
+ OUT ntuples double precision,
+ OUT ffactor int2,
+ OUT bsize int2,
+ OUT bmsize int2,
+ OUT bmshift int2,
+ OUT maxbucket int4,
+ OUT highmask int4,
+ OUT lowmask int4,
+ OUT ovflpoint int4,
+ OUT firstfree int4,
+ OUT nmaps int4,
+ OUT procid int4,
+ OUT spares text,
+ OUT mapp text)
+AS 'MODULE_PATHNAME', 'hash_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- hash_page_stats()
+--
+CREATE FUNCTION hash_page_stats(IN page bytea,
+ OUT type text,
+ OUT live_items int4,
+ OUT dead_items int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT hasho_prevblkno int4,
+ OUT hasho_nextblkno int4,
+ OUT hasho_bucket int4,
+ OUT hasho_flag int4,
+ 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 text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'hash_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control
index 23c8eff..1a61c9f 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/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml
index 5d187ed..626feba 100644
--- a/doc/src/sgml/pageinspect.sgml
+++ b/doc/src/sgml/pageinspect.sgml
@@ -14,7 +14,7 @@
</para>
<sect2>
- <title>Functions</title>
+ <title>General functions</title>
<variablelist>
<varlistentry>
@@ -160,7 +160,36 @@ test=# SELECT * FROM heap_page_item_attrs(get_raw_page('pg_class', 0), 'pg_class
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term>
+ <function>fsm_page_contents(page bytea) returns text</function>
+ <indexterm>
+ <primary>fsm_page_contents</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+ <para>
+ <function>fsm_page_contents</function> shows the internal node structure
+ of a FSM page. The output is a multiline string, with one line per
+ node in the binary tree within the page. Only those nodes that are not
+ zero are printed. The so-called "next" pointer, which points to the
+ next slot to be returned from the page, is also printed.
+ </para>
+ <para>
+ See <filename>src/backend/storage/freespace/README</> for more
+ information on the structure of an FSM page.
+ </para>
+ </listitem>
+ </varlistentry>
+</variablelist>
+</sect2>
+ <sect2>
+ <title>B-tree functions</title>
+
+ <variablelist>
<varlistentry>
<term>
<function>bt_metap(relname text) returns record</function>
@@ -261,7 +290,13 @@ test=# SELECT * FROM bt_page_items('pg_cast_oid_index', 1);
</para>
</listitem>
</varlistentry>
+ </variablelist>
+ </sect2>
+
+ <sect2>
+ <title>BRIN functions</title>
+ <variablelist>
<varlistentry>
<term>
<function>brin_page_type(page bytea) returns text</function>
@@ -365,7 +400,13 @@ test=# SELECT * FROM brin_page_items(get_raw_page('brinidx', 5),
</para>
</listitem>
</varlistentry>
+ </variablelist>
+ </sect2>
+
+ <sect2>
+ <title>GIN functions</title>
+ <variablelist>
<varlistentry>
<term>
<function>gin_metapage_info(page bytea) returns record</function>
@@ -449,26 +490,111 @@ test=# SELECT first_tid, nbytes, tids[0:5] as some_tids
</para>
</listitem>
</varlistentry>
+ </variablelist>
+ </sect2>
+
+ <sect2>
+ <title>Hash functions</title>
+ <para>
+ The hash index data structures are defined in <filename>src/include/access/hash.h</>.
+ </para>
+
+ <variablelist>
<varlistentry>
<term>
- <function>fsm_page_contents(page bytea) returns text</function>
+ <function>hash_metapage_info(page bytes) returns record</function>
<indexterm>
- <primary>fsm_page_contents</primary>
+ <primary>hash_metapage_info</primary>
</indexterm>
</term>
<listitem>
<para>
- <function>fsm_page_contents</function> shows the internal node structure
- of a FSM page. The output is a multiline string, with one line per
- node in the binary tree within the page. Only those nodes that are not
- zero are printed. The so-called "next" pointer, which points to the
- next slot to be returned from the page, is also printed.
+ <function>hash_metapage_info</function> returns information about a hash
+ index's metapage. For example:
+<screen>
+test=# SELECT * FROM hash_metapage_info(get_raw_page('mytab_index', 0));
+-[ RECORD 1 ]-----
+magic | 0x06440640
+version | 2
+ntuples | 1000000
+ffactor | 307
+bsize | 8152
+bmsize | 4096
+bmshift | 15
+maxbucket | 4095
+highmask | 8191
+lowmask | 4095
+ovflpoint | 12
+firstfree | 0
+nmaps | 1
+procid | 400
+spares | 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 0000 ...
+mapp | 1001 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 ...
+</screen>
</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <function>hash_page_stats(page bytea) returns record</function>
+ <indexterm>
+ <primary>hash_page_stats</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
<para>
- See <filename>src/backend/storage/freespace/README</> for more
- information on the structure of an FSM page.
+ <function>hash_page_stats</function> returns summary information about
+ single pages of hash indexes. For example:
+<screen>
+test=# SELECT * FROM hash_page_stats(get_raw_page('mytab_index', 1));
+-[ RECORD 1 ]---+-----
+type | bucket
+live_items | 211
+dead_items | 0
+page_size | 8192
+free_size | 3928
+hasho_prevblkno | -1
+hasho_nextblkno | -1
+hasho_bucket | 0
+hasho_flag | 2
+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 detailed information about
+ all of the items on a hash index page. For example:
+<screen>
+test=# SELECT * FROM hash_page_items(get_raw_page('mytab_index', 1));
+ itemoffset | ctid | data
+------------+------------+-------------------------
+ 1 | (2057,114) | 00 a0 25 02 00 00 00 00
+ 2 | (4033,38) | 00 20 8d 02 00 00 00 00
+ 3 | (4223,130) | 00 80 f7 02 00 00 00 00
+ 4 | (1524,126) | 00 70 de 04 00 00 00 00
+ 5 | (5850,86) | 00 70 1a 07 00 00 00 00
+...
+ 210 | (5541,140) | 00 90 a9 ff 00 00 00 00
+ 211 | (4654,93) | 00 d0 fb ff 00 00 00 00
+
+</screen>
+ The <structfield>ctid</> column points to a heap tuple
+ (BlockIdData,OffsetNumber).
</para>
</listitem>
</varlistentry>
--
2.7.4
On 9/27/16 10:10 AM, Jesper Pedersen wrote:
contrib/pageinspect/pageinspect--1.5--1.6.sql | 59 ++++
contrib/pageinspect/pageinspect--1.5.sql | 279 ------------------
contrib/pageinspect/pageinspect--1.6.sql | 346 ++++++++++++++++++++++
I think there is still a misunderstanding about these extension files.
You only need to supply pageinspect--1.5--1.6.sql and leave all the
other ones alone.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 09/29/2016 11:58 AM, Peter Eisentraut wrote:
On 9/27/16 10:10 AM, Jesper Pedersen wrote:
contrib/pageinspect/pageinspect--1.5--1.6.sql | 59 ++++
contrib/pageinspect/pageinspect--1.5.sql | 279 ------------------
contrib/pageinspect/pageinspect--1.6.sql | 346 ++++++++++++++++++++++I think there is still a misunderstanding about these extension files.
You only need to supply pageinspect--1.5--1.6.sql and leave all the
other ones alone.
Ok.
Left the 'DATA' section in the Makefile, and the control file as is.
Best regards,
Jesper
Attachments:
0001-pageinspect-Hash-index-support_v8.patchtext/x-patch; name=0001-pageinspect-Hash-index-support_v8.patchDownload
From b48623beda6aad43116d46ff44de55bdc7547325 Mon Sep 17 00:00:00 2001
From: jesperpedersen <jesper.pedersen@redhat.com>
Date: Fri, 5 Aug 2016 10:16:32 -0400
Subject: [PATCH] pageinspect: Hash index support
---
contrib/pageinspect/Makefile | 10 +-
contrib/pageinspect/hashfuncs.c | 408 ++++++++++++++++++++++++++
contrib/pageinspect/pageinspect--1.5--1.6.sql | 59 ++++
contrib/pageinspect/pageinspect.control | 2 +-
doc/src/sgml/pageinspect.sgml | 146 ++++++++-
5 files changed, 609 insertions(+), 16 deletions(-)
create mode 100644 contrib/pageinspect/hashfuncs.c
create mode 100644 contrib/pageinspect/pageinspect--1.5--1.6.sql
diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index a98237e..c73d728 100644
--- a/contrib/pageinspect/Makefile
+++ b/contrib/pageinspect/Makefile
@@ -2,13 +2,13 @@
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.6.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"
ifdef USE_PGXS
diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c
new file mode 100644
index 0000000..a76b683
--- /dev/null
+++ b/contrib/pageinspect/hashfuncs.c
@@ -0,0 +1,408 @@
+/*
+ * hashfuncs.c
+ * Functions to investigate the content of HASH indexes
+ *
+ * Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * contrib/pageinspect/hashfuncs.c
+ */
+
+#include "postgres.h"
+
+#include "access/hash.h"
+#include "access/htup_details.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+
+PG_FUNCTION_INFO_V1(hash_metapage_info);
+PG_FUNCTION_INFO_V1(hash_page_items);
+PG_FUNCTION_INFO_V1(hash_page_stats);
+
+/* ------------------------------------------------
+ * structure for single hash page statistics
+ * ------------------------------------------------
+ */
+typedef struct HashPageStat
+{
+ uint32 live_items;
+ uint32 dead_items;
+ uint32 page_size;
+ uint32 free_size;
+ char *type;
+
+ /* 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, bool metap)
+{
+ Page page;
+ int raw_page_size;
+ HashPageOpaque pageopaque;
+
+ raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
+
+ if (raw_page_size != BLCKSZ)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("input page too small"),
+ errdetail("Expected size %d, got %d",
+ BLCKSZ, raw_page_size)));
+
+ page = VARDATA(raw_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)));
+
+ if (metap)
+ {
+ if (pageopaque->hasho_flag != LH_META_PAGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("page is not a metadata page"),
+ errdetail("Expected %08x, got %08x.",
+ LH_META_PAGE, pageopaque->hasho_flag)));
+ }
+ else
+ {
+ if (pageopaque->hasho_flag == LH_META_PAGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("page is a metadata page"),
+ errdetail("Flags %08x.",
+ pageopaque->hasho_flag)));
+ }
+
+ 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);
+
+ /* page type (flags) */
+ if (opaque->hasho_flag & LH_META_PAGE)
+ stat->type = "metapage";
+ else if (opaque->hasho_flag & LH_OVERFLOW_PAGE)
+ stat->type = "overflow";
+ else if (opaque->hasho_flag & LH_BUCKET_PAGE)
+ stat->type = "bucket";
+ else if (opaque->hasho_flag & LH_BITMAP_PAGE)
+ stat->type = "bitmap";
+ else
+ stat->type = "unused";
+
+ /* 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_stats()
+ *
+ * Usage: SELECT * FROM hash_page_stats(get_raw_page('t1_hash', 1));
+ * ---------------------------------------------------
+ */
+Datum
+hash_page_stats(PG_FUNCTION_ARGS)
+{
+ bytea *raw_page = PG_GETARG_BYTEA_P(0);
+ Page page;
+ int j;
+ Datum values[10];
+ bool nulls[10];
+ HashPageStat stat;
+ HeapTuple tuple;
+ TupleDesc tupleDesc;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ page = verify_hash_page(raw_page, false);
+
+ /* 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++] = CStringGetTextDatum(stat.type);
+ values[j++] = UInt32GetDatum(stat.live_items);
+ values[j++] = UInt32GetDatum(stat.dead_items);
+ values[j++] = UInt32GetDatum(stat.page_size);
+ values[j++] = UInt32GetDatum(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('t1_hash', 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];
+ HeapTuple tuple;
+ FuncCallContext *fctx;
+ MemoryContext mctx;
+ struct user_args *uargs;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ TupleDesc tupleDesc;
+
+ fctx = SRF_FIRSTCALL_INIT();
+
+ page = verify_hash_page(raw_page, false);
+
+ /*
+ * We copy the page into local storage to avoid holding pin on the
+ * buffer longer than we must, and possibly failing to release it at
+ * all if the calling query doesn't fetch all rows.
+ */
+ mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
+
+ uargs = palloc(sizeof(struct user_args));
+
+ uargs->page = palloc(BLCKSZ);
+ memcpy(uargs->page, page, BLCKSZ);
+
+ 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;
+ int off;
+ int dlen;
+ char *dump;
+ char *s;
+ char *ptr;
+
+ 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++] = CStringGetTextDatum(psprintf("(%u,%u)",
+ BlockIdGetBlockNumber(&(itup->t_tid.ip_blkid)),
+ itup->t_tid.ip_posid));
+
+ ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
+ dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info);
+ dump = palloc0(dlen * 3 + 1);
+ s = dump;
+ for (off = 0; off < dlen; off++)
+ {
+ if (off > 0)
+ *dump++ = ' ';
+ sprintf(dump, "%02x", *(ptr + off) & 0xff);
+ dump += 2;
+ }
+ values[j] = CStringGetTextDatum(s);
+
+ 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->page);
+ pfree(uargs);
+ SRF_RETURN_DONE(fctx);
+ }
+}
+
+/* ------------------------------------------------
+ * hash_metapage_info()
+ *
+ * Get a hash's meta-page information
+ *
+ * Usage: SELECT * FROM hash_metapage_info(get_raw_page('t1_hash', 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];
+ char *spares;
+ char *mapp;
+ char *s;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ page = verify_hash_page(raw_page, true);
+
+ /* 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++] = CStringGetTextDatum(psprintf("0x%08X", 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);
+
+ spares = palloc0(HASH_MAX_SPLITPOINTS * 5 + 1);
+ s = spares;
+ for (i = 0; i < HASH_MAX_SPLITPOINTS; i++)
+ {
+ if (i > 0)
+ *spares++ = ' ';
+
+ sprintf(spares, "%04x", metad->hashm_spares[i]);
+ spares += 4;
+ }
+ values[j++] = CStringGetTextDatum(s);
+
+ mapp = palloc0(HASH_MAX_BITMAPS * 5 + 1);
+ s = mapp;
+ for (i = 0; i < HASH_MAX_BITMAPS; i++)
+ {
+ if (i > 0)
+ *mapp++ = ' ';
+
+ sprintf(mapp, "%04x", metad->hashm_mapp[i]);
+ mapp += 4;
+ }
+ values[j++] = CStringGetTextDatum(s);
+
+ 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 0000000..7f8c2d2
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.5--1.6.sql
@@ -0,0 +1,59 @@
+/* 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_metapage_info()
+--
+CREATE FUNCTION hash_metapage_info(IN page bytea,
+ OUT magic text,
+ OUT version int4,
+ OUT ntuples double precision,
+ OUT ffactor int2,
+ OUT bsize int2,
+ OUT bmsize int2,
+ OUT bmshift int2,
+ OUT maxbucket int4,
+ OUT highmask int4,
+ OUT lowmask int4,
+ OUT ovflpoint int4,
+ OUT firstfree int4,
+ OUT nmaps int4,
+ OUT procid int4,
+ OUT spares text,
+ OUT mapp text)
+AS 'MODULE_PATHNAME', 'hash_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- hash_page_stats()
+--
+CREATE FUNCTION hash_page_stats(IN page bytea,
+ OUT type text,
+ OUT live_items int4,
+ OUT dead_items int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT hasho_prevblkno int4,
+ OUT hasho_nextblkno int4,
+ OUT hasho_bucket int4,
+ OUT hasho_flag int4,
+ 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 text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'hash_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control
index 23c8eff..1a61c9f 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/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml
index 5d187ed..626feba 100644
--- a/doc/src/sgml/pageinspect.sgml
+++ b/doc/src/sgml/pageinspect.sgml
@@ -14,7 +14,7 @@
</para>
<sect2>
- <title>Functions</title>
+ <title>General functions</title>
<variablelist>
<varlistentry>
@@ -160,7 +160,36 @@ test=# SELECT * FROM heap_page_item_attrs(get_raw_page('pg_class', 0), 'pg_class
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term>
+ <function>fsm_page_contents(page bytea) returns text</function>
+ <indexterm>
+ <primary>fsm_page_contents</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+ <para>
+ <function>fsm_page_contents</function> shows the internal node structure
+ of a FSM page. The output is a multiline string, with one line per
+ node in the binary tree within the page. Only those nodes that are not
+ zero are printed. The so-called "next" pointer, which points to the
+ next slot to be returned from the page, is also printed.
+ </para>
+ <para>
+ See <filename>src/backend/storage/freespace/README</> for more
+ information on the structure of an FSM page.
+ </para>
+ </listitem>
+ </varlistentry>
+</variablelist>
+</sect2>
+ <sect2>
+ <title>B-tree functions</title>
+
+ <variablelist>
<varlistentry>
<term>
<function>bt_metap(relname text) returns record</function>
@@ -261,7 +290,13 @@ test=# SELECT * FROM bt_page_items('pg_cast_oid_index', 1);
</para>
</listitem>
</varlistentry>
+ </variablelist>
+ </sect2>
+
+ <sect2>
+ <title>BRIN functions</title>
+ <variablelist>
<varlistentry>
<term>
<function>brin_page_type(page bytea) returns text</function>
@@ -365,7 +400,13 @@ test=# SELECT * FROM brin_page_items(get_raw_page('brinidx', 5),
</para>
</listitem>
</varlistentry>
+ </variablelist>
+ </sect2>
+
+ <sect2>
+ <title>GIN functions</title>
+ <variablelist>
<varlistentry>
<term>
<function>gin_metapage_info(page bytea) returns record</function>
@@ -449,26 +490,111 @@ test=# SELECT first_tid, nbytes, tids[0:5] as some_tids
</para>
</listitem>
</varlistentry>
+ </variablelist>
+ </sect2>
+
+ <sect2>
+ <title>Hash functions</title>
+ <para>
+ The hash index data structures are defined in <filename>src/include/access/hash.h</>.
+ </para>
+
+ <variablelist>
<varlistentry>
<term>
- <function>fsm_page_contents(page bytea) returns text</function>
+ <function>hash_metapage_info(page bytes) returns record</function>
<indexterm>
- <primary>fsm_page_contents</primary>
+ <primary>hash_metapage_info</primary>
</indexterm>
</term>
<listitem>
<para>
- <function>fsm_page_contents</function> shows the internal node structure
- of a FSM page. The output is a multiline string, with one line per
- node in the binary tree within the page. Only those nodes that are not
- zero are printed. The so-called "next" pointer, which points to the
- next slot to be returned from the page, is also printed.
+ <function>hash_metapage_info</function> returns information about a hash
+ index's metapage. For example:
+<screen>
+test=# SELECT * FROM hash_metapage_info(get_raw_page('mytab_index', 0));
+-[ RECORD 1 ]-----
+magic | 0x06440640
+version | 2
+ntuples | 1000000
+ffactor | 307
+bsize | 8152
+bmsize | 4096
+bmshift | 15
+maxbucket | 4095
+highmask | 8191
+lowmask | 4095
+ovflpoint | 12
+firstfree | 0
+nmaps | 1
+procid | 400
+spares | 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 0000 ...
+mapp | 1001 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 ...
+</screen>
</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <function>hash_page_stats(page bytea) returns record</function>
+ <indexterm>
+ <primary>hash_page_stats</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
<para>
- See <filename>src/backend/storage/freespace/README</> for more
- information on the structure of an FSM page.
+ <function>hash_page_stats</function> returns summary information about
+ single pages of hash indexes. For example:
+<screen>
+test=# SELECT * FROM hash_page_stats(get_raw_page('mytab_index', 1));
+-[ RECORD 1 ]---+-----
+type | bucket
+live_items | 211
+dead_items | 0
+page_size | 8192
+free_size | 3928
+hasho_prevblkno | -1
+hasho_nextblkno | -1
+hasho_bucket | 0
+hasho_flag | 2
+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 detailed information about
+ all of the items on a hash index page. For example:
+<screen>
+test=# SELECT * FROM hash_page_items(get_raw_page('mytab_index', 1));
+ itemoffset | ctid | data
+------------+------------+-------------------------
+ 1 | (2057,114) | 00 a0 25 02 00 00 00 00
+ 2 | (4033,38) | 00 20 8d 02 00 00 00 00
+ 3 | (4223,130) | 00 80 f7 02 00 00 00 00
+ 4 | (1524,126) | 00 70 de 04 00 00 00 00
+ 5 | (5850,86) | 00 70 1a 07 00 00 00 00
+...
+ 210 | (5541,140) | 00 90 a9 ff 00 00 00 00
+ 211 | (4654,93) | 00 d0 fb ff 00 00 00 00
+
+</screen>
+ The <structfield>ctid</> column points to a heap tuple
+ (BlockIdData,OffsetNumber).
</para>
</listitem>
</varlistentry>
--
2.7.4
I wrote some tests for pageinspect, attached here (hash stuff in a
separate patch). I think all the output numbers ought to be
deterministic (I excluded everything that might contain xids). Please test.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
0001-Add-tests-for-pageinspect.patchinvalid/octet-stream; name=0001-Add-tests-for-pageinspect.patchDownload
From b9c649159bc3291785dfe7a56baf4443db70062a Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Thu, 29 Sep 2016 12:00:00 -0400
Subject: [PATCH 1/2] Add tests for pageinspect
---
contrib/pageinspect/.gitignore | 4 ++
contrib/pageinspect/Makefile | 2 +
contrib/pageinspect/expected/brin.out | 51 ++++++++++++++++++++++++
contrib/pageinspect/expected/btree.out | 45 +++++++++++++++++++++
contrib/pageinspect/expected/gin.out | 30 ++++++++++++++
contrib/pageinspect/expected/page.out | 73 ++++++++++++++++++++++++++++++++++
contrib/pageinspect/sql/brin.sql | 18 +++++++++
contrib/pageinspect/sql/btree.sql | 17 ++++++++
contrib/pageinspect/sql/gin.sql | 14 +++++++
contrib/pageinspect/sql/page.sql | 30 ++++++++++++++
10 files changed, 284 insertions(+)
create mode 100644 contrib/pageinspect/.gitignore
create mode 100644 contrib/pageinspect/expected/brin.out
create mode 100644 contrib/pageinspect/expected/btree.out
create mode 100644 contrib/pageinspect/expected/gin.out
create mode 100644 contrib/pageinspect/expected/page.out
create mode 100644 contrib/pageinspect/sql/brin.sql
create mode 100644 contrib/pageinspect/sql/btree.sql
create mode 100644 contrib/pageinspect/sql/gin.sql
create mode 100644 contrib/pageinspect/sql/page.sql
diff --git a/contrib/pageinspect/.gitignore b/contrib/pageinspect/.gitignore
new file mode 100644
index 0000000..5dcb3ff
--- /dev/null
+++ b/contrib/pageinspect/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index c73d728..ecf0df0 100644
--- a/contrib/pageinspect/Makefile
+++ b/contrib/pageinspect/Makefile
@@ -11,6 +11,8 @@ DATA = pageinspect--1.6.sql pageinspect--1.5--1.6.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
+
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
diff --git a/contrib/pageinspect/expected/brin.out b/contrib/pageinspect/expected/brin.out
new file mode 100644
index 0000000..71eb190
--- /dev/null
+++ b/contrib/pageinspect/expected/brin.out
@@ -0,0 +1,51 @@
+CREATE TABLE test1 (a int, b text);
+INSERT INTO test1 VALUES (1, 'one');
+CREATE INDEX test1_a_idx ON test1 USING brin (a);
+SELECT brin_page_type(get_raw_page('test1_a_idx', 0));
+ brin_page_type
+----------------
+ meta
+(1 row)
+
+SELECT brin_page_type(get_raw_page('test1_a_idx', 1));
+ brin_page_type
+----------------
+ revmap
+(1 row)
+
+SELECT brin_page_type(get_raw_page('test1_a_idx', 2));
+ brin_page_type
+----------------
+ regular
+(1 row)
+
+SELECT * FROM brin_metapage_info(get_raw_page('test1_a_idx', 0));
+ magic | version | pagesperrange | lastrevmappage
+------------+---------+---------------+----------------
+ 0xA8109CFA | 1 | 128 | 1
+(1 row)
+
+SELECT * FROM brin_metapage_info(get_raw_page('test1_a_idx', 1));
+ERROR: page is not a BRIN page of type "metapage"
+DETAIL: Expected special type 0000f091, got 0000f092.
+SELECT * FROM brin_revmap_data(get_raw_page('test1_a_idx', 0)) LIMIT 5;
+ERROR: page is not a BRIN page of type "revmap"
+DETAIL: Expected special type 0000f092, got 0000f091.
+SELECT * FROM brin_revmap_data(get_raw_page('test1_a_idx', 1)) LIMIT 5;
+ pages
+-------
+ (2,1)
+ (0,0)
+ (0,0)
+ (0,0)
+ (0,0)
+(5 rows)
+
+SELECT * FROM brin_page_items(get_raw_page('test1_a_idx', 2), 'test1_a_idx')
+ ORDER BY blknum, attnum LIMIT 5;
+ itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | value
+------------+--------+--------+----------+----------+-------------+----------
+ 1 | 0 | 1 | f | f | f | {1 .. 1}
+(1 row)
+
+DROP TABLE test1;
diff --git a/contrib/pageinspect/expected/btree.out b/contrib/pageinspect/expected/btree.out
new file mode 100644
index 0000000..ef7f86e
--- /dev/null
+++ b/contrib/pageinspect/expected/btree.out
@@ -0,0 +1,45 @@
+CREATE TABLE test1 (a int, b text);
+INSERT INTO test1 VALUES (1, 'one');
+CREATE INDEX test1_a_idx ON test1 USING btree (a);
+\x
+SELECT * FROM bt_metap('test1_a_idx');
+-[ RECORD 1 ]-----
+magic | 340322
+version | 2
+root | 1
+level | 0
+fastroot | 1
+fastlevel | 0
+
+SELECT * FROM bt_page_stats('test1_a_idx', 0);
+ERROR: block 0 is a meta page
+SELECT * FROM bt_page_stats('test1_a_idx', 1);
+-[ RECORD 1 ]-+-----
+blkno | 1
+type | l
+live_items | 1
+dead_items | 0
+avg_item_size | 16
+page_size | 8192
+free_size | 8128
+btpo_prev | 0
+btpo_next | 0
+btpo | 0
+btpo_flags | 3
+
+SELECT * FROM bt_page_stats('test1_a_idx', 2);
+ERROR: block number out of range
+SELECT * FROM bt_page_items('test1_a_idx', 0);
+ERROR: block 0 is a meta page
+SELECT * FROM bt_page_items('test1_a_idx', 1);
+-[ RECORD 1 ]-----------------------
+itemoffset | 1
+ctid | (0,1)
+itemlen | 16
+nulls | f
+vars | f
+data | 01 00 00 00 00 00 00 00
+
+SELECT * FROM bt_page_items('test1_a_idx', 2);
+ERROR: block number out of range
+DROP TABLE test1;
diff --git a/contrib/pageinspect/expected/gin.out b/contrib/pageinspect/expected/gin.out
new file mode 100644
index 0000000..4f341e1
--- /dev/null
+++ b/contrib/pageinspect/expected/gin.out
@@ -0,0 +1,30 @@
+CREATE TABLE test1 (x int, y int[]);
+INSERT INTO test1 VALUES (1, ARRAY[11, 111]);
+CREATE INDEX test1_y_idx ON test1 USING gin (y);
+\x
+SELECT * FROM gin_metapage_info(get_raw_page('test1_y_idx', 0));
+-[ RECORD 1 ]----+-----------
+pending_head | 4294967295
+pending_tail | 4294967295
+tail_free_size | 0
+n_pending_pages | 0
+n_pending_tuples | 0
+n_total_pages | 2
+n_entry_pages | 1
+n_data_pages | 0
+n_entries | 2
+version | 2
+
+SELECT * FROM gin_metapage_info(get_raw_page('test1_y_idx', 1));
+ERROR: input page is not a GIN metapage
+DETAIL: Flags 0002, expected 0008
+SELECT * FROM gin_page_opaque_info(get_raw_page('test1_y_idx', 1));
+-[ RECORD 1 ]---------
+rightlink | 4294967295
+maxoff | 0
+flags | {leaf}
+
+SELECT * FROM gin_leafpage_items(get_raw_page('test1_y_idx', 1));
+ERROR: input page is not a compressed GIN data leaf page
+DETAIL: Flags 0002, expected 0083
+DROP TABLE test1;
diff --git a/contrib/pageinspect/expected/page.out b/contrib/pageinspect/expected/page.out
new file mode 100644
index 0000000..673a852
--- /dev/null
+++ b/contrib/pageinspect/expected/page.out
@@ -0,0 +1,73 @@
+CREATE EXTENSION pageinspect;
+CREATE TABLE test1 (a int, b text);
+INSERT INTO test1 VALUES (1, 'one');
+VACUUM test1; -- set up FSM
+-- The page contents can vary, so just test that it can be read
+-- successfully, but don't keep the output.
+SELECT octet_length(get_raw_page('test1', 'main', 0)) AS main_0;
+ main_0
+--------
+ 8192
+(1 row)
+
+SELECT octet_length(get_raw_page('test1', 'main', 1)) AS main_1;
+ERROR: block number 1 is out of range for relation "test1"
+SELECT octet_length(get_raw_page('test1', 'fsm', 0)) AS fsm_0;
+ fsm_0
+-------
+ 8192
+(1 row)
+
+SELECT octet_length(get_raw_page('test1', 'fsm', 1)) AS fsm_1;
+ fsm_1
+-------
+ 8192
+(1 row)
+
+SELECT octet_length(get_raw_page('test1', 'vm', 0)) AS vm_0;
+ vm_0
+------
+ 8192
+(1 row)
+
+SELECT octet_length(get_raw_page('test1', 'vm', 1)) AS vm_1;
+ERROR: block number 1 is out of range for relation "test1"
+SELECT octet_length(get_raw_page('xxx', 'main', 0));
+ERROR: relation "xxx" does not exist
+SELECT octet_length(get_raw_page('test1', 'xxx', 0));
+ERROR: invalid fork name
+HINT: Valid fork names are "main", "fsm", "vm", and "init".
+SELECT get_raw_page('test1', 0) = get_raw_page('test1', 'main', 0);
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT tuple_data_split('test1'::regclass, t_data, t_infomask, t_infomask2, t_bits)
+ FROM heap_page_items(get_raw_page('test1', 0));
+ tuple_data_split
+-------------------------------
+ {"\\x01000000","\\x096f6e65"}
+(1 row)
+
+SELECT * FROM fsm_page_contents(get_raw_page('test1', 'fsm', 0));
+ fsm_page_contents
+-------------------
+ 0: 254 +
+ 1: 254 +
+ 3: 254 +
+ 7: 254 +
+ 15: 254 +
+ 31: 254 +
+ 63: 254 +
+ 127: 254 +
+ 255: 254 +
+ 511: 254 +
+ 1023: 254 +
+ 2047: 254 +
+ 4095: 254 +
+ fp_next_slot: 0 +
+
+(1 row)
+
+DROP TABLE test1;
diff --git a/contrib/pageinspect/sql/brin.sql b/contrib/pageinspect/sql/brin.sql
new file mode 100644
index 0000000..735bc3b
--- /dev/null
+++ b/contrib/pageinspect/sql/brin.sql
@@ -0,0 +1,18 @@
+CREATE TABLE test1 (a int, b text);
+INSERT INTO test1 VALUES (1, 'one');
+CREATE INDEX test1_a_idx ON test1 USING brin (a);
+
+SELECT brin_page_type(get_raw_page('test1_a_idx', 0));
+SELECT brin_page_type(get_raw_page('test1_a_idx', 1));
+SELECT brin_page_type(get_raw_page('test1_a_idx', 2));
+
+SELECT * FROM brin_metapage_info(get_raw_page('test1_a_idx', 0));
+SELECT * FROM brin_metapage_info(get_raw_page('test1_a_idx', 1));
+
+SELECT * FROM brin_revmap_data(get_raw_page('test1_a_idx', 0)) LIMIT 5;
+SELECT * FROM brin_revmap_data(get_raw_page('test1_a_idx', 1)) LIMIT 5;
+
+SELECT * FROM brin_page_items(get_raw_page('test1_a_idx', 2), 'test1_a_idx')
+ ORDER BY blknum, attnum LIMIT 5;
+
+DROP TABLE test1;
diff --git a/contrib/pageinspect/sql/btree.sql b/contrib/pageinspect/sql/btree.sql
new file mode 100644
index 0000000..4fa3416
--- /dev/null
+++ b/contrib/pageinspect/sql/btree.sql
@@ -0,0 +1,17 @@
+CREATE TABLE test1 (a int, b text);
+INSERT INTO test1 VALUES (1, 'one');
+CREATE INDEX test1_a_idx ON test1 USING btree (a);
+
+\x
+
+SELECT * FROM bt_metap('test1_a_idx');
+
+SELECT * FROM bt_page_stats('test1_a_idx', 0);
+SELECT * FROM bt_page_stats('test1_a_idx', 1);
+SELECT * FROM bt_page_stats('test1_a_idx', 2);
+
+SELECT * FROM bt_page_items('test1_a_idx', 0);
+SELECT * FROM bt_page_items('test1_a_idx', 1);
+SELECT * FROM bt_page_items('test1_a_idx', 2);
+
+DROP TABLE test1;
diff --git a/contrib/pageinspect/sql/gin.sql b/contrib/pageinspect/sql/gin.sql
new file mode 100644
index 0000000..ba79ff2
--- /dev/null
+++ b/contrib/pageinspect/sql/gin.sql
@@ -0,0 +1,14 @@
+CREATE TABLE test1 (x int, y int[]);
+INSERT INTO test1 VALUES (1, ARRAY[11, 111]);
+CREATE INDEX test1_y_idx ON test1 USING gin (y);
+
+\x
+
+SELECT * FROM gin_metapage_info(get_raw_page('test1_y_idx', 0));
+SELECT * FROM gin_metapage_info(get_raw_page('test1_y_idx', 1));
+
+SELECT * FROM gin_page_opaque_info(get_raw_page('test1_y_idx', 1));
+
+SELECT * FROM gin_leafpage_items(get_raw_page('test1_y_idx', 1));
+
+DROP TABLE test1;
diff --git a/contrib/pageinspect/sql/page.sql b/contrib/pageinspect/sql/page.sql
new file mode 100644
index 0000000..a304151
--- /dev/null
+++ b/contrib/pageinspect/sql/page.sql
@@ -0,0 +1,30 @@
+CREATE EXTENSION pageinspect;
+
+CREATE TABLE test1 (a int, b text);
+INSERT INTO test1 VALUES (1, 'one');
+
+VACUUM test1; -- set up FSM
+
+-- The page contents can vary, so just test that it can be read
+-- successfully, but don't keep the output.
+
+SELECT octet_length(get_raw_page('test1', 'main', 0)) AS main_0;
+SELECT octet_length(get_raw_page('test1', 'main', 1)) AS main_1;
+
+SELECT octet_length(get_raw_page('test1', 'fsm', 0)) AS fsm_0;
+SELECT octet_length(get_raw_page('test1', 'fsm', 1)) AS fsm_1;
+
+SELECT octet_length(get_raw_page('test1', 'vm', 0)) AS vm_0;
+SELECT octet_length(get_raw_page('test1', 'vm', 1)) AS vm_1;
+
+SELECT octet_length(get_raw_page('xxx', 'main', 0));
+SELECT octet_length(get_raw_page('test1', 'xxx', 0));
+
+SELECT get_raw_page('test1', 0) = get_raw_page('test1', 'main', 0);
+
+SELECT tuple_data_split('test1'::regclass, t_data, t_infomask, t_infomask2, t_bits)
+ FROM heap_page_items(get_raw_page('test1', 0));
+
+SELECT * FROM fsm_page_contents(get_raw_page('test1', 'fsm', 0));
+
+DROP TABLE test1;
--
2.10.0
0002-Add-tests-for-pageinspect-hash.patchinvalid/octet-stream; name=0002-Add-tests-for-pageinspect-hash.patchDownload
From a690c18221dd9c05cc7c9ae42b5046c866dae5a7 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Thu, 29 Sep 2016 12:00:00 -0400
Subject: [PATCH 2/2] Add tests for pageinspect hash
---
contrib/pageinspect/Makefile | 2 +-
contrib/pageinspect/expected/hash.out | 131 ++++++++++++++++++++++++++++++++++
contrib/pageinspect/sql/hash.sql | 29 ++++++++
3 files changed, 161 insertions(+), 1 deletion(-)
create mode 100644 contrib/pageinspect/expected/hash.out
create mode 100644 contrib/pageinspect/sql/hash.sql
diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index ecf0df0..d566c35 100644
--- a/contrib/pageinspect/Makefile
+++ b/contrib/pageinspect/Makefile
@@ -11,7 +11,7 @@ DATA = pageinspect--1.6.sql pageinspect--1.5--1.6.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 0000000..c605297
--- /dev/null
+++ b/contrib/pageinspect/expected/hash.out
@@ -0,0 +1,131 @@
+CREATE TABLE test1 (a int, b text);
+INSERT INTO test1 VALUES (1, 'one');
+CREATE INDEX test1_a_idx ON test1 USING hash (a);
+WARNING: hash indexes are not WAL-logged and their use is discouraged
+\x
+SELECT * FROM hash_metapage_info(get_raw_page('test1_a_idx', 0));
+-[ RECORD 1 ]--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+magic | 0x06440640
+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 | 0000 0000 0001 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+mapp | 0005 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+
+SELECT * FROM hash_metapage_info(get_raw_page('test1_a_idx', 1));
+ERROR: page is not a metadata page
+DETAIL: Expected 00000008, got 00000002.
+SELECT * FROM hash_metapage_info(get_raw_page('test1_a_idx', 2));
+ERROR: page is not a metadata page
+DETAIL: Expected 00000008, got 00000002.
+SELECT * FROM hash_metapage_info(get_raw_page('test1_a_idx', 3));
+ERROR: page is not a metadata page
+DETAIL: Expected 00000008, got 00000002.
+SELECT * FROM hash_metapage_info(get_raw_page('test1_a_idx', 4));
+ERROR: page is not a metadata page
+DETAIL: Expected 00000008, got 00000002.
+SELECT * FROM hash_metapage_info(get_raw_page('test1_a_idx', 5));
+ERROR: page is not a metadata page
+DETAIL: Expected 00000008, got 00000004.
+SELECT * FROM hash_metapage_info(get_raw_page('test1_a_idx', 6));
+ERROR: block number 6 is out of range for relation "test1_a_idx"
+SELECT * FROM hash_page_stats(get_raw_page('test1_a_idx', 0));
+ERROR: page is a metadata page
+DETAIL: Flags 00000008.
+SELECT * FROM hash_page_stats(get_raw_page('test1_a_idx', 1));
+-[ RECORD 1 ]---+-------
+type | bucket
+live_items | 0
+dead_items | 0
+page_size | 8192
+free_size | 8148
+hasho_prevblkno | -1
+hasho_nextblkno | -1
+hasho_bucket | 0
+hasho_flag | 2
+hasho_page_id | 65408
+
+SELECT * FROM hash_page_stats(get_raw_page('test1_a_idx', 2));
+-[ RECORD 1 ]---+-------
+type | bucket
+live_items | 0
+dead_items | 0
+page_size | 8192
+free_size | 8148
+hasho_prevblkno | -1
+hasho_nextblkno | -1
+hasho_bucket | 1
+hasho_flag | 2
+hasho_page_id | 65408
+
+SELECT * FROM hash_page_stats(get_raw_page('test1_a_idx', 3));
+-[ RECORD 1 ]---+-------
+type | bucket
+live_items | 1
+dead_items | 0
+page_size | 8192
+free_size | 8128
+hasho_prevblkno | -1
+hasho_nextblkno | -1
+hasho_bucket | 2
+hasho_flag | 2
+hasho_page_id | 65408
+
+SELECT * FROM hash_page_stats(get_raw_page('test1_a_idx', 4));
+-[ RECORD 1 ]---+-------
+type | bucket
+live_items | 0
+dead_items | 0
+page_size | 8192
+free_size | 8148
+hasho_prevblkno | -1
+hasho_nextblkno | -1
+hasho_bucket | 3
+hasho_flag | 2
+hasho_page_id | 65408
+
+SELECT * FROM hash_page_stats(get_raw_page('test1_a_idx', 5));
+-[ RECORD 1 ]---+-------
+type | bitmap
+live_items | 0
+dead_items | 0
+page_size | 8192
+free_size | 8148
+hasho_prevblkno | -1
+hasho_nextblkno | -1
+hasho_bucket | -1
+hasho_flag | 4
+hasho_page_id | 65408
+
+SELECT * FROM hash_page_items(get_raw_page('test1_a_idx', 0));
+ERROR: page is a metadata page
+DETAIL: Flags 00000008.
+SELECT * FROM hash_page_items(get_raw_page('test1_a_idx', 1));
+(0 rows)
+
+SELECT * FROM hash_page_items(get_raw_page('test1_a_idx', 2));
+(0 rows)
+
+SELECT * FROM hash_page_items(get_raw_page('test1_a_idx', 3));
+-[ RECORD 1 ]-----------------------
+itemoffset | 1
+ctid | (2359296,12328)
+data | 46 17 73 8e 00 00 00 00
+
+SELECT * FROM hash_page_items(get_raw_page('test1_a_idx', 4));
+(0 rows)
+
+SELECT * FROM hash_page_items(get_raw_page('test1_a_idx', 5));
+(0 rows)
+
+DROP TABLE test1;
diff --git a/contrib/pageinspect/sql/hash.sql b/contrib/pageinspect/sql/hash.sql
new file mode 100644
index 0000000..5f5f6f8
--- /dev/null
+++ b/contrib/pageinspect/sql/hash.sql
@@ -0,0 +1,29 @@
+CREATE TABLE test1 (a int, b text);
+INSERT INTO test1 VALUES (1, 'one');
+CREATE INDEX test1_a_idx ON test1 USING hash (a);
+
+\x
+
+SELECT * FROM hash_metapage_info(get_raw_page('test1_a_idx', 0));
+SELECT * FROM hash_metapage_info(get_raw_page('test1_a_idx', 1));
+SELECT * FROM hash_metapage_info(get_raw_page('test1_a_idx', 2));
+SELECT * FROM hash_metapage_info(get_raw_page('test1_a_idx', 3));
+SELECT * FROM hash_metapage_info(get_raw_page('test1_a_idx', 4));
+SELECT * FROM hash_metapage_info(get_raw_page('test1_a_idx', 5));
+SELECT * FROM hash_metapage_info(get_raw_page('test1_a_idx', 6));
+
+SELECT * FROM hash_page_stats(get_raw_page('test1_a_idx', 0));
+SELECT * FROM hash_page_stats(get_raw_page('test1_a_idx', 1));
+SELECT * FROM hash_page_stats(get_raw_page('test1_a_idx', 2));
+SELECT * FROM hash_page_stats(get_raw_page('test1_a_idx', 3));
+SELECT * FROM hash_page_stats(get_raw_page('test1_a_idx', 4));
+SELECT * FROM hash_page_stats(get_raw_page('test1_a_idx', 5));
+
+SELECT * FROM hash_page_items(get_raw_page('test1_a_idx', 0));
+SELECT * FROM hash_page_items(get_raw_page('test1_a_idx', 1));
+SELECT * FROM hash_page_items(get_raw_page('test1_a_idx', 2));
+SELECT * FROM hash_page_items(get_raw_page('test1_a_idx', 3));
+SELECT * FROM hash_page_items(get_raw_page('test1_a_idx', 4));
+SELECT * FROM hash_page_items(get_raw_page('test1_a_idx', 5));
+
+DROP TABLE test1;
--
2.10.0
I think we should look into handling the different page types better.
The hash_page_stats function was copied from btree, which only has one
type. It's not clear whether all the values apply to each page type.
At least they should be null if they don't apply. BRIN has a separate
function for each page type, which might make more sense. I suggest
taken the test suite that I posted and expanding the tests so that we
see output for each different page type.
Besides that, I would still like better data types for some of the
output columns, as previously discussed. In addition to what I already
pointed out, the ctid column can be of type ctid instead of text.
Since the commit fest is drawing to a close, I'll set this patch as
returned with feedback. Please continue working on it, since there is
clearly renewed interest in hash indexes, and we'll need this type of
functionality for that.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 9/29/16 4:00 PM, Peter Eisentraut wrote:
Since the commit fest is drawing to a close, I'll set this patch as
returned with feedback.
Actually, the CF app informs me that moving to the next CF is more
appropriate, so I have done that.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 09/29/2016 04:02 PM, Peter Eisentraut wrote:
On 9/29/16 4:00 PM, Peter Eisentraut wrote:
Since the commit fest is drawing to a close, I'll set this patch as
returned with feedback.Actually, the CF app informs me that moving to the next CF is more
appropriate, so I have done that.
Ok, thanks for your feedback.
Maybe "Returned with Feedback" is more appropriate, as there is still
development left.
Best regards,
Jesper
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Oct 3, 2016 at 9:52 PM, Jesper Pedersen
<jesper.pedersen@redhat.com> wrote:
Maybe "Returned with Feedback" is more appropriate, as there is still
development left.
I have switched it to "waiting on author".
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 10/3/16 8:52 AM, Jesper Pedersen wrote:
On 09/29/2016 04:02 PM, Peter Eisentraut wrote:
On 9/29/16 4:00 PM, Peter Eisentraut wrote:
Since the commit fest is drawing to a close, I'll set this patch as
returned with feedback.Actually, the CF app informs me that moving to the next CF is more
appropriate, so I have done that.Ok, thanks for your feedback.
Maybe "Returned with Feedback" is more appropriate, as there is still
development left.
I have applied the documentation change that introduced subsections,
which seems quite useful independently. I have also committed the tests
that I proposed and will work through the failures.
As no new patch has been posted for the 2016-11 CF, I will close the
patch entry now. Please submit an updated patch when you have the time,
keeping an eye on ongoing work to update hash indexes.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 11/01/2016 03:28 PM, Peter Eisentraut wrote:
Ok, thanks for your feedback.
Maybe "Returned with Feedback" is more appropriate, as there is still
development left.I have applied the documentation change that introduced subsections,
which seems quite useful independently. I have also committed the tests
that I proposed and will work through the failures.As no new patch has been posted for the 2016-11 CF, I will close the
patch entry now. Please submit an updated patch when you have the time,
keeping an eye on ongoing work to update hash indexes.
Agreed.
Best regards,
Jesper
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 11/1/16 3:28 PM, Peter Eisentraut wrote:
I have also committed the tests
that I proposed and will work through the failures.
So, we're down to crashes in gin_metapage_info() on ia64 and sparc64.
My guess is that the raw page data that is passed into the function
needs to be 8-byte aligned before being accessed as GinMetaPageData.
(Maybe GinPageGetMeta() should do it?) Does anyone have a box like this
handy to experiment? Of course an actual core dump could tell more.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Peter Eisentraut <peter.eisentraut@2ndquadrant.com> writes:
So, we're down to crashes in gin_metapage_info() on ia64 and sparc64.
My guess is that the raw page data that is passed into the function
needs to be 8-byte aligned before being accessed as GinMetaPageData.
That's what it looks like to me, too. The "bytea" page image is
guaranteed to be improperly aligned for 64-bit access, since it will have
an int32 length word before the actual page data, breaking the alignment
that would exist for a page sitting in a page buffer. This is likely to
be a problem for more things than just gin_metapage_info(); sooner or
later it could affect just about everything in pageinspect.
(Maybe GinPageGetMeta() should do it?)
I think the right thing is likely to be to copy the presented bytea
into a palloc'd (and therefore properly aligned) buffer. And not
just in this one function.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 11/2/16 1:54 PM, Tom Lane wrote:
I think the right thing is likely to be to copy the presented bytea
into a palloc'd (and therefore properly aligned) buffer. And not
just in this one function.
Does the attached look reasonable?
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
0001-pageinspect-Fix-unaligned-struct-access-in-GIN-funct.patchtext/x-patch; name=0001-pageinspect-Fix-unaligned-struct-access-in-GIN-funct.patchDownload
From 086c603016de5018a8baddc88a8932472e0fcd1e Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Thu, 3 Nov 2016 12:00:00 -0400
Subject: [PATCH] pageinspect: Fix unaligned struct access in GIN functions
The raw page data that is passed into the functions will not be aligned
at 8-byte boundaries. Casting that to a struct and accessing int64
fields will result in unaligned access. On most platforms, you get away
with it, but it will result on a crash on pickier platforms such as ia64
and sparc64.
---
contrib/pageinspect/ginfuncs.c | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/contrib/pageinspect/ginfuncs.c b/contrib/pageinspect/ginfuncs.c
index 145e2ce..660d236 100644
--- a/contrib/pageinspect/ginfuncs.c
+++ b/contrib/pageinspect/ginfuncs.c
@@ -51,7 +51,10 @@ gin_metapage_info(PG_FUNCTION_ARGS)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("input page too small (%d bytes)", raw_page_size)));
- page = VARDATA(raw_page);
+
+ /* make a copy so that the page is properly aligned for struct access */
+ page = palloc(raw_page_size);
+ memcpy(page, VARDATA(raw_page), raw_page_size);
opaq = (GinPageOpaque) PageGetSpecialPointer(page);
if (opaq->flags != GIN_META)
@@ -115,7 +118,10 @@ gin_page_opaque_info(PG_FUNCTION_ARGS)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("input page too small (%d bytes)", raw_page_size)));
- page = VARDATA(raw_page);
+
+ /* make a copy so that the page is properly aligned for struct access */
+ page = palloc(raw_page_size);
+ memcpy(page, VARDATA(raw_page), raw_page_size);
opaq = (GinPageOpaque) PageGetSpecialPointer(page);
@@ -195,7 +201,10 @@ gin_leafpage_items(PG_FUNCTION_ARGS)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("input page too small (%d bytes)", raw_page_size)));
- page = VARDATA(raw_page);
+
+ /* make a copy so that the page is properly aligned for struct access */
+ page = palloc(raw_page_size);
+ memcpy(page, VARDATA(raw_page), raw_page_size);
if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GinPageOpaqueData)))
ereport(ERROR,
--
2.10.2
Peter Eisentraut <peter.eisentraut@2ndquadrant.com> writes:
On 11/2/16 1:54 PM, Tom Lane wrote:
I think the right thing is likely to be to copy the presented bytea
into a palloc'd (and therefore properly aligned) buffer. And not
just in this one function.
Does the attached look reasonable?
I'd be inclined to wrap it in some kind of support function for
conciseness; and you could put the other boilerplate (the length check)
there too. Otherwise, +1.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi All,
I have spent some time in reviewing the latest v8 patch from Jesper
and could find some issues which i would like to list down,
1) Firstly, the DATA section in the Makefile is referring to
"pageinspect--1.6.sql" file and currently this file is missing.
+DATA = pageinspect--1.6.sql pageinspect--1.5--1.6.sql
2) Secondly, I can see that the server is crashing when following
queries are executed on a code build with cassert-enabled.
postgres=# SELECT * FROM hash_page_items(get_raw_page('con_hash_index', 25000));
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
!> \q
[ashu@localhost bin]$ ./psql -d postgres
psql (10devel)
Type "help" for help.
postgres=# SELECT * FROM hash_page_stats(get_raw_page('con_hash_index', 25000));
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
!> \q
postgres=# SELECT * FROM
hash_metapage_info(get_raw_page('con_hash_index', 25000));
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
!>
3) Thirdly, AFAIU the functions hash_page_stats() and
hash_page_items() are basically dedicated for bucket and overflow
pages in hash index and may not be used with bitmap page. It is
currently blocked for metapage but not for the bitmap page. I think we
need to block it for bitmap page as well.
Apart from issues listed above there are few other review comments
that has not been addressed yet. Considering the fact that it is a
very important project from hash index perspective and as there has
been no work being done on this project for quite some time, I thought
of working on it to close some of the open review comments along with
the issues found by myself. I would like to share the revised patch
which is based on Jesper's v8 patch. The patch has mainly following
following changes in it,
1) It introduces two new functions hash_page_type() and
hash_bitmap_info(). hash_page_type basically displays the type of hash
page whereas hash_bitmap_info() shows the status of a bit for a
particular overflow page in bitmap page of hash index.
2) The functions hash_page_stats() and hash_page_items() basically
shows the information about data stored in bucket and overflow pages
of hash index. If a metapage or bitmap page is passed as an input to
this function it throws an error saying "Expected page type:'' got:
''".
3) It also improves verify_hash_page() function to handle any type of
page in hash index. It is now being used as
verify_hash_page('raw_page', <page_type>) to verify if the page passed
to this function is of 'page_type' or not. Here page_type can be
LH_META_PAGE, LH_BUCKET_PAGE, LH_OVERFLOW_PAGE, LH_BITMAP_PAGE.
Attached is the revised patch. Please have a look and let me know your
feedback. I have also changed the status for this patch in the
upcoming commitfest to "needs review". Thanks.
Attachments:
0001-Add-support-for-hash-index-in-pageinspect-contrib-mo.patchtext/x-patch; charset=US-ASCII; name=0001-Add-support-for-hash-index-in-pageinspect-contrib-mo.patchDownload
From 46c375dbba6b345165b3a2130f5a582db18b813e Mon Sep 17 00:00:00 2001
From: ashu <ashu@localhost.localdomain>
Date: Tue, 20 Dec 2016 13:49:02 +0530
Subject: [PATCH] Add support for hash index in pageinspect contrib module v9
Revised patch by Ashutosh. Original patch was from Jesper
Pedersen.
---
contrib/pageinspect/Makefile | 10 +-
contrib/pageinspect/hashfuncs.c | 558 ++++++++++++++++++++++++++
contrib/pageinspect/pageinspect--1.5--1.6.sql | 76 ++++
contrib/pageinspect/pageinspect.control | 2 +-
doc/src/sgml/pageinspect.sgml | 149 +++++++
src/backend/access/hash/hashovfl.c | 12 +
src/include/access/hash.h | 1 +
7 files changed, 802 insertions(+), 6 deletions(-)
create mode 100644 contrib/pageinspect/hashfuncs.c
create mode 100644 contrib/pageinspect/pageinspect--1.5--1.6.sql
diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index 87a28e9..6897f90 100644
--- a/contrib/pageinspect/Makefile
+++ b/contrib/pageinspect/Makefile
@@ -2,13 +2,13 @@
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--1.6.sql 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
PGFILEDESC = "pageinspect - functions to inspect contents of database pages"
REGRESS = page btree brin gin
diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c
new file mode 100644
index 0000000..5b9c848
--- /dev/null
+++ b/contrib/pageinspect/hashfuncs.c
@@ -0,0 +1,558 @@
+/*
+ * hashfuncs.c
+ * Functions to investigate the content of HASH indexes
+ *
+ * Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * contrib/pageinspect/hashfuncs.c
+ */
+
+#include "postgres.h"
+
+#include "access/hash.h"
+#include "access/htup_details.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
+{
+ uint32 live_items;
+ uint32 dead_items;
+ uint32 page_size;
+ uint32 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;
+ HashPageOpaque pageopaque;
+
+ raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
+
+ if (raw_page_size != BLCKSZ)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("input page too small"),
+ 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 unexpected 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)));
+
+ if (!(pageopaque->hasho_flag & flags))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("page is not a hash page of expected type"),
+ errdetail("Expected hash page type: %08x got: %08x.",
+ flags, pageopaque->hasho_flag)));
+ 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 */
+ if (opaque->hasho_flag & LH_BUCKET_PAGE)
+ stat->hasho_prevblkno = InvalidBlockNumber;
+ else
+ 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 * FROM hash_page_type(get_raw_page('t1_hash', 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, LH_META_PAGE | LH_BUCKET_PAGE |
+ LH_OVERFLOW_PAGE | LH_BITMAP_PAGE);
+ 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('t1_hash', 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 pageinspect 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++] = UInt32GetDatum(stat.live_items);
+ values[j++] = UInt32GetDatum(stat.dead_items);
+ values[j++] = UInt32GetDatum(stat.page_size);
+ values[j++] = UInt32GetDatum(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('t1_hash', 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];
+ HeapTuple tuple;
+ FuncCallContext *fctx;
+ MemoryContext mctx;
+ struct user_args *uargs;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ TupleDesc tupleDesc;
+
+ fctx = SRF_FIRSTCALL_INIT();
+
+ page = verify_hash_page(raw_page, LH_BUCKET_PAGE | LH_OVERFLOW_PAGE);
+
+ /*
+ * We copy the page into local storage to avoid holding pin on the
+ * buffer longer than we must, and possibly failing to release it at
+ * all if the calling query doesn't fetch all rows.
+ */
+ mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
+
+ uargs = palloc(sizeof(struct user_args));
+
+ uargs->page = palloc(BLCKSZ);
+ memcpy(uargs->page, page, BLCKSZ);
+
+ 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;
+ int off;
+ int dlen;
+ char *dump;
+ char *s;
+ char *ptr;
+
+ 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++] = CStringGetTextDatum(psprintf("(%u,%u)",
+ BlockIdGetBlockNumber(&(itup->t_tid.ip_blkid)),
+ itup->t_tid.ip_posid));
+
+ ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
+ dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info);
+ dump = palloc0(dlen * 3 + 1);
+ s = dump;
+ for (off = 0; off < dlen; off++)
+ {
+ if (off > 0)
+ *dump++ = ' ';
+ sprintf(dump, "%02x", *(ptr + off) & 0xff);
+ dump += 2;
+ }
+ values[j] = CStringGetTextDatum(s);
+
+ 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->page);
+ pfree(uargs);
+ SRF_RETURN_DONE(fctx);
+ }
+}
+
+/* ------------------------------------------------
+ * hash_bitmap_info()
+ *
+ * Get bitmap information for a particular overflow page
+ *
+ * Usage: SELECT * FROM hash_bitmap_info('idx'::regclass, 5);
+ * ------------------------------------------------
+ */
+Datum
+hash_bitmap_info(PG_FUNCTION_ARGS)
+{
+ Oid indexRelid = PG_GETARG_OID(0);
+ uint32 ovflblkno = PG_GETARG_UINT32(1);
+ bytea *raw_page;
+ char *raw_page_data;
+ HashMetaPage metap;
+ Buffer buf, metabuf, mapbuf;
+ BlockNumber bitmapblkno;
+ Page page, mappage;
+ HashPageOpaque pageopaque;
+ TupleDesc tupleDesc;
+ Relation indexRel;
+ uint32 ovflbitno, bit = 0;
+ int32 bitmappage,bitmapbit;
+ HeapTuple tuple;
+ int j;
+ Datum values[2];
+ bool nulls[2];
+ uint32 *freep = NULL;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect 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))));
+
+ /* Create a raw page */
+ raw_page = (bytea *) palloc(BLCKSZ + VARHDRSZ);
+ SET_VARSIZE(raw_page, BLCKSZ + VARHDRSZ);
+ raw_page_data = VARDATA(raw_page);
+
+ buf = ReadBufferExtended(indexRel, MAIN_FORKNUM, ovflblkno, RBM_NORMAL, NULL);
+ LockBuffer(buf, BUFFER_LOCK_SHARE);
+
+ memcpy(raw_page_data, BufferGetPage(buf), BLCKSZ);
+
+ LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+ ReleaseBuffer(buf);
+
+ page = verify_hash_page(raw_page, LH_OVERFLOW_PAGE);
+ pageopaque = (HashPageOpaque) PageGetSpecialPointer(page);
+
+ if (pageopaque->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, pageopaque->hasho_flag)));
+
+ /* 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 = convert_ovflblkno_to_bitno(metap, ovflblkno);
+
+ bitmappage = ovflbitno >> BMPG_SHIFT(metap);
+ bitmapbit = ovflbitno & BMPG_MASK(metap);
+
+ if (bitmappage >= metap->hashm_nmaps)
+ elog(ERROR, "invalid overflow bit number %u", ovflbitno);
+
+ bitmapblkno = metap->hashm_mapp[bitmappage];
+
+ /* Check the status of bitmap bit for overflow page */
+ mapbuf = _hash_getbuf(indexRel, bitmapblkno, HASH_WRITE, LH_BITMAP_PAGE);
+ mappage = BufferGetPage(mapbuf);
+ freep = HashPageGetBitmap(mappage);
+
+ if (ISSET(freep, bitmapbit))
+ bit = 1;
+
+ _hash_relbuf(indexRel, metabuf);
+
+ _hash_relbuf(indexRel, mapbuf);
+
+ 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++] = UInt32GetDatum(bit);
+
+ tuple = heap_form_tuple(tupleDesc, values, nulls);
+
+ PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
+}
+
+/* ------------------------------------------------
+ * hash_metapage_info()
+ *
+ * Get a hash's meta-page information
+ *
+ * Usage: SELECT * FROM hash_metapage_info(get_raw_page('t1_hash', 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];
+ char *spares;
+ char *mapp;
+ char *s;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect 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++] = CStringGetTextDatum(psprintf("0x%08X", 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);
+
+ spares = palloc0(HASH_MAX_SPLITPOINTS * 5 + 1);
+ s = spares;
+ for (i = 0; i < HASH_MAX_SPLITPOINTS; i++)
+ {
+ if (i > 0)
+ *spares++ = ' ';
+
+ sprintf(spares, "%04x", metad->hashm_spares[i]);
+ spares += 4;
+ }
+ values[j++] = CStringGetTextDatum(s);
+
+ mapp = palloc0(HASH_MAX_BITMAPS * 5 + 1);
+ s = mapp;
+ for (i = 0; i < HASH_MAX_BITMAPS; i++)
+ {
+ if (i > 0)
+ *mapp++ = ' ';
+
+ sprintf(mapp, "%04x", metad->hashm_mapp[i]);
+ mapp += 4;
+ }
+ values[j++] = CStringGetTextDatum(s);
+
+ 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 0000000..08d844c
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.5--1.6.sql
@@ -0,0 +1,76 @@
+/* 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 int4,
+ OUT dead_items int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT hasho_prevblkno int4,
+ OUT hasho_nextblkno int4,
+ OUT hasho_bucket int4,
+ OUT hasho_flag int4,
+ 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 text)
+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 int4,
+ OUT bitmapblkno int4,
+ OUT bitmapbit int4)
+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 text,
+ OUT version int4,
+ OUT ntuples double precision,
+ OUT ffactor int2,
+ OUT bsize int2,
+ OUT bmsize int2,
+ OUT bmshift int2,
+ OUT maxbucket int4,
+ OUT highmask int4,
+ OUT lowmask int4,
+ OUT ovflpoint int4,
+ OUT firstfree int4,
+ OUT nmaps int4,
+ OUT procid int4,
+ OUT spares text,
+ OUT mapp text)
+AS 'MODULE_PATHNAME', 'hash_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control
index 23c8eff..1a61c9f 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/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml
index d12dbac..839058f 100644
--- a/doc/src/sgml/pageinspect.sgml
+++ b/doc/src/sgml/pageinspect.sgml
@@ -493,4 +493,153 @@ test=# SELECT first_tid, nbytes, tids[0:5] AS some_tids
</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 * FROM 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
+ bucket or overflow page of <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 | -1
+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 <acronym>HASH</acronym>
+ index page. For example:
+<screen>
+test=# SELECT * FROM hash_page_items(get_raw_page('con_hash_index', 1));
+ itemoffset | ctid | data
+------------+-----------------+-------------------------
+ 1 | (3145728,14376) | 00 c0 ca 3e 00 00 00 00
+ 2 | (3145728,14376) | 00 c0 ca 3e 00 00 00 00
+ 3 | (3407872,14376) | 00 c0 ca 3e 00 00 00 00
+ 4 | (3407872,14376) | 00 c0 ca 3e 00 00 00 00
+ 5 | (3407872,14376) | 00 c0 ca 3e 00 00 00 00
+...
+ 404 | (2883584,14120) | 00 c0 ca 3e 00 00 00 00
+ 405 | (2883584,13608) | 00 c0 ca 3e 00 00 00 00
+ 406 | (2621440,13096) | 00 c0 ca 3e 00 00 00 00
+ 407 | (2621440,12584) | 00 c0 ca 3e 00 00 00 00
+</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> returns information about the
+ the status of a bit for an overflow page in bitmap page of <acronym>HASH</acronym>
+ index. For example:
+<screen>
+test=# SELECT * FROM hash_bitmap_info('con_hash_index', 2050);
+ bitmapblkno | bitmapbit
+-------------+-----------
+ 65 | 1
+</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 | 0x06440640
+version | 2
+ntuples | 686000
+ffactor | 40
+bsize | 8152
+bmsize | 4096
+bmshift | 15
+maxbucket | 17149
+highmask | 32767
+lowmask | 16383
+ovflpoint | 15
+firstfree | 2012
+nmaps | 1
+procid | 450
+spares | 0000 0000 0000 0000 0000 0000 0001 0001 0001 0001 0001 0004 003b 02c0 07c3 07dc 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+mapp | 0041 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </sect2>
+
</sect1>
diff --git a/src/backend/access/hash/hashovfl.c b/src/backend/access/hash/hashovfl.c
index df7838c..458bc8f 100644
--- a/src/backend/access/hash/hashovfl.c
+++ b/src/backend/access/hash/hashovfl.c
@@ -802,3 +802,15 @@ _hash_squeezebucket(Relation rel,
/* NOTREACHED */
}
+
+/*
+ * convert_ovflblkno_to_bitno() - Returns the bitno corresponding
+ * to an overflow page. The function blkno_to_bitno() already
+ * does this but as it is a static function it cannot be called
+ * from outside this file.
+ */
+uint32
+convert_ovflblkno_to_bitno(HashMetaPage metap, BlockNumber ovflblkno)
+{
+ return blkno_to_bitno(metap, ovflblkno);
+}
diff --git a/src/include/access/hash.h b/src/include/access/hash.h
index 627fa2c..1794389 100644
--- a/src/include/access/hash.h
+++ b/src/include/access/hash.h
@@ -321,6 +321,7 @@ extern void _hash_squeezebucket(Relation rel,
Bucket bucket, BlockNumber bucket_blkno,
Buffer bucket_buf,
BufferAccessStrategy bstrategy);
+extern uint32 convert_ovflblkno_to_bitno(HashMetaPage metap, BlockNumber ovflblkno);
/* hashpage.c */
extern Buffer _hash_getbuf(Relation rel, BlockNumber blkno,
--
1.8.3.1
Hi,
On 12/20/2016 05:55 AM, Ashutosh Sharma wrote:
Attached is the revised patch. Please have a look and let me know your
feedback. I have also changed the status for this patch in the
upcoming commitfest to "needs review". Thanks.
I was planning to submit a new version next week for CF/January, so I'll
review your changes with the previous feedback in mind.
Thanks for working on this !
Best regards,
Jesper
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi Jesper,
I was planning to submit a new version next week for CF/January, so I'll
review your changes with the previous feedback in mind.Thanks for working on this !
As i was not seeing any updates from you for last 1 month, I thought
of working on it. I have created a commit-fest entry for it and have
added your name as main Author. I am sorry, I think i should have
taken your opinion before starting to work on it.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi Ashutosh,
On 12/20/2016 05:55 AM, Ashutosh Sharma wrote:
1) It introduces two new functions hash_page_type() and
hash_bitmap_info(). hash_page_type basically displays the type of hash
page whereas hash_bitmap_info() shows the status of a bit for a
particular overflow page in bitmap page of hash index.2) The functions hash_page_stats() and hash_page_items() basically
shows the information about data stored in bucket and overflow pages
of hash index. If a metapage or bitmap page is passed as an input to
this function it throws an error saying "Expected page type:'' got:
''".3) It also improves verify_hash_page() function to handle any type of
page in hash index. It is now being used as
verify_hash_page('raw_page', <page_type>) to verify if the page passed
to this function is of 'page_type' or not. Here page_type can be
LH_META_PAGE, LH_BUCKET_PAGE, LH_OVERFLOW_PAGE, LH_BITMAP_PAGE.
Here is an updated patch with some changes.
* Rename convert_ovflblkno_to_bitno to _hash_ovflblkno_to_bitno
Such that there is only 1 method, which is exposed
* Readded pageinspect--1.6.sql
In order to have the latest pageinspect interface in 1 file, as we need
something to install from.
Removing --1.5.sql otherwise would give
test=# CREATE EXTENSION "pageinspect";
ERROR: extension "pageinspect" has no installation script nor update
path for version "1.6"
* Minor documentation changes
Look it over, and maybe there is a simple test case we can add for this.
Best regards,
Jesper
Attachments:
0001-Add-support-for-hash-index-in-pageinspect-contrib-mo_v2.patchtext/x-patch; name=0001-Add-support-for-hash-index-in-pageinspect-contrib-mo_v2.patchDownload
From 46374ba3aba57d290c1940fee4a12ed31c12342f Mon Sep 17 00:00:00 2001
From: jesperpedersen <jesper.pedersen@redhat.com>
Date: Tue, 3 Jan 2017 07:49:42 -0500
Subject: [PATCH] Add support for hash index in pageinspect contrib module v10
Authors: Ashutosh Sharma and Jesper Pedersen.
---
contrib/pageinspect/Makefile | 10 +-
contrib/pageinspect/hashfuncs.c | 558 ++++++++++++++++++++++++++
contrib/pageinspect/pageinspect--1.5--1.6.sql | 76 ++++
contrib/pageinspect/pageinspect--1.5.sql | 279 -------------
contrib/pageinspect/pageinspect--1.6.sql | 351 ++++++++++++++++
contrib/pageinspect/pageinspect.control | 2 +-
doc/src/sgml/pageinspect.sgml | 149 +++++++
src/backend/access/hash/hashovfl.c | 52 +--
src/include/access/hash.h | 1 +
9 files changed, 1168 insertions(+), 310 deletions(-)
create mode 100644 contrib/pageinspect/hashfuncs.c
create mode 100644 contrib/pageinspect/pageinspect--1.5--1.6.sql
delete mode 100644 contrib/pageinspect/pageinspect--1.5.sql
create mode 100644 contrib/pageinspect/pageinspect--1.6.sql
diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index 87a28e9..ecf0df0 100644
--- a/contrib/pageinspect/Makefile
+++ b/contrib/pageinspect/Makefile
@@ -2,13 +2,13 @@
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.6.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
diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c
new file mode 100644
index 0000000..525e780
--- /dev/null
+++ b/contrib/pageinspect/hashfuncs.c
@@ -0,0 +1,558 @@
+/*
+ * 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_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
+{
+ uint32 live_items;
+ uint32 dead_items;
+ uint32 page_size;
+ uint32 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;
+ HashPageOpaque pageopaque;
+
+ raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
+
+ if (raw_page_size != BLCKSZ)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("input page too small"),
+ 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 unexpected 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)));
+
+ if (!(pageopaque->hasho_flag & flags))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("page is not a hash page of expected type"),
+ errdetail("Expected hash page type: %08x got: %08x.",
+ flags, pageopaque->hasho_flag)));
+ 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 */
+ if (opaque->hasho_flag & LH_BUCKET_PAGE)
+ stat->hasho_prevblkno = InvalidBlockNumber;
+ else
+ 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 * FROM 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, LH_META_PAGE | LH_BUCKET_PAGE |
+ LH_OVERFLOW_PAGE | LH_BITMAP_PAGE);
+ 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 pageinspect 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++] = UInt32GetDatum(stat.live_items);
+ values[j++] = UInt32GetDatum(stat.dead_items);
+ values[j++] = UInt32GetDatum(stat.page_size);
+ values[j++] = UInt32GetDatum(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];
+ HeapTuple tuple;
+ FuncCallContext *fctx;
+ MemoryContext mctx;
+ struct user_args *uargs;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ TupleDesc tupleDesc;
+
+ fctx = SRF_FIRSTCALL_INIT();
+
+ page = verify_hash_page(raw_page, LH_BUCKET_PAGE | LH_OVERFLOW_PAGE);
+
+ /*
+ * We copy the page into local storage to avoid holding pin on the
+ * buffer longer than we must, and possibly failing to release it at
+ * all if the calling query doesn't fetch all rows.
+ */
+ mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
+
+ uargs = palloc(sizeof(struct user_args));
+
+ uargs->page = palloc(BLCKSZ);
+ memcpy(uargs->page, page, BLCKSZ);
+
+ 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;
+ int off;
+ int dlen;
+ char *dump;
+ char *s;
+ char *ptr;
+
+ 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++] = CStringGetTextDatum(psprintf("(%u,%u)",
+ BlockIdGetBlockNumber(&(itup->t_tid.ip_blkid)),
+ itup->t_tid.ip_posid));
+
+ ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
+ dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info);
+ dump = palloc0(dlen * 3 + 1);
+ s = dump;
+ for (off = 0; off < dlen; off++)
+ {
+ if (off > 0)
+ *dump++ = ' ';
+ sprintf(dump, "%02x", *(ptr + off) & 0xff);
+ dump += 2;
+ }
+ values[j] = CStringGetTextDatum(s);
+
+ 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->page);
+ 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);
+ bytea *raw_page;
+ char *raw_page_data;
+ HashMetaPage metap;
+ Buffer buf, metabuf, mapbuf;
+ BlockNumber bitmapblkno;
+ Page page, mappage;
+ HashPageOpaque pageopaque;
+ TupleDesc tupleDesc;
+ Relation indexRel;
+ uint32 ovflbitno, bit = 0;
+ int32 bitmappage,bitmapbit;
+ HeapTuple tuple;
+ int j;
+ Datum values[2];
+ bool nulls[2];
+ uint32 *freep = NULL;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect 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))));
+
+ /* Create a raw page */
+ raw_page = (bytea *) palloc(BLCKSZ + VARHDRSZ);
+ SET_VARSIZE(raw_page, BLCKSZ + VARHDRSZ);
+ raw_page_data = VARDATA(raw_page);
+
+ buf = ReadBufferExtended(indexRel, MAIN_FORKNUM, ovflblkno, RBM_NORMAL, NULL);
+ LockBuffer(buf, BUFFER_LOCK_SHARE);
+
+ memcpy(raw_page_data, BufferGetPage(buf), BLCKSZ);
+
+ LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+ ReleaseBuffer(buf);
+
+ page = verify_hash_page(raw_page, LH_OVERFLOW_PAGE);
+ pageopaque = (HashPageOpaque) PageGetSpecialPointer(page);
+
+ if (pageopaque->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, pageopaque->hasho_flag)));
+
+ /* 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)
+ elog(ERROR, "invalid overflow bit number %u", ovflbitno);
+
+ bitmapblkno = metap->hashm_mapp[bitmappage];
+
+ /* Check the status of bitmap bit for overflow page */
+ mapbuf = _hash_getbuf(indexRel, bitmapblkno, HASH_WRITE, LH_BITMAP_PAGE);
+ mappage = BufferGetPage(mapbuf);
+ freep = HashPageGetBitmap(mappage);
+
+ if (ISSET(freep, bitmapbit))
+ bit = 1;
+
+ _hash_relbuf(indexRel, metabuf);
+
+ _hash_relbuf(indexRel, mapbuf);
+
+ 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++] = UInt32GetDatum(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];
+ char *spares;
+ char *mapp;
+ char *s;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect 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++] = CStringGetTextDatum(psprintf("0x%08X", 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);
+
+ spares = palloc0(HASH_MAX_SPLITPOINTS * 5 + 1);
+ s = spares;
+ for (i = 0; i < HASH_MAX_SPLITPOINTS; i++)
+ {
+ if (i > 0)
+ *spares++ = ' ';
+
+ sprintf(spares, "%04x", metad->hashm_spares[i]);
+ spares += 4;
+ }
+ values[j++] = CStringGetTextDatum(s);
+
+ mapp = palloc0(HASH_MAX_BITMAPS * 5 + 1);
+ s = mapp;
+ for (i = 0; i < HASH_MAX_BITMAPS; i++)
+ {
+ if (i > 0)
+ *mapp++ = ' ';
+
+ sprintf(mapp, "%04x", metad->hashm_mapp[i]);
+ mapp += 4;
+ }
+ values[j++] = CStringGetTextDatum(s);
+
+ 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 0000000..08d844c
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.5--1.6.sql
@@ -0,0 +1,76 @@
+/* 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 int4,
+ OUT dead_items int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT hasho_prevblkno int4,
+ OUT hasho_nextblkno int4,
+ OUT hasho_bucket int4,
+ OUT hasho_flag int4,
+ 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 text)
+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 int4,
+ OUT bitmapblkno int4,
+ OUT bitmapbit int4)
+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 text,
+ OUT version int4,
+ OUT ntuples double precision,
+ OUT ffactor int2,
+ OUT bsize int2,
+ OUT bmsize int2,
+ OUT bmshift int2,
+ OUT maxbucket int4,
+ OUT highmask int4,
+ OUT lowmask int4,
+ OUT ovflpoint int4,
+ OUT firstfree int4,
+ OUT nmaps int4,
+ OUT procid int4,
+ OUT spares text,
+ OUT mapp text)
+AS 'MODULE_PATHNAME', 'hash_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect--1.5.sql b/contrib/pageinspect/pageinspect--1.5.sql
deleted file mode 100644
index 1e40c3c..0000000
--- a/contrib/pageinspect/pageinspect--1.5.sql
+++ /dev/null
@@ -1,279 +0,0 @@
-/* contrib/pageinspect/pageinspect--1.5.sql */
-
--- complain if script is sourced in psql, rather than via CREATE EXTENSION
-\echo Use "CREATE EXTENSION pageinspect" to load this file. \quit
-
---
--- get_raw_page()
---
-CREATE FUNCTION get_raw_page(text, int4)
-RETURNS bytea
-AS 'MODULE_PATHNAME', 'get_raw_page'
-LANGUAGE C STRICT PARALLEL SAFE;
-
-CREATE FUNCTION get_raw_page(text, text, int4)
-RETURNS bytea
-AS 'MODULE_PATHNAME', 'get_raw_page_fork'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- page_header()
---
-CREATE FUNCTION page_header(IN page bytea,
- OUT lsn pg_lsn,
- OUT checksum smallint,
- OUT flags smallint,
- OUT lower smallint,
- OUT upper smallint,
- OUT special smallint,
- OUT pagesize smallint,
- OUT version smallint,
- OUT prune_xid xid)
-AS 'MODULE_PATHNAME', 'page_header'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- heap_page_items()
---
-CREATE FUNCTION heap_page_items(IN page bytea,
- OUT lp smallint,
- OUT lp_off smallint,
- OUT lp_flags smallint,
- OUT lp_len smallint,
- OUT t_xmin xid,
- OUT t_xmax xid,
- OUT t_field3 int4,
- OUT t_ctid tid,
- OUT t_infomask2 integer,
- OUT t_infomask integer,
- OUT t_hoff smallint,
- OUT t_bits text,
- OUT t_oid oid,
- OUT t_data bytea)
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'heap_page_items'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- tuple_data_split()
---
-CREATE FUNCTION tuple_data_split(rel_oid oid,
- t_data bytea,
- t_infomask integer,
- t_infomask2 integer,
- t_bits text)
-RETURNS bytea[]
-AS 'MODULE_PATHNAME','tuple_data_split'
-LANGUAGE C PARALLEL SAFE;
-
-CREATE FUNCTION tuple_data_split(rel_oid oid,
- t_data bytea,
- t_infomask integer,
- t_infomask2 integer,
- t_bits text,
- do_detoast bool)
-RETURNS bytea[]
-AS 'MODULE_PATHNAME','tuple_data_split'
-LANGUAGE C PARALLEL SAFE;
-
---
--- heap_page_item_attrs()
---
-CREATE FUNCTION heap_page_item_attrs(
- IN page bytea,
- IN rel_oid regclass,
- IN do_detoast bool,
- OUT lp smallint,
- OUT lp_off smallint,
- OUT lp_flags smallint,
- OUT lp_len smallint,
- OUT t_xmin xid,
- OUT t_xmax xid,
- OUT t_field3 int4,
- OUT t_ctid tid,
- OUT t_infomask2 integer,
- OUT t_infomask integer,
- OUT t_hoff smallint,
- OUT t_bits text,
- OUT t_oid oid,
- OUT t_attrs bytea[]
- )
-RETURNS SETOF record AS $$
-SELECT lp,
- lp_off,
- lp_flags,
- lp_len,
- t_xmin,
- t_xmax,
- t_field3,
- t_ctid,
- t_infomask2,
- t_infomask,
- t_hoff,
- t_bits,
- t_oid,
- tuple_data_split(
- rel_oid,
- t_data,
- t_infomask,
- t_infomask2,
- t_bits,
- do_detoast)
- AS t_attrs
- FROM heap_page_items(page);
-$$ LANGUAGE SQL PARALLEL SAFE;
-
-CREATE FUNCTION heap_page_item_attrs(IN page bytea, IN rel_oid regclass,
- OUT lp smallint,
- OUT lp_off smallint,
- OUT lp_flags smallint,
- OUT lp_len smallint,
- OUT t_xmin xid,
- OUT t_xmax xid,
- OUT t_field3 int4,
- OUT t_ctid tid,
- OUT t_infomask2 integer,
- OUT t_infomask integer,
- OUT t_hoff smallint,
- OUT t_bits text,
- OUT t_oid oid,
- OUT t_attrs bytea[]
- )
-RETURNS SETOF record AS $$
-SELECT * from heap_page_item_attrs(page, rel_oid, false);
-$$ LANGUAGE SQL PARALLEL SAFE;
-
---
--- bt_metap()
---
-CREATE FUNCTION bt_metap(IN relname text,
- OUT magic int4,
- OUT version int4,
- OUT root int4,
- OUT level int4,
- OUT fastroot int4,
- OUT fastlevel int4)
-AS 'MODULE_PATHNAME', 'bt_metap'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- bt_page_stats()
---
-CREATE FUNCTION bt_page_stats(IN relname text, IN blkno int4,
- OUT blkno int4,
- OUT type "char",
- OUT live_items int4,
- OUT dead_items int4,
- OUT avg_item_size int4,
- OUT page_size int4,
- OUT free_size int4,
- OUT btpo_prev int4,
- OUT btpo_next int4,
- OUT btpo int4,
- OUT btpo_flags int4)
-AS 'MODULE_PATHNAME', 'bt_page_stats'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- bt_page_items()
---
-CREATE FUNCTION bt_page_items(IN relname text, IN blkno int4,
- OUT itemoffset smallint,
- OUT ctid tid,
- OUT itemlen smallint,
- OUT nulls bool,
- OUT vars bool,
- OUT data text)
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'bt_page_items'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_page_type()
---
-CREATE FUNCTION brin_page_type(IN page bytea)
-RETURNS text
-AS 'MODULE_PATHNAME', 'brin_page_type'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_metapage_info()
---
-CREATE FUNCTION brin_metapage_info(IN page bytea, OUT magic text,
- OUT version integer, OUT pagesperrange integer, OUT lastrevmappage bigint)
-AS 'MODULE_PATHNAME', 'brin_metapage_info'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_revmap_data()
---
-CREATE FUNCTION brin_revmap_data(IN page bytea,
- OUT pages tid)
-RETURNS SETOF tid
-AS 'MODULE_PATHNAME', 'brin_revmap_data'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- brin_page_items()
---
-CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass,
- OUT itemoffset int,
- OUT blknum int,
- OUT attnum int,
- OUT allnulls bool,
- OUT hasnulls bool,
- OUT placeholder bool,
- OUT value text)
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'brin_page_items'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- fsm_page_contents()
---
-CREATE FUNCTION fsm_page_contents(IN page bytea)
-RETURNS text
-AS 'MODULE_PATHNAME', 'fsm_page_contents'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- GIN functions
---
-
---
--- gin_metapage_info()
---
-CREATE FUNCTION gin_metapage_info(IN page bytea,
- OUT pending_head bigint,
- OUT pending_tail bigint,
- OUT tail_free_size int4,
- OUT n_pending_pages bigint,
- OUT n_pending_tuples bigint,
- OUT n_total_pages bigint,
- OUT n_entry_pages bigint,
- OUT n_data_pages bigint,
- OUT n_entries bigint,
- OUT version int4)
-AS 'MODULE_PATHNAME', 'gin_metapage_info'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- gin_page_opaque_info()
---
-CREATE FUNCTION gin_page_opaque_info(IN page bytea,
- OUT rightlink bigint,
- OUT maxoff int4,
- OUT flags text[])
-AS 'MODULE_PATHNAME', 'gin_page_opaque_info'
-LANGUAGE C STRICT PARALLEL SAFE;
-
---
--- gin_leafpage_items()
---
-CREATE FUNCTION gin_leafpage_items(IN page bytea,
- OUT first_tid tid,
- OUT nbytes int2,
- OUT tids tid[])
-RETURNS SETOF record
-AS 'MODULE_PATHNAME', 'gin_leafpage_items'
-LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect--1.6.sql b/contrib/pageinspect/pageinspect--1.6.sql
new file mode 100644
index 0000000..8d655d7
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.6.sql
@@ -0,0 +1,351 @@
+/* contrib/pageinspect/pageinspect--1.6.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION pageinspect" to load this file. \quit
+
+--
+-- get_raw_page()
+--
+CREATE FUNCTION get_raw_page(text, int4)
+RETURNS bytea
+AS 'MODULE_PATHNAME', 'get_raw_page'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+CREATE FUNCTION get_raw_page(text, text, int4)
+RETURNS bytea
+AS 'MODULE_PATHNAME', 'get_raw_page_fork'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- page_header()
+--
+CREATE FUNCTION page_header(IN page bytea,
+ OUT lsn pg_lsn,
+ OUT checksum smallint,
+ OUT flags smallint,
+ OUT lower smallint,
+ OUT upper smallint,
+ OUT special smallint,
+ OUT pagesize smallint,
+ OUT version smallint,
+ OUT prune_xid xid)
+AS 'MODULE_PATHNAME', 'page_header'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- heap_page_items()
+--
+CREATE FUNCTION heap_page_items(IN page bytea,
+ OUT lp smallint,
+ OUT lp_off smallint,
+ OUT lp_flags smallint,
+ OUT lp_len smallint,
+ OUT t_xmin xid,
+ OUT t_xmax xid,
+ OUT t_field3 int4,
+ OUT t_ctid tid,
+ OUT t_infomask2 integer,
+ OUT t_infomask integer,
+ OUT t_hoff smallint,
+ OUT t_bits text,
+ OUT t_oid oid,
+ OUT t_data bytea)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'heap_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- tuple_data_split()
+--
+CREATE FUNCTION tuple_data_split(rel_oid oid,
+ t_data bytea,
+ t_infomask integer,
+ t_infomask2 integer,
+ t_bits text)
+RETURNS bytea[]
+AS 'MODULE_PATHNAME','tuple_data_split'
+LANGUAGE C PARALLEL SAFE;
+
+CREATE FUNCTION tuple_data_split(rel_oid oid,
+ t_data bytea,
+ t_infomask integer,
+ t_infomask2 integer,
+ t_bits text,
+ do_detoast bool)
+RETURNS bytea[]
+AS 'MODULE_PATHNAME','tuple_data_split'
+LANGUAGE C PARALLEL SAFE;
+
+--
+-- heap_page_item_attrs()
+--
+CREATE FUNCTION heap_page_item_attrs(
+ IN page bytea,
+ IN rel_oid regclass,
+ IN do_detoast bool,
+ OUT lp smallint,
+ OUT lp_off smallint,
+ OUT lp_flags smallint,
+ OUT lp_len smallint,
+ OUT t_xmin xid,
+ OUT t_xmax xid,
+ OUT t_field3 int4,
+ OUT t_ctid tid,
+ OUT t_infomask2 integer,
+ OUT t_infomask integer,
+ OUT t_hoff smallint,
+ OUT t_bits text,
+ OUT t_oid oid,
+ OUT t_attrs bytea[]
+ )
+RETURNS SETOF record AS $$
+SELECT lp,
+ lp_off,
+ lp_flags,
+ lp_len,
+ t_xmin,
+ t_xmax,
+ t_field3,
+ t_ctid,
+ t_infomask2,
+ t_infomask,
+ t_hoff,
+ t_bits,
+ t_oid,
+ tuple_data_split(
+ rel_oid,
+ t_data,
+ t_infomask,
+ t_infomask2,
+ t_bits,
+ do_detoast)
+ AS t_attrs
+ FROM heap_page_items(page);
+$$ LANGUAGE SQL PARALLEL SAFE;
+
+CREATE FUNCTION heap_page_item_attrs(IN page bytea, IN rel_oid regclass,
+ OUT lp smallint,
+ OUT lp_off smallint,
+ OUT lp_flags smallint,
+ OUT lp_len smallint,
+ OUT t_xmin xid,
+ OUT t_xmax xid,
+ OUT t_field3 int4,
+ OUT t_ctid tid,
+ OUT t_infomask2 integer,
+ OUT t_infomask integer,
+ OUT t_hoff smallint,
+ OUT t_bits text,
+ OUT t_oid oid,
+ OUT t_attrs bytea[]
+ )
+RETURNS SETOF record AS $$
+SELECT * from heap_page_item_attrs(page, rel_oid, false);
+$$ LANGUAGE SQL PARALLEL SAFE;
+
+--
+-- bt_metap()
+--
+CREATE FUNCTION bt_metap(IN relname text,
+ OUT magic int4,
+ OUT version int4,
+ OUT root int4,
+ OUT level int4,
+ OUT fastroot int4,
+ OUT fastlevel int4)
+AS 'MODULE_PATHNAME', 'bt_metap'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- bt_page_stats()
+--
+CREATE FUNCTION bt_page_stats(IN relname text, IN blkno int4,
+ OUT blkno int4,
+ OUT type "char",
+ OUT live_items int4,
+ OUT dead_items int4,
+ OUT avg_item_size int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT btpo_prev int4,
+ OUT btpo_next int4,
+ OUT btpo int4,
+ OUT btpo_flags int4)
+AS 'MODULE_PATHNAME', 'bt_page_stats'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- bt_page_items()
+--
+CREATE FUNCTION bt_page_items(IN relname text, IN blkno int4,
+ OUT itemoffset smallint,
+ OUT ctid tid,
+ OUT itemlen smallint,
+ OUT nulls bool,
+ OUT vars bool,
+ OUT data text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'bt_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_page_type()
+--
+CREATE FUNCTION brin_page_type(IN page bytea)
+RETURNS text
+AS 'MODULE_PATHNAME', 'brin_page_type'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_metapage_info()
+--
+CREATE FUNCTION brin_metapage_info(IN page bytea, OUT magic text,
+ OUT version integer, OUT pagesperrange integer, OUT lastrevmappage bigint)
+AS 'MODULE_PATHNAME', 'brin_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_revmap_data()
+--
+CREATE FUNCTION brin_revmap_data(IN page bytea,
+ OUT pages tid)
+RETURNS SETOF tid
+AS 'MODULE_PATHNAME', 'brin_revmap_data'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- brin_page_items()
+--
+CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass,
+ OUT itemoffset int,
+ OUT blknum int,
+ OUT attnum int,
+ OUT allnulls bool,
+ OUT hasnulls bool,
+ OUT placeholder bool,
+ OUT value text)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'brin_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- fsm_page_contents()
+--
+CREATE FUNCTION fsm_page_contents(IN page bytea)
+RETURNS text
+AS 'MODULE_PATHNAME', 'fsm_page_contents'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- GIN functions
+--
+
+--
+-- gin_metapage_info()
+--
+CREATE FUNCTION gin_metapage_info(IN page bytea,
+ OUT pending_head bigint,
+ OUT pending_tail bigint,
+ OUT tail_free_size int4,
+ OUT n_pending_pages bigint,
+ OUT n_pending_tuples bigint,
+ OUT n_total_pages bigint,
+ OUT n_entry_pages bigint,
+ OUT n_data_pages bigint,
+ OUT n_entries bigint,
+ OUT version int4)
+AS 'MODULE_PATHNAME', 'gin_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- gin_page_opaque_info()
+--
+CREATE FUNCTION gin_page_opaque_info(IN page bytea,
+ OUT rightlink bigint,
+ OUT maxoff int4,
+ OUT flags text[])
+AS 'MODULE_PATHNAME', 'gin_page_opaque_info'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- gin_leafpage_items()
+--
+CREATE FUNCTION gin_leafpage_items(IN page bytea,
+ OUT first_tid tid,
+ OUT nbytes int2,
+ OUT tids tid[])
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'gin_leafpage_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+--
+-- 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 int4,
+ OUT dead_items int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT hasho_prevblkno int4,
+ OUT hasho_nextblkno int4,
+ OUT hasho_bucket int4,
+ OUT hasho_flag int4,
+ 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 text)
+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 int4,
+ OUT bitmapblkno int4,
+ OUT bitmapbit int4)
+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 text,
+ OUT version int4,
+ OUT ntuples double precision,
+ OUT ffactor int2,
+ OUT bsize int2,
+ OUT bmsize int2,
+ OUT bmshift int2,
+ OUT maxbucket int4,
+ OUT highmask int4,
+ OUT lowmask int4,
+ OUT ovflpoint int4,
+ OUT firstfree int4,
+ OUT nmaps int4,
+ OUT procid int4,
+ OUT spares text,
+ OUT mapp text)
+AS 'MODULE_PATHNAME', 'hash_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control
index 23c8eff..1a61c9f 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/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml
index d12dbac..aff299a 100644
--- a/doc/src/sgml/pageinspect.sgml
+++ b/doc/src/sgml/pageinspect.sgml
@@ -493,4 +493,153 @@ test=# SELECT first_tid, nbytes, tids[0:5] AS some_tids
</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 * FROM 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 | -1
+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));
+ itemoffset | ctid | data
+------------+-----------------+-------------------------
+ 1 | (3145728,14376) | 00 c0 ca 3e 00 00 00 00
+ 2 | (3145728,14376) | 00 c0 ca 3e 00 00 00 00
+ 3 | (3407872,14376) | 00 c0 ca 3e 00 00 00 00
+ 4 | (3407872,14376) | 00 c0 ca 3e 00 00 00 00
+ 5 | (3407872,14376) | 00 c0 ca 3e 00 00 00 00
+...
+ 404 | (2883584,14120) | 00 c0 ca 3e 00 00 00 00
+ 405 | (2883584,13608) | 00 c0 ca 3e 00 00 00 00
+ 406 | (2621440,13096) | 00 c0 ca 3e 00 00 00 00
+ 407 | (2621440,12584) | 00 c0 ca 3e 00 00 00 00
+</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> returns information about
+ the status of a bit for an overflow page in bitmap page of a <acronym>HASH</acronym>
+ index. For example:
+<screen>
+test=# SELECT * FROM hash_bitmap_info('con_hash_index', 2050);
+ bitmapblkno | bitmapbit
+-------------+-----------
+ 65 | 1
+</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 | 0x06440640
+version | 2
+ntuples | 686000
+ffactor | 40
+bsize | 8152
+bmsize | 4096
+bmshift | 15
+maxbucket | 17149
+highmask | 32767
+lowmask | 16383
+ovflpoint | 15
+firstfree | 2012
+nmaps | 1
+procid | 450
+spares | 0000 0000 0000 0000 0000 0000 0001 0001 0001 0001 0001 0004 003b 02c0 07c3 07dc 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+mapp | 0041 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </sect2>
+
</sect1>
diff --git a/src/backend/access/hash/hashovfl.c b/src/backend/access/hash/hashovfl.c
index c7c4922..ee5d91a 100644
--- a/src/backend/access/hash/hashovfl.c
+++ b/src/backend/access/hash/hashovfl.c
@@ -53,30 +53,6 @@ bitno_to_blkno(HashMetaPage metap, uint32 ovflbitnum)
}
/*
- * Convert overflow page block number to bit number for free-page bitmap.
- */
-static uint32
-blkno_to_bitno(HashMetaPage metap, BlockNumber ovflblkno)
-{
- uint32 splitnum = metap->hashm_ovflpoint;
- uint32 i;
- uint32 bitnum;
-
- /* Determine the split number containing this page */
- for (i = 1; i <= splitnum; i++)
- {
- if (ovflblkno <= (BlockNumber) (1 << i))
- break; /* oops */
- bitnum = ovflblkno - (1 << i);
- if (bitnum <= metap->hashm_spares[i])
- return bitnum - 1; /* -1 to convert 1-based to 0-based */
- }
-
- elog(ERROR, "invalid overflow block number %u", ovflblkno);
- return 0; /* keep compiler quiet */
-}
-
-/*
* _hash_addovflpage
*
* Add an overflow page to the bucket whose last page is pointed to by 'buf'.
@@ -552,7 +528,7 @@ _hash_freeovflpage(Relation rel, Buffer bucketbuf, Buffer ovflbuf,
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);
@@ -1087,3 +1063,29 @@ readpage:
/* NOTREACHED */
}
+
+/*
+ * _hash_ovflblkno_to_bitno
+ *
+ * Convert overflow page block number to bit number for free-page bitmap.
+ */
+uint32
+_hash_ovflblkno_to_bitno(HashMetaPage metap, BlockNumber ovflblkno)
+{
+ uint32 splitnum = metap->hashm_ovflpoint;
+ uint32 i;
+ uint32 bitnum;
+
+ /* Determine the split number containing this page */
+ for (i = 1; i <= splitnum; i++)
+ {
+ if (ovflblkno <= (BlockNumber) (1 << i))
+ break; /* oops */
+ bitnum = ovflblkno - (1 << i);
+ if (bitnum <= metap->hashm_spares[i])
+ return bitnum - 1; /* -1 to convert 1-based to 0-based */
+ }
+
+ elog(ERROR, "invalid overflow block number %u", ovflblkno);
+ return 0; /* keep compiler quiet */
+}
diff --git a/src/include/access/hash.h b/src/include/access/hash.h
index bd56ee2..c56ba34 100644
--- a/src/include/access/hash.h
+++ b/src/include/access/hash.h
@@ -323,6 +323,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,
--
2.7.4
Hi Jesper,
* Rename convert_ovflblkno_to_bitno to _hash_ovflblkno_to_bitno
Such that there is only 1 method, which is exposed
Okay, Thanks. It makes sense.
* Readded pageinspect--1.6.sql
In order to have the latest pageinspect interface in 1 file, as we need
something to install from.
I think there should be no problem even if we simply add
pageinspect--1.5--1.6.sql file instead of removing 1.5.sql as with the
latest changes to the create extension infrastructure in postgresql it
automatically finds a lower version script if unable to find the
default version script and then upgrade it to the latest version.
Removing --1.5.sql otherwise would give
test=# CREATE EXTENSION "pageinspect";
ERROR: extension "pageinspect" has no installation script nor update path
for version "1.6"
I didn't get this. Do you mean to say that if you add 1.6.sql and do
not remove 1.5.sql you get this error when trying to add pageinspect
module. I didn't
get any such error. See below,
postgres=# create extension pageinspect WITH VERSION '1.6';
CREATE EXTENSION
* Minor documentation changes
Look it over, and maybe there is a simple test case we can add for this.
Peter has already written a test-case-[1]/messages/by-id/bcf8c21b-702e-20a7-a4b0-236ed2363d84@2ndquadrant.com based on your earlier patch
for supporting hash index with pageinspect module. Once the latest
patch (v10) becomes stable i will share a separete patch having a
test-case for hash index. Infact I will try to modify an already
existing patch by Peter.
[1]: /messages/by-id/bcf8c21b-702e-20a7-a4b0-236ed2363d84@2ndquadrant.com
--
With Regards,
Ashutosh Sharma
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi Ashutosh,
On 01/05/2017 07:13 AM, Ashutosh Sharma wrote:
* Readded pageinspect--1.6.sql
In order to have the latest pageinspect interface in 1 file, as we need
something to install from.I think there should be no problem even if we simply add
pageinspect--1.5--1.6.sql file instead of removing 1.5.sql as with the
latest changes to the create extension infrastructure in postgresql it
automatically finds a lower version script if unable to find the
default version script and then upgrade it to the latest version.Removing --1.5.sql otherwise would give
test=# CREATE EXTENSION "pageinspect";
ERROR: extension "pageinspect" has no installation script nor update path
for version "1.6"I didn't get this. Do you mean to say that if you add 1.6.sql and do
not remove 1.5.sql you get this error when trying to add pageinspect
module. I didn't
get any such error. See below,postgres=# create extension pageinspect WITH VERSION '1.6';
CREATE EXTENSION
The previous patch was using pageinspect--1.5.sql as a base, and then
uses pageinspect--1.5-1.6.sql to upgrade to version 1.6.
Removing pageinspect--1.5.sql, and adding pageinspect--1.6.sql with the
current interface will use pageinspect--1.6.sql directly where as
existing installations will use the upgrade scripts.
Hence I don't see a reason why we should keep pageinspect--1.5.sql
around when we can provide a clear interface description in a
pageinspect--1.6.sql.
Peter has already written a test-case-[1] based on your earlier patch
for supporting hash index with pageinspect module. Once the latest
patch (v10) becomes stable i will share a separete patch having a
test-case for hash index. Infact I will try to modify an already
existing patch by Peter.
Ok, sounds good.
Best regards,
Jesper
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
The previous patch was using pageinspect--1.5.sql as a base, and then uses
pageinspect--1.5-1.6.sql to upgrade to version 1.6.Removing pageinspect--1.5.sql, and adding pageinspect--1.6.sql with the
current interface will use pageinspect--1.6.sql directly where as existing
installations will use the upgrade scripts.Hence I don't see a reason why we should keep pageinspect--1.5.sql around
when we can provide a clear interface description in a pageinspect--1.6.sql.
okay, agreed.
With Regards,
Ashutosh Sharma
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Jan 6, 2017 at 1:14 AM, Ashutosh Sharma <ashu.coek88@gmail.com> wrote:
The previous patch was using pageinspect--1.5.sql as a base, and then uses
pageinspect--1.5-1.6.sql to upgrade to version 1.6.Removing pageinspect--1.5.sql, and adding pageinspect--1.6.sql with the
current interface will use pageinspect--1.6.sql directly where as existing
installations will use the upgrade scripts.Hence I don't see a reason why we should keep pageinspect--1.5.sql around
when we can provide a clear interface description in a pageinspect--1.6.sql.okay, agreed.
No, you're missing the point. The patch doesn't need to add
pageinspect--1.6.sql at all, nor does it remove pageinspect--1.5.sql.
It only needs to add pageinspect--1.5--1.6.sql and change the default
version to 1.6. No other changes are required.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
No, you're missing the point. The patch doesn't need to add
pageinspect--1.6.sql at all, nor does it remove pageinspect--1.5.sql.
It only needs to add pageinspect--1.5--1.6.sql and change the default
version to 1.6. No other changes are required.
Right, this is a new recommended process since commit 40b449ae8,
which see for rationale.
Use, eg, commit 11da83a0e as a model for extension update patches.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 01/10/2017 10:39 AM, Tom Lane wrote:
Robert Haas <robertmhaas@gmail.com> writes:
No, you're missing the point. The patch doesn't need to add
pageinspect--1.6.sql at all, nor does it remove pageinspect--1.5.sql.
It only needs to add pageinspect--1.5--1.6.sql and change the default
version to 1.6. No other changes are required.Right, this is a new recommended process since commit 40b449ae8,
which see for rationale.Use, eg, commit 11da83a0e as a model for extension update patches.
Thanks for the commit ids !
Revised patched attached.
Best regards,
Jesper
Attachments:
0001-Add-support-for-hash-index-in-pageinspect-contrib-mo_v3.patchtext/x-patch; name=0001-Add-support-for-hash-index-in-pageinspect-contrib-mo_v3.patchDownload
From 2c75e4af17903c22961f23ed5455614340d0dd4e Mon Sep 17 00:00:00 2001
From: jesperpedersen <jesper.pedersen@redhat.com>
Date: Tue, 3 Jan 2017 07:49:42 -0500
Subject: [PATCH] Add support for hash index in pageinspect contrib module v11
Authors: Ashutosh Sharma and Jesper Pedersen.
---
contrib/pageinspect/Makefile | 10 +-
contrib/pageinspect/hashfuncs.c | 558 ++++++++++++++++++++++++++
contrib/pageinspect/pageinspect--1.5--1.6.sql | 76 ++++
contrib/pageinspect/pageinspect.control | 2 +-
doc/src/sgml/pageinspect.sgml | 149 +++++++
src/backend/access/hash/hashovfl.c | 52 +--
src/include/access/hash.h | 1 +
7 files changed, 817 insertions(+), 31 deletions(-)
create mode 100644 contrib/pageinspect/hashfuncs.c
create mode 100644 contrib/pageinspect/pageinspect--1.5--1.6.sql
diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index 87a28e9..d7f3645 100644
--- a/contrib/pageinspect/Makefile
+++ b/contrib/pageinspect/Makefile
@@ -2,13 +2,13 @@
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
diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c
new file mode 100644
index 0000000..525e780
--- /dev/null
+++ b/contrib/pageinspect/hashfuncs.c
@@ -0,0 +1,558 @@
+/*
+ * 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_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
+{
+ uint32 live_items;
+ uint32 dead_items;
+ uint32 page_size;
+ uint32 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;
+ HashPageOpaque pageopaque;
+
+ raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
+
+ if (raw_page_size != BLCKSZ)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("input page too small"),
+ 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 unexpected 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)));
+
+ if (!(pageopaque->hasho_flag & flags))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("page is not a hash page of expected type"),
+ errdetail("Expected hash page type: %08x got: %08x.",
+ flags, pageopaque->hasho_flag)));
+ 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 */
+ if (opaque->hasho_flag & LH_BUCKET_PAGE)
+ stat->hasho_prevblkno = InvalidBlockNumber;
+ else
+ 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 * FROM 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, LH_META_PAGE | LH_BUCKET_PAGE |
+ LH_OVERFLOW_PAGE | LH_BITMAP_PAGE);
+ 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 pageinspect 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++] = UInt32GetDatum(stat.live_items);
+ values[j++] = UInt32GetDatum(stat.dead_items);
+ values[j++] = UInt32GetDatum(stat.page_size);
+ values[j++] = UInt32GetDatum(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];
+ HeapTuple tuple;
+ FuncCallContext *fctx;
+ MemoryContext mctx;
+ struct user_args *uargs;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ TupleDesc tupleDesc;
+
+ fctx = SRF_FIRSTCALL_INIT();
+
+ page = verify_hash_page(raw_page, LH_BUCKET_PAGE | LH_OVERFLOW_PAGE);
+
+ /*
+ * We copy the page into local storage to avoid holding pin on the
+ * buffer longer than we must, and possibly failing to release it at
+ * all if the calling query doesn't fetch all rows.
+ */
+ mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
+
+ uargs = palloc(sizeof(struct user_args));
+
+ uargs->page = palloc(BLCKSZ);
+ memcpy(uargs->page, page, BLCKSZ);
+
+ 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;
+ int off;
+ int dlen;
+ char *dump;
+ char *s;
+ char *ptr;
+
+ 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++] = CStringGetTextDatum(psprintf("(%u,%u)",
+ BlockIdGetBlockNumber(&(itup->t_tid.ip_blkid)),
+ itup->t_tid.ip_posid));
+
+ ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
+ dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info);
+ dump = palloc0(dlen * 3 + 1);
+ s = dump;
+ for (off = 0; off < dlen; off++)
+ {
+ if (off > 0)
+ *dump++ = ' ';
+ sprintf(dump, "%02x", *(ptr + off) & 0xff);
+ dump += 2;
+ }
+ values[j] = CStringGetTextDatum(s);
+
+ 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->page);
+ 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);
+ bytea *raw_page;
+ char *raw_page_data;
+ HashMetaPage metap;
+ Buffer buf, metabuf, mapbuf;
+ BlockNumber bitmapblkno;
+ Page page, mappage;
+ HashPageOpaque pageopaque;
+ TupleDesc tupleDesc;
+ Relation indexRel;
+ uint32 ovflbitno, bit = 0;
+ int32 bitmappage,bitmapbit;
+ HeapTuple tuple;
+ int j;
+ Datum values[2];
+ bool nulls[2];
+ uint32 *freep = NULL;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect 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))));
+
+ /* Create a raw page */
+ raw_page = (bytea *) palloc(BLCKSZ + VARHDRSZ);
+ SET_VARSIZE(raw_page, BLCKSZ + VARHDRSZ);
+ raw_page_data = VARDATA(raw_page);
+
+ buf = ReadBufferExtended(indexRel, MAIN_FORKNUM, ovflblkno, RBM_NORMAL, NULL);
+ LockBuffer(buf, BUFFER_LOCK_SHARE);
+
+ memcpy(raw_page_data, BufferGetPage(buf), BLCKSZ);
+
+ LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+ ReleaseBuffer(buf);
+
+ page = verify_hash_page(raw_page, LH_OVERFLOW_PAGE);
+ pageopaque = (HashPageOpaque) PageGetSpecialPointer(page);
+
+ if (pageopaque->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, pageopaque->hasho_flag)));
+
+ /* 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)
+ elog(ERROR, "invalid overflow bit number %u", ovflbitno);
+
+ bitmapblkno = metap->hashm_mapp[bitmappage];
+
+ /* Check the status of bitmap bit for overflow page */
+ mapbuf = _hash_getbuf(indexRel, bitmapblkno, HASH_WRITE, LH_BITMAP_PAGE);
+ mappage = BufferGetPage(mapbuf);
+ freep = HashPageGetBitmap(mappage);
+
+ if (ISSET(freep, bitmapbit))
+ bit = 1;
+
+ _hash_relbuf(indexRel, metabuf);
+
+ _hash_relbuf(indexRel, mapbuf);
+
+ 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++] = UInt32GetDatum(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];
+ char *spares;
+ char *mapp;
+ char *s;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect 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++] = CStringGetTextDatum(psprintf("0x%08X", 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);
+
+ spares = palloc0(HASH_MAX_SPLITPOINTS * 5 + 1);
+ s = spares;
+ for (i = 0; i < HASH_MAX_SPLITPOINTS; i++)
+ {
+ if (i > 0)
+ *spares++ = ' ';
+
+ sprintf(spares, "%04x", metad->hashm_spares[i]);
+ spares += 4;
+ }
+ values[j++] = CStringGetTextDatum(s);
+
+ mapp = palloc0(HASH_MAX_BITMAPS * 5 + 1);
+ s = mapp;
+ for (i = 0; i < HASH_MAX_BITMAPS; i++)
+ {
+ if (i > 0)
+ *mapp++ = ' ';
+
+ sprintf(mapp, "%04x", metad->hashm_mapp[i]);
+ mapp += 4;
+ }
+ values[j++] = CStringGetTextDatum(s);
+
+ 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 0000000..08d844c
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.5--1.6.sql
@@ -0,0 +1,76 @@
+/* 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 int4,
+ OUT dead_items int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT hasho_prevblkno int4,
+ OUT hasho_nextblkno int4,
+ OUT hasho_bucket int4,
+ OUT hasho_flag int4,
+ 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 text)
+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 int4,
+ OUT bitmapblkno int4,
+ OUT bitmapbit int4)
+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 text,
+ OUT version int4,
+ OUT ntuples double precision,
+ OUT ffactor int2,
+ OUT bsize int2,
+ OUT bmsize int2,
+ OUT bmshift int2,
+ OUT maxbucket int4,
+ OUT highmask int4,
+ OUT lowmask int4,
+ OUT ovflpoint int4,
+ OUT firstfree int4,
+ OUT nmaps int4,
+ OUT procid int4,
+ OUT spares text,
+ OUT mapp text)
+AS 'MODULE_PATHNAME', 'hash_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control
index 23c8eff..1a61c9f 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/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml
index d12dbac..aff299a 100644
--- a/doc/src/sgml/pageinspect.sgml
+++ b/doc/src/sgml/pageinspect.sgml
@@ -493,4 +493,153 @@ test=# SELECT first_tid, nbytes, tids[0:5] AS some_tids
</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 * FROM 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 | -1
+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));
+ itemoffset | ctid | data
+------------+-----------------+-------------------------
+ 1 | (3145728,14376) | 00 c0 ca 3e 00 00 00 00
+ 2 | (3145728,14376) | 00 c0 ca 3e 00 00 00 00
+ 3 | (3407872,14376) | 00 c0 ca 3e 00 00 00 00
+ 4 | (3407872,14376) | 00 c0 ca 3e 00 00 00 00
+ 5 | (3407872,14376) | 00 c0 ca 3e 00 00 00 00
+...
+ 404 | (2883584,14120) | 00 c0 ca 3e 00 00 00 00
+ 405 | (2883584,13608) | 00 c0 ca 3e 00 00 00 00
+ 406 | (2621440,13096) | 00 c0 ca 3e 00 00 00 00
+ 407 | (2621440,12584) | 00 c0 ca 3e 00 00 00 00
+</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> returns information about
+ the status of a bit for an overflow page in bitmap page of a <acronym>HASH</acronym>
+ index. For example:
+<screen>
+test=# SELECT * FROM hash_bitmap_info('con_hash_index', 2050);
+ bitmapblkno | bitmapbit
+-------------+-----------
+ 65 | 1
+</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 | 0x06440640
+version | 2
+ntuples | 686000
+ffactor | 40
+bsize | 8152
+bmsize | 4096
+bmshift | 15
+maxbucket | 17149
+highmask | 32767
+lowmask | 16383
+ovflpoint | 15
+firstfree | 2012
+nmaps | 1
+procid | 450
+spares | 0000 0000 0000 0000 0000 0000 0001 0001 0001 0001 0001 0004 003b 02c0 07c3 07dc 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+mapp | 0041 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </sect2>
+
</sect1>
diff --git a/src/backend/access/hash/hashovfl.c b/src/backend/access/hash/hashovfl.c
index 504670b..658249e 100644
--- a/src/backend/access/hash/hashovfl.c
+++ b/src/backend/access/hash/hashovfl.c
@@ -53,30 +53,6 @@ bitno_to_blkno(HashMetaPage metap, uint32 ovflbitnum)
}
/*
- * Convert overflow page block number to bit number for free-page bitmap.
- */
-static uint32
-blkno_to_bitno(HashMetaPage metap, BlockNumber ovflblkno)
-{
- uint32 splitnum = metap->hashm_ovflpoint;
- uint32 i;
- uint32 bitnum;
-
- /* Determine the split number containing this page */
- for (i = 1; i <= splitnum; i++)
- {
- if (ovflblkno <= (BlockNumber) (1 << i))
- break; /* oops */
- bitnum = ovflblkno - (1 << i);
- if (bitnum <= metap->hashm_spares[i])
- return bitnum - 1; /* -1 to convert 1-based to 0-based */
- }
-
- elog(ERROR, "invalid overflow block number %u", ovflblkno);
- return 0; /* keep compiler quiet */
-}
-
-/*
* _hash_addovflpage
*
* Add an overflow page to the bucket whose last page is pointed to by 'buf'.
@@ -558,7 +534,7 @@ _hash_freeovflpage(Relation rel, Buffer bucketbuf, Buffer ovflbuf,
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);
@@ -1093,3 +1069,29 @@ readpage:
/* NOTREACHED */
}
+
+/*
+ * _hash_ovflblkno_to_bitno
+ *
+ * Convert overflow page block number to bit number for free-page bitmap.
+ */
+uint32
+_hash_ovflblkno_to_bitno(HashMetaPage metap, BlockNumber ovflblkno)
+{
+ uint32 splitnum = metap->hashm_ovflpoint;
+ uint32 i;
+ uint32 bitnum;
+
+ /* Determine the split number containing this page */
+ for (i = 1; i <= splitnum; i++)
+ {
+ if (ovflblkno <= (BlockNumber) (1 << i))
+ break; /* oops */
+ bitnum = ovflblkno - (1 << i);
+ if (bitnum <= metap->hashm_spares[i])
+ return bitnum - 1; /* -1 to convert 1-based to 0-based */
+ }
+
+ elog(ERROR, "invalid overflow block number %u", ovflblkno);
+ return 0; /* keep compiler quiet */
+}
diff --git a/src/include/access/hash.h b/src/include/access/hash.h
index 8328fc5..687b3d5 100644
--- a/src/include/access/hash.h
+++ b/src/include/access/hash.h
@@ -323,6 +323,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,
--
2.7.4
On Tue, Jan 10, 2017 at 12:19 PM, Jesper Pedersen
<jesper.pedersen@redhat.com> wrote:
Revised patched attached.
+ itup = (IndexTuple) PageGetItem(uargs->page, id);
+
+ MemSet(nulls, 0, sizeof(nulls));
+
+ j = 0;
+ values[j++] = UInt16GetDatum(uargs->offset);
+ values[j++] = CStringGetTextDatum(psprintf("(%u,%u)",
+
BlockIdGetBlockNumber(&(itup->t_tid.ip_blkid)),
+ itup->t_tid.ip_posid));
+
+ ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
+ dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info);
It seems like this could be used to index off the end of the page, if
you feed it invalid data.
+ dump = palloc0(dlen * 3 + 1);
This is wasteful. Just use palloc and install a terminating NUL byte instead.
+ sprintf(dump, "%02x", *(ptr + off) & 0xff);
*(ptr + off) is normally written ptr[off].
+ if (pageopaque->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, pageopaque->hasho_flag)));
I think this is an unnecessary test given that you've already called
verify_hash_page().
+ if (bitmappage >= metap->hashm_nmaps)
+ elog(ERROR, "invalid overflow bit number %u", ovflbitno);
I think this should be an ereport(), because it's reachable given a
bogus page which a user might construct (or a corrupted page).
+test=# SELECT * FROM hash_page_items(get_raw_page('con_hash_index', 1));
+ itemoffset | ctid | data
+------------+-----------------+-------------------------
+ 1 | (3145728,14376) | 00 c0 ca 3e 00 00 00 00
+ 2 | (3145728,14376) | 00 c0 ca 3e 00 00 00 00
+ 3 | (3407872,14376) | 00 c0 ca 3e 00 00 00 00
Won't the first 4 bytes always be a hash code and the second 4 bytes
always 0? Should we just return the hash code as an int4 or int8
instead of pretending it's a bunch of uninterpretable binary data?
+ <function>hash_bitmap_info</function> returns information about
+ the status of a bit for an overflow page in bitmap page of a
<acronym>HASH</acronym>
+ index. For example:
+<screen>
+test=# SELECT * FROM hash_bitmap_info('con_hash_index', 2050);
+ bitmapblkno | bitmapbit
+-------------+-----------
+ 65 | 1
+</screen>
I find this hard to understand. This says that it returns
information, but the nature of the returned information is unspecified
and in my opinion unclear.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
+ itup = (IndexTuple) PageGetItem(uargs->page, id); + + MemSet(nulls, 0, sizeof(nulls)); + + j = 0; + values[j++] = UInt16GetDatum(uargs->offset); + values[j++] = CStringGetTextDatum(psprintf("(%u,%u)", + BlockIdGetBlockNumber(&(itup->t_tid.ip_blkid)), + itup->t_tid.ip_posid)); + + ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info); + dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info);It seems like this could be used to index off the end of the page, if
you feed it invalid data.
I think it should not exceed the page size. This is how it has been
implemented for btree as well. However, just to be on a safer side i
am planning to add following 'if check' to ensure that we do not go
beyond the page size while reading tuples.
ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
+ if (ptr > page + BLCKSZ)
+ /* Error */
dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info);
Meanwhile, I am working on other review comments and will try to share
an updated patch asap.
With Regards,
Ashutosh Sharma
EnterpriseDB:http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Jesper Pedersen wrote:
+ /* + * We copy the page into local storage to avoid holding pin on the + * buffer longer than we must, and possibly failing to release it at + * all if the calling query doesn't fetch all rows. + */ + mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx); + + uargs = palloc(sizeof(struct user_args)); + + uargs->page = palloc(BLCKSZ);
Is this necessary? I think this was copied from btreefuncs, but there
is no buffer release in this code.
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi,
+ values[j++] = UInt16GetDatum(uargs->offset); + values[j++] = CStringGetTextDatum(psprintf("(%u,%u)", + BlockIdGetBlockNumber(&(itup->t_tid.ip_blkid)), + itup->t_tid.ip_posid)); + + ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info); + dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info);It seems like this could be used to index off the end of the page, if
you feed it invalid data.
okay, I have handled it in the attached patch.
+ dump = palloc0(dlen * 3 + 1);
This is wasteful. Just use palloc and install a terminating NUL byte instead.
fixed. Please check the attached patch.
+ sprintf(dump, "%02x", *(ptr + off) & 0xff);
*(ptr + off) is normally written ptr[off].
corrected.
+ if (pageopaque->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, pageopaque->hasho_flag)));I think this is an unnecessary test given that you've already called
verify_hash_page().
Yes, it is not required. I have removed it in the attached patch.
+ if (bitmappage >= metap->hashm_nmaps) + elog(ERROR, "invalid overflow bit number %u", ovflbitno);I think this should be an ereport(), because it's reachable given a
bogus page which a user might construct (or a corrupted page).
okay, I have corrected it.
+test=# SELECT * FROM hash_page_items(get_raw_page('con_hash_index', 1)); + itemoffset | ctid | data +------------+-----------------+------------------------- + 1 | (3145728,14376) | 00 c0 ca 3e 00 00 00 00 + 2 | (3145728,14376) | 00 c0 ca 3e 00 00 00 00 + 3 | (3407872,14376) | 00 c0 ca 3e 00 00 00 00Won't the first 4 bytes always be a hash code and the second 4 bytes
always 0? Should we just return the hash code as an int4 or int8
instead of pretending it's a bunch of uninterpretable binary data?
Yes, the first 4 bytes represents a hash code and the second 4 bytes
is always zero. Now, returning the hash code as int4.
+test=# SELECT * FROM hash_bitmap_info('con_hash_index', 2050); + bitmapblkno | bitmapbit +-------------+----------- + 65 | 1 +</screen>I find this hard to understand. This says that it returns
information, but the nature of the returned information is unspecified
and in my opinion unclear.
I have rephrased it to make it more clear.
Attachments:
0001-Add-support-for-hash-index-in-pageinspect-contrib-mo.patchinvalid/octet-stream; name=0001-Add-support-for-hash-index-in-pageinspect-contrib-mo.patchDownload
From ef354ccef1a2ecc3b43b7cb7fc39502717e97bdd Mon Sep 17 00:00:00 2001
From: ashu <ashu@localhost.localdomain>
Date: Thu, 12 Jan 2017 00:43:08 +0530
Subject: [PATCH] Add support for hash index in pageinspect contrib module v12
Authors: Ashutosh Sharma and Jesper Pedersen.
---
contrib/pageinspect/Makefile | 10 +-
contrib/pageinspect/hashfuncs.c | 548 ++++++++++++++++++++++++++
contrib/pageinspect/pageinspect--1.5--1.6.sql | 76 ++++
contrib/pageinspect/pageinspect.control | 2 +-
doc/src/sgml/pageinspect.sgml | 148 +++++++
src/backend/access/hash/hashovfl.c | 52 +--
src/include/access/hash.h | 1 +
7 files changed, 806 insertions(+), 31 deletions(-)
create mode 100644 contrib/pageinspect/hashfuncs.c
create mode 100644 contrib/pageinspect/pageinspect--1.5--1.6.sql
diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index 87a28e9..d7f3645 100644
--- a/contrib/pageinspect/Makefile
+++ b/contrib/pageinspect/Makefile
@@ -2,13 +2,13 @@
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
diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c
new file mode 100644
index 0000000..3b1cd13
--- /dev/null
+++ b/contrib/pageinspect/hashfuncs.c
@@ -0,0 +1,548 @@
+/*
+ * 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_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
+{
+ uint32 live_items;
+ uint32 dead_items;
+ uint32 page_size;
+ uint32 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;
+ HashPageOpaque pageopaque;
+
+ raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
+
+ if (raw_page_size != BLCKSZ)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("input page too small"),
+ 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 unexpected 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)));
+
+ if (!(pageopaque->hasho_flag & flags))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("page is not a hash page of expected type"),
+ errdetail("Expected hash page type: %08x got: %08x.",
+ flags, pageopaque->hasho_flag)));
+ 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 */
+ if (opaque->hasho_flag & LH_BUCKET_PAGE)
+ stat->hasho_prevblkno = InvalidBlockNumber;
+ else
+ 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 * FROM 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, LH_META_PAGE | LH_BUCKET_PAGE |
+ LH_OVERFLOW_PAGE | LH_BITMAP_PAGE);
+ 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 pageinspect 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++] = UInt32GetDatum(stat.live_items);
+ values[j++] = UInt32GetDatum(stat.dead_items);
+ values[j++] = UInt32GetDatum(stat.page_size);
+ values[j++] = UInt32GetDatum(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];
+ HeapTuple tuple;
+ FuncCallContext *fctx;
+ MemoryContext mctx;
+ struct user_args *uargs;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect 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;
+ int off;
+ int dlen;
+ char *s;
+ char *ptr;
+ char *dump;
+ int number;
+
+ 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++] = CStringGetTextDatum(psprintf("(%u,%u)",
+ BlockIdGetBlockNumber(&(itup->t_tid.ip_blkid)),
+ itup->t_tid.ip_posid));
+
+ ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
+ Assert(ptr <= uargs->page + BLCKSZ);
+ dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info);
+ dump = palloc(dlen * 2 + 1);
+ s = dump;
+ for (off = (dlen - 1); off >= 0; off--)
+ {
+ sprintf(dump, "%02x", ptr[off] & 0xff);
+ dump += 2;
+ }
+ *dump++ = '\0';
+
+ number = (int) strtol(s, NULL, 16);
+
+ values[j] = Int32GetDatum(number & 0xFFFFFFFF);
+
+ 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);
+ bytea *raw_page;
+ char *raw_page_data;
+ HashMetaPage metap;
+ Buffer buf, metabuf, mapbuf;
+ BlockNumber bitmapblkno;
+ Page page, mappage;
+ TupleDesc tupleDesc;
+ Relation indexRel;
+ uint32 ovflbitno, bit = 0;
+ int32 bitmappage,bitmapbit;
+ HeapTuple tuple;
+ int j;
+ Datum values[2];
+ bool nulls[2];
+ uint32 *freep = NULL;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect 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))));
+
+ /* Create a raw page */
+ raw_page = (bytea *) palloc(BLCKSZ + VARHDRSZ);
+ SET_VARSIZE(raw_page, BLCKSZ + VARHDRSZ);
+ raw_page_data = VARDATA(raw_page);
+
+ buf = ReadBufferExtended(indexRel, MAIN_FORKNUM, ovflblkno, RBM_NORMAL, NULL);
+ LockBuffer(buf, BUFFER_LOCK_SHARE);
+
+ memcpy(raw_page_data, BufferGetPage(buf), BLCKSZ);
+
+ LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+ ReleaseBuffer(buf);
+
+ page = verify_hash_page(raw_page, LH_OVERFLOW_PAGE);
+
+ /* 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];
+
+ /* Check the status of bitmap bit for overflow page */
+ mapbuf = _hash_getbuf(indexRel, bitmapblkno, HASH_WRITE, LH_BITMAP_PAGE);
+ mappage = BufferGetPage(mapbuf);
+ freep = HashPageGetBitmap(mappage);
+
+ if (ISSET(freep, bitmapbit))
+ bit = 1;
+
+ _hash_relbuf(indexRel, metabuf);
+
+ _hash_relbuf(indexRel, mapbuf);
+
+ 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++] = UInt32GetDatum(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];
+ char *spares;
+ char *mapp;
+ char *s;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect 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++] = CStringGetTextDatum(psprintf("0x%08X", 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);
+
+ spares = palloc0(HASH_MAX_SPLITPOINTS * 5 + 1);
+ s = spares;
+ for (i = 0; i < HASH_MAX_SPLITPOINTS; i++)
+ {
+ if (i > 0)
+ *spares++ = ' ';
+
+ sprintf(spares, "%04x", metad->hashm_spares[i]);
+ spares += 4;
+ }
+ values[j++] = CStringGetTextDatum(s);
+
+ mapp = palloc0(HASH_MAX_BITMAPS * 5 + 1);
+ s = mapp;
+ for (i = 0; i < HASH_MAX_BITMAPS; i++)
+ {
+ if (i > 0)
+ *mapp++ = ' ';
+
+ sprintf(mapp, "%04x", metad->hashm_mapp[i]);
+ mapp += 4;
+ }
+ values[j++] = CStringGetTextDatum(s);
+
+ 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 0000000..e159099
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.5--1.6.sql
@@ -0,0 +1,76 @@
+/* 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 int4,
+ OUT dead_items int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT hasho_prevblkno int4,
+ OUT hasho_nextblkno int4,
+ OUT hasho_bucket int4,
+ OUT hasho_flag int4,
+ OUT hasho_page_id int8)
+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 int4,
+ OUT bitmapblkno int4,
+ OUT bitmapbit int4)
+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 text,
+ OUT version int4,
+ OUT ntuples double precision,
+ OUT ffactor int2,
+ OUT bsize int2,
+ OUT bmsize int2,
+ OUT bmshift int2,
+ OUT maxbucket int4,
+ OUT highmask int4,
+ OUT lowmask int4,
+ OUT ovflpoint int4,
+ OUT firstfree int4,
+ OUT nmaps int4,
+ OUT procid int4,
+ OUT spares text,
+ OUT mapp text)
+AS 'MODULE_PATHNAME', 'hash_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control
index 23c8eff..1a61c9f 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/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml
index d12dbac..3eba20c 100644
--- a/doc/src/sgml/pageinspect.sgml
+++ b/doc/src/sgml/pageinspect.sgml
@@ -493,4 +493,152 @@ test=# SELECT first_tid, nbytes, tids[0:5] AS some_tids
</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 * FROM 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 | -1
+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));
+ itemoffset | ctid | data
+------------+-----------------+------------
+ 1 | (3407872,12584) | 1053474816
+ 2 | (3407872,12584) | 1053474816
+ 3 | (3145728,12584) | 1053474816
+ 4 | (3145728,12584) | 1053474816
+ 5 | (3407872,12584) | 1053474816
+...
+ 131 | (2883584,13352) | 2778469888
+ 132 | (2883584,12840) | 2778469888
+ 133 | (2883584,12328) | 2778469888
+</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', 2050);
+ bitmapblkno | bitmapbit
+-------------+-----------
+ 65 | 1
+</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 | 0x06440640
+version | 2
+ntuples | 686000
+ffactor | 40
+bsize | 8152
+bmsize | 4096
+bmshift | 15
+maxbucket | 17149
+highmask | 32767
+lowmask | 16383
+ovflpoint | 15
+firstfree | 2012
+nmaps | 1
+procid | 450
+spares | 0000 0000 0000 0000 0000 0000 0001 0001 0001 0001 0001 0004 003b 02c0 07c3 07dc 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+mapp | 0041 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </sect2>
+
</sect1>
diff --git a/src/backend/access/hash/hashovfl.c b/src/backend/access/hash/hashovfl.c
index 6d4e723..8176584 100644
--- a/src/backend/access/hash/hashovfl.c
+++ b/src/backend/access/hash/hashovfl.c
@@ -53,30 +53,6 @@ bitno_to_blkno(HashMetaPage metap, uint32 ovflbitnum)
}
/*
- * Convert overflow page block number to bit number for free-page bitmap.
- */
-static uint32
-blkno_to_bitno(HashMetaPage metap, BlockNumber ovflblkno)
-{
- uint32 splitnum = metap->hashm_ovflpoint;
- uint32 i;
- uint32 bitnum;
-
- /* Determine the split number containing this page */
- for (i = 1; i <= splitnum; i++)
- {
- if (ovflblkno <= (BlockNumber) (1 << i))
- break; /* oops */
- bitnum = ovflblkno - (1 << i);
- if (bitnum <= metap->hashm_spares[i])
- return bitnum - 1; /* -1 to convert 1-based to 0-based */
- }
-
- elog(ERROR, "invalid overflow block number %u", ovflblkno);
- return 0; /* keep compiler quiet */
-}
-
-/*
* _hash_addovflpage
*
* Add an overflow page to the bucket whose last page is pointed to by 'buf'.
@@ -552,7 +528,7 @@ _hash_freeovflpage(Relation rel, Buffer bucketbuf, Buffer ovflbuf,
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);
@@ -1087,3 +1063,29 @@ readpage:
/* NOTREACHED */
}
+
+/*
+ * _hash_ovflblkno_to_bitno
+ *
+ * Convert overflow page block number to bit number for free-page bitmap.
+ */
+uint32
+_hash_ovflblkno_to_bitno(HashMetaPage metap, BlockNumber ovflblkno)
+{
+ uint32 splitnum = metap->hashm_ovflpoint;
+ uint32 i;
+ uint32 bitnum;
+
+ /* Determine the split number containing this page */
+ for (i = 1; i <= splitnum; i++)
+ {
+ if (ovflblkno <= (BlockNumber) (1 << i))
+ break; /* oops */
+ bitnum = ovflblkno - (1 << i);
+ if (bitnum <= metap->hashm_spares[i])
+ return bitnum - 1; /* -1 to convert 1-based to 0-based */
+ }
+
+ elog(ERROR, "invalid overflow block number %u", ovflblkno);
+ return 0; /* keep compiler quiet */
+}
diff --git a/src/include/access/hash.h b/src/include/access/hash.h
index 8328fc5..687b3d5 100644
--- a/src/include/access/hash.h
+++ b/src/include/access/hash.h
@@ -323,6 +323,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,
--
1.8.3.1
Hi,
+ /* + * We copy the page into local storage to avoid holding pin on the + * buffer longer than we must, and possibly failing to release it at + * all if the calling query doesn't fetch all rows. + */ + mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx); + + uargs = palloc(sizeof(struct user_args)); + + uargs->page = palloc(BLCKSZ);Is this necessary? I think this was copied from btreefuncs, but there
is no buffer release in this code.
Yes, it was copied from btreefuncs and is not required in this case as
we are already passing raw_page as an input to hash_page_items. I have
taken care of it in the updated patch shared up thread.
With Regards,
Ashutosh Sharma
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi,
On 01/11/2017 03:16 PM, Ashutosh Sharma wrote:
I have rephrased it to make it more clear.
Rebased, and removed the compile warn in hashfuncs.c
Best regards,
Jesper
Attachments:
0001-Add-support-for-hash-index-in-pageinspect-contrib-mo_v5.patchtext/x-patch; name=0001-Add-support-for-hash-index-in-pageinspect-contrib-mo_v5.patchDownload
From 8a07230b89b97280f0f1d645145da1fd140969c6 Mon Sep 17 00:00:00 2001
From: jesperpedersen <jesper.pedersen@redhat.com>
Date: Thu, 12 Jan 2017 14:00:45 -0500
Subject: [PATCH] Add support for hash index in pageinspect contrib module v13
Authors: Ashutosh Sharma and Jesper Pedersen.
---
contrib/pageinspect/Makefile | 10 +-
contrib/pageinspect/hashfuncs.c | 548 ++++++++++++++++++++++++++
contrib/pageinspect/pageinspect--1.5--1.6.sql | 76 ++++
contrib/pageinspect/pageinspect.control | 2 +-
doc/src/sgml/pageinspect.sgml | 148 +++++++
src/backend/access/hash/hashovfl.c | 52 +--
src/include/access/hash.h | 1 +
7 files changed, 806 insertions(+), 31 deletions(-)
create mode 100644 contrib/pageinspect/hashfuncs.c
create mode 100644 contrib/pageinspect/pageinspect--1.5--1.6.sql
diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index 87a28e9..d7f3645 100644
--- a/contrib/pageinspect/Makefile
+++ b/contrib/pageinspect/Makefile
@@ -2,13 +2,13 @@
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
diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c
new file mode 100644
index 0000000..25e9641
--- /dev/null
+++ b/contrib/pageinspect/hashfuncs.c
@@ -0,0 +1,548 @@
+/*
+ * 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_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
+{
+ uint32 live_items;
+ uint32 dead_items;
+ uint32 page_size;
+ uint32 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;
+ HashPageOpaque pageopaque;
+
+ raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
+
+ if (raw_page_size != BLCKSZ)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("input page too small"),
+ 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 unexpected 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)));
+
+ if (!(pageopaque->hasho_flag & flags))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("page is not a hash page of expected type"),
+ errdetail("Expected hash page type: %08x got: %08x.",
+ flags, pageopaque->hasho_flag)));
+ 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 */
+ if (opaque->hasho_flag & LH_BUCKET_PAGE)
+ stat->hasho_prevblkno = InvalidBlockNumber;
+ else
+ 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 * FROM 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, LH_META_PAGE | LH_BUCKET_PAGE |
+ LH_OVERFLOW_PAGE | LH_BITMAP_PAGE);
+ 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 pageinspect 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++] = UInt32GetDatum(stat.live_items);
+ values[j++] = UInt32GetDatum(stat.dead_items);
+ values[j++] = UInt32GetDatum(stat.page_size);
+ values[j++] = UInt32GetDatum(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];
+ HeapTuple tuple;
+ FuncCallContext *fctx;
+ MemoryContext mctx;
+ struct user_args *uargs;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect 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;
+ int off;
+ int dlen;
+ char *s;
+ char *ptr;
+ char *dump;
+ int number;
+
+ 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++] = CStringGetTextDatum(psprintf("(%u,%u)",
+ BlockIdGetBlockNumber(&(itup->t_tid.ip_blkid)),
+ itup->t_tid.ip_posid));
+
+ ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
+ Assert(ptr <= uargs->page + BLCKSZ);
+ dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info);
+ dump = palloc(dlen * 2 + 1);
+ s = dump;
+ for (off = (dlen - 1); off >= 0; off--)
+ {
+ sprintf(dump, "%02x", ptr[off] & 0xff);
+ dump += 2;
+ }
+ *dump++ = '\0';
+
+ number = (int) strtol(s, NULL, 16);
+
+ values[j] = Int32GetDatum(number & 0xFFFFFFFF);
+
+ 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);
+ bytea *raw_page;
+ char *raw_page_data;
+ HashMetaPage metap;
+ Buffer buf, metabuf, mapbuf;
+ BlockNumber bitmapblkno;
+ Page mappage;
+ TupleDesc tupleDesc;
+ Relation indexRel;
+ uint32 ovflbitno, bit = 0;
+ int32 bitmappage,bitmapbit;
+ HeapTuple tuple;
+ int j;
+ Datum values[2];
+ bool nulls[2];
+ uint32 *freep = NULL;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect 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))));
+
+ /* Create a raw page */
+ raw_page = (bytea *) palloc(BLCKSZ + VARHDRSZ);
+ SET_VARSIZE(raw_page, BLCKSZ + VARHDRSZ);
+ raw_page_data = VARDATA(raw_page);
+
+ buf = ReadBufferExtended(indexRel, MAIN_FORKNUM, ovflblkno, RBM_NORMAL, NULL);
+ LockBuffer(buf, BUFFER_LOCK_SHARE);
+
+ memcpy(raw_page_data, BufferGetPage(buf), BLCKSZ);
+
+ LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+ ReleaseBuffer(buf);
+
+ verify_hash_page(raw_page, LH_OVERFLOW_PAGE);
+
+ /* 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];
+
+ /* Check the status of bitmap bit for overflow page */
+ mapbuf = _hash_getbuf(indexRel, bitmapblkno, HASH_WRITE, LH_BITMAP_PAGE);
+ mappage = BufferGetPage(mapbuf);
+ freep = HashPageGetBitmap(mappage);
+
+ if (ISSET(freep, bitmapbit))
+ bit = 1;
+
+ _hash_relbuf(indexRel, metabuf);
+
+ _hash_relbuf(indexRel, mapbuf);
+
+ 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++] = UInt32GetDatum(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];
+ char *spares;
+ char *mapp;
+ char *s;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect 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++] = CStringGetTextDatum(psprintf("0x%08X", 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);
+
+ spares = palloc0(HASH_MAX_SPLITPOINTS * 5 + 1);
+ s = spares;
+ for (i = 0; i < HASH_MAX_SPLITPOINTS; i++)
+ {
+ if (i > 0)
+ *spares++ = ' ';
+
+ sprintf(spares, "%04x", metad->hashm_spares[i]);
+ spares += 4;
+ }
+ values[j++] = CStringGetTextDatum(s);
+
+ mapp = palloc0(HASH_MAX_BITMAPS * 5 + 1);
+ s = mapp;
+ for (i = 0; i < HASH_MAX_BITMAPS; i++)
+ {
+ if (i > 0)
+ *mapp++ = ' ';
+
+ sprintf(mapp, "%04x", metad->hashm_mapp[i]);
+ mapp += 4;
+ }
+ values[j++] = CStringGetTextDatum(s);
+
+ 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 0000000..e159099
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.5--1.6.sql
@@ -0,0 +1,76 @@
+/* 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 int4,
+ OUT dead_items int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT hasho_prevblkno int4,
+ OUT hasho_nextblkno int4,
+ OUT hasho_bucket int4,
+ OUT hasho_flag int4,
+ OUT hasho_page_id int8)
+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 int4,
+ OUT bitmapblkno int4,
+ OUT bitmapbit int4)
+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 text,
+ OUT version int4,
+ OUT ntuples double precision,
+ OUT ffactor int2,
+ OUT bsize int2,
+ OUT bmsize int2,
+ OUT bmshift int2,
+ OUT maxbucket int4,
+ OUT highmask int4,
+ OUT lowmask int4,
+ OUT ovflpoint int4,
+ OUT firstfree int4,
+ OUT nmaps int4,
+ OUT procid int4,
+ OUT spares text,
+ OUT mapp text)
+AS 'MODULE_PATHNAME', 'hash_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control
index 23c8eff..1a61c9f 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/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml
index d12dbac..3eba20c 100644
--- a/doc/src/sgml/pageinspect.sgml
+++ b/doc/src/sgml/pageinspect.sgml
@@ -493,4 +493,152 @@ test=# SELECT first_tid, nbytes, tids[0:5] AS some_tids
</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 * FROM 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 | -1
+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));
+ itemoffset | ctid | data
+------------+-----------------+------------
+ 1 | (3407872,12584) | 1053474816
+ 2 | (3407872,12584) | 1053474816
+ 3 | (3145728,12584) | 1053474816
+ 4 | (3145728,12584) | 1053474816
+ 5 | (3407872,12584) | 1053474816
+...
+ 131 | (2883584,13352) | 2778469888
+ 132 | (2883584,12840) | 2778469888
+ 133 | (2883584,12328) | 2778469888
+</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', 2050);
+ bitmapblkno | bitmapbit
+-------------+-----------
+ 65 | 1
+</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 | 0x06440640
+version | 2
+ntuples | 686000
+ffactor | 40
+bsize | 8152
+bmsize | 4096
+bmshift | 15
+maxbucket | 17149
+highmask | 32767
+lowmask | 16383
+ovflpoint | 15
+firstfree | 2012
+nmaps | 1
+procid | 450
+spares | 0000 0000 0000 0000 0000 0000 0001 0001 0001 0001 0001 0004 003b 02c0 07c3 07dc 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+mapp | 0041 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </sect2>
+
</sect1>
diff --git a/src/backend/access/hash/hashovfl.c b/src/backend/access/hash/hashovfl.c
index e8928ef..756cce2 100644
--- a/src/backend/access/hash/hashovfl.c
+++ b/src/backend/access/hash/hashovfl.c
@@ -52,30 +52,6 @@ bitno_to_blkno(HashMetaPage metap, uint32 ovflbitnum)
}
/*
- * Convert overflow page block number to bit number for free-page bitmap.
- */
-static uint32
-blkno_to_bitno(HashMetaPage metap, BlockNumber ovflblkno)
-{
- uint32 splitnum = metap->hashm_ovflpoint;
- uint32 i;
- uint32 bitnum;
-
- /* Determine the split number containing this page */
- for (i = 1; i <= splitnum; i++)
- {
- if (ovflblkno <= (BlockNumber) (1 << i))
- break; /* oops */
- bitnum = ovflblkno - (1 << i);
- if (bitnum <= metap->hashm_spares[i])
- return bitnum - 1; /* -1 to convert 1-based to 0-based */
- }
-
- elog(ERROR, "invalid overflow block number %u", ovflblkno);
- return 0; /* keep compiler quiet */
-}
-
-/*
* _hash_addovflpage
*
* Add an overflow page to the bucket whose last page is pointed to by 'buf'.
@@ -485,7 +461,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);
@@ -814,3 +790,29 @@ _hash_squeezebucket(Relation rel,
/* NOTREACHED */
}
+
+/*
+ * _hash_ovflblkno_to_bitno
+ *
+ * Convert overflow page block number to bit number for free-page bitmap.
+ */
+uint32
+_hash_ovflblkno_to_bitno(HashMetaPage metap, BlockNumber ovflblkno)
+{
+ uint32 splitnum = metap->hashm_ovflpoint;
+ uint32 i;
+ uint32 bitnum;
+
+ /* Determine the split number containing this page */
+ for (i = 1; i <= splitnum; i++)
+ {
+ if (ovflblkno <= (BlockNumber) (1 << i))
+ break; /* oops */
+ bitnum = ovflblkno - (1 << i);
+ if (bitnum <= metap->hashm_spares[i])
+ return bitnum - 1; /* -1 to convert 1-based to 0-based */
+ }
+
+ elog(ERROR, "invalid overflow block number %u", ovflblkno);
+ return 0; /* keep compiler quiet */
+}
diff --git a/src/include/access/hash.h b/src/include/access/hash.h
index b0a1131..60a801f 100644
--- a/src/include/access/hash.h
+++ b/src/include/access/hash.h
@@ -321,6 +321,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,
--
2.7.4
On Fri, Jan 13, 2017 at 12:49 AM, Jesper Pedersen
<jesper.pedersen@redhat.com> wrote:
Rebased, and removed the compile warn in hashfuncs.c
I did some testing and review for the patch. I did not see any major
issue, but there are few minor cases for which I have few suggestions.
1. Source File : /doc/src/sgml/pageinspect.sgml
example given.
SELECT * FROM hash_page_type(get_raw_page('con_hash_index', 0));
can be changed to
SELECT hash_page_type(get_raw_page('con_hash_index', 0));
2. @verify_hash_page
I can easily produce this error right after the split, so there will
be pages which are not inited before it is used. So an error saying it
is unexpected might be slightly misleading. I think error message
should be improved.
postgres=# SELECT hash_page_type(get_raw_page('i1', 12));
ERROR: index table contains unexpected zero page
3. @verify_hash_page
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("page is not a HASH page"),
+ errdetail("Expected %08x, got %08x.",
In error message "HASH" can downcase to "hash". That makes error
messages consistent with other error messages like "page is not a hash
page of expected type"
4. The condition is raw_page_size != BLCKSZ; But error msg "input page
too small"; I think error message should be changed to "invalid page
size".
if (raw_page_size != BLCKSZ)
+ ereport(ERROR,
i+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("input page too small"),
+ errdetail("Expected size %d, got %d",
+ BLCKSZ, raw_page_size)));
5. @hash_bitmap_info
Metabuf can be released once after bitmapblkno is set it is off no use.
_hash_relbuf(indexRel, metabuf);
is Write lock required here for bitmap page?
mapbuf = _hash_getbuf(indexRel, bitmapblkno, HASH_WRITE, LH_BITMAP_PAGE);
6. You have renamed convert_ovflblkno_to_bitno to
_hash_ovflblkno_to_bitno. But unfortunately, you also moved the
function down. The diff appears as if you have completely replaced it.
I think we should put it back to at old place.
7. I think it is not your bug, but probably a bug in Hash index
itself; page flag is set to 66 (for below test); So the page is both
LH_BUCKET_PAGE and LH_BITMAP_PAGE. Is not this a bug in hash index?
I have inserted 3000 records. Hash index is on integer column.
select hasho_flag FROM hash_page_stats(get_raw_page('i1', 1));
hasho_flag
------------
66
--
Thanks and Regards
Mithun C Y
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
7. I think it is not your bug, but probably a bug in Hash index
itself; page flag is set to 66 (for below test); So the page is both
LH_BUCKET_PAGE and LH_BITMAP_PAGE. Is not this a bug in hash index?I have inserted 3000 records. Hash index is on integer column.
select hasho_flag FROM hash_page_stats(get_raw_page('i1', 1));
hasho_flag
------------
66
Here is the test for same. After insertion of 3000 records, I think at
first split we can see bucket page flag is set with LH_BITMAP_PAGE.
create table t1( ti int);
create index i1 on t1 using hash(ti);
postgres=# select hasho_flag from hash_page_stats(get_raw_page('i1', 1));
hasho_flag
------------
2
postgres=# insert into t1 select generate_series(1, 1000);
INSERT 0 1000
postgres=# select hasho_flag from hash_page_stats(get_raw_page('i1', 1));
hasho_flag
------------
2
(1 row)
postgres=# insert into t1 select generate_series(1, 1000);
INSERT 0 1000
postgres=# select hasho_flag from hash_page_stats(get_raw_page('i1', 1));
hasho_flag
------------
2
(1 row)
postgres=# insert into t1 select generate_series(1, 1000);
INSERT 0 1000
postgres=# select hasho_flag from hash_page_stats(get_raw_page('i1', 1));
hasho_flag
------------
66
(1 row)
I think If this is a bug then example given in pageinspect.sgml should
be changed in your patch after the bug fix.
+hasho_prevblkno | -1
+hasho_nextblkno | 8474
+hasho_bucket | 0
+hasho_flag | 66
+hasho_page_id | 65408
--
Thanks and Regards
Mithun C Y
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Jan 17, 2017 at 1:22 PM, Mithun Cy <mithun.cy@enterprisedb.com> wrote:
7. I think it is not your bug, but probably a bug in Hash index
itself; page flag is set to 66 (for below test); So the page is both
LH_BUCKET_PAGE and LH_BITMAP_PAGE. Is not this a bug in hash index?I have inserted 3000 records. Hash index is on integer column.
select hasho_flag FROM hash_page_stats(get_raw_page('i1', 1));
hasho_flag
------------
66Here is the test for same. After insertion of 3000 records, I think at
first split we can see bucket page flag is set with LH_BITMAP_PAGE.
I think your calculation is not right. 66 indicates LH_BUCKET_PAGE |
LH_BUCKET_NEEDS_SPLIT_CLEANUP which is a valid state after the split.
This flag will be cleared either during next split or when vacuum
operates on that index page.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Jan 17, 2017 at 3:06 PM, Amit Kapila <amit.kapila16@gmail.com> wrote:
I think your calculation is not right. 66 indicates LH_BUCKET_PAGE |
LH_BUCKET_NEEDS_SPLIT_CLEANUP which is a valid state after the split.
This flag will be cleared either during next split or when vacuum
operates on that index page.
Oops, I errored in decimal to binary conversion. Sorry, I was wrong.
What you said is correct. No need to change .sgml file.
--
Thanks and Regards
Mithun C Y
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Jan 13, 2017 at 12:49 AM, Jesper Pedersen
<jesper.pedersen@redhat.com> wrote:
Hi,
On 01/11/2017 03:16 PM, Ashutosh Sharma wrote:
I have rephrased it to make it more clear.
Rebased, and removed the compile warn in hashfuncs.c
Review comments:
1.
+static Page
+verify_hash_page(bytea *raw_page, int flags)
Few checks for meta page are missing, refer _hash_checkpage.
2.
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use pageinspect functions"))));
Isn't it better to use "raw page" instead of "pageinspect" in the
above error message? If you agree, then fix other similar occurrences
in the patch.
3.
+ values[j++] = CStringGetTextDatum(psprintf("(%u,%u)",
+ BlockIdGetBlockNumber(&(itup->t_tid.ip_blkid)),
+ itup->t_tid.ip_posid));
Fix indentation in the third line.
4.
+Datum
+hash_page_items(PG_FUNCTION_ARGS)
+{
+ bytea *raw_page = PG_GETARG_BYTEA_P(0);
+Datum
+hash_bitmap_info(PG_FUNCTION_ARGS)
+{
+ Oid indexRelid = PG_GETARG_OID(0);
+ uint32 ovflblkno = PG_GETARG_UINT32(1);
Is there a reason for keeping the input arguments for
hash_bitmap_info() different from hash_page_items()?
5.
+hash_metapage_info(PG_FUNCTION_ARGS)
{
..
+ spares = palloc0(HASH_MAX_SPLITPOINTS * 5 + 1);
..
+ mapp = palloc0(HASH_MAX_BITMAPS * 5 + 1);
..
}
Don't you think we should free the allocated memory in this function?
Also, why are you 5 as a multiplier in both the above pallocs,
shouldn't it be 4?
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi,
1. +static Page +verify_hash_page(bytea *raw_page, int flags)Few checks for meta page are missing, refer _hash_checkpage.
okay, I have added the checks for meta page as well. Please refer to
attached patch.
2. + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + (errmsg("must be superuser to use pageinspect functions"))));Isn't it better to use "raw page" instead of "pageinspect" in the
above error message? If you agree, then fix other similar occurrences
in the patch.
Yes, knowing that we deal with raw pages as in brin index it would be
good to use raw page instead of pageinspect to maintain the
consistency in the error message.
3. + values[j++] = CStringGetTextDatum(psprintf("(%u,%u)", + BlockIdGetBlockNumber(&(itup->t_tid.ip_blkid)), + itup->t_tid.ip_posid));Fix indentation in the third line.
Corrected. Please check the attached patch.
4. +Datum +hash_page_items(PG_FUNCTION_ARGS) +{ + bytea *raw_page = PG_GETARG_BYTEA_P(0);+Datum +hash_bitmap_info(PG_FUNCTION_ARGS) +{ + Oid indexRelid = PG_GETARG_OID(0); + uint32 ovflblkno = PG_GETARG_UINT32(1);Is there a reason for keeping the input arguments for
hash_bitmap_info() different from hash_page_items()?
Yes, there are two reasons behind it.
Firstly, we need metapage to identify the bitmap page that holds the
information about the overflow page passed as an input to this
function.
Secondly, we will have to input overflow block number as an input to
this function so as to determine the overflow bit number which can be
used further to identify the bitmap page.
+ /* 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];
5. +hash_metapage_info(PG_FUNCTION_ARGS) { .. + spares = palloc0(HASH_MAX_SPLITPOINTS * 5 + 1); .. + mapp = palloc0(HASH_MAX_BITMAPS * 5 + 1); .. }Don't you think we should free the allocated memory in this function?
Also, why are you 5 as a multiplier in both the above pallocs,
shouldn't it be 4?
Yes, we should free it. We have used 5 as a multiplier instead of 4
because of ' ' character.
Apart from above comments, the attached patch also handles all the
comments from Mithun.
With Regards,
Ashutosh Sharma.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
0001-Add-support-for-hash-index-in-pageinspect-contrib-mo.patchinvalid/octet-stream; name=0001-Add-support-for-hash-index-in-pageinspect-contrib-mo.patchDownload
From b7b2055accfae4f80652bec15c9c42c8c5bad0d5 Mon Sep 17 00:00:00 2001
From: ashu <ashu@localhost.localdomain>
Date: Wed, 18 Jan 2017 15:06:54 +0530
Subject: [PATCH] Add support for hash index in pageinspect contrib module v14
Authors: Ashutosh Sharma and Jesper Pedersen.
---
contrib/pageinspect/Makefile | 10 +-
contrib/pageinspect/hashfuncs.c | 574 ++++++++++++++++++++++++++
contrib/pageinspect/pageinspect--1.5--1.6.sql | 76 ++++
contrib/pageinspect/pageinspect.control | 2 +-
doc/src/sgml/pageinspect.sgml | 148 +++++++
src/backend/access/hash/hashovfl.c | 8 +-
src/include/access/hash.h | 1 +
7 files changed, 810 insertions(+), 9 deletions(-)
create mode 100644 contrib/pageinspect/hashfuncs.c
create mode 100644 contrib/pageinspect/pageinspect--1.5--1.6.sql
diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index 87a28e9..052a8e1 100644
--- a/contrib/pageinspect/Makefile
+++ b/contrib/pageinspect/Makefile
@@ -2,13 +2,13 @@
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
diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c
new file mode 100644
index 0000000..ab076d9
--- /dev/null
+++ b/contrib/pageinspect/hashfuncs.c
@@ -0,0 +1,574 @@
+/*
+ * 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_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
+{
+ uint32 live_items;
+ uint32 dead_items;
+ uint32 page_size;
+ uint32 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;
+ 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)));
+
+ if (!(pageopaque->hasho_flag & flags))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("page is not a hash page of expected type"),
+ errdetail("Expected hash page type: %08x got: %08x.",
+ flags, pageopaque->hasho_flag)));
+
+ /*
+ * If it is metapage, also verify magic number and version.
+ */
+ if (flags == 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 %d, got %d",
+ 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 */
+ if (opaque->hasho_flag & LH_BUCKET_PAGE)
+ stat->hasho_prevblkno = InvalidBlockNumber;
+ else
+ 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 * FROM 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, LH_META_PAGE | LH_BUCKET_PAGE |
+ LH_OVERFLOW_PAGE | LH_BITMAP_PAGE);
+ 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++] = UInt32GetDatum(stat.live_items);
+ values[j++] = UInt32GetDatum(stat.dead_items);
+ values[j++] = UInt32GetDatum(stat.page_size);
+ values[j++] = UInt32GetDatum(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];
+ 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;
+ int off;
+ int dlen;
+ char *s;
+ char *ptr;
+ char *dump;
+ int number;
+
+ 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++] = CStringGetTextDatum(psprintf("(%u,%u)",
+ BlockIdGetBlockNumber(&(itup->t_tid.ip_blkid)),
+ itup->t_tid.ip_posid));
+
+ ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
+ Assert(ptr <= uargs->page + BLCKSZ);
+ dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info);
+ dump = palloc(dlen * 2 + 1);
+ s = dump;
+ for (off = (dlen - 1); off >= 0; off--)
+ {
+ sprintf(dump, "%02x", ptr[off] & 0xff);
+ dump += 2;
+ }
+ *dump++ = '\0';
+
+ number = (int) strtol(s, NULL, 16);
+
+ values[j] = Int32GetDatum(number & 0xFFFFFFFF);
+ pfree(s);
+
+ 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);
+ bytea *raw_page;
+ char *raw_page_data;
+ HashMetaPage metap;
+ Buffer buf, metabuf, mapbuf;
+ BlockNumber bitmapblkno;
+ Page mappage;
+ TupleDesc tupleDesc;
+ Relation indexRel;
+ uint32 ovflbitno, bit = 0;
+ int32 bitmappage,bitmapbit;
+ HeapTuple tuple;
+ int j;
+ Datum values[2];
+ bool nulls[2];
+ uint32 *freep = NULL;
+
+ 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))));
+
+ /* Create a raw page */
+ raw_page = (bytea *) palloc(BLCKSZ + VARHDRSZ);
+ SET_VARSIZE(raw_page, BLCKSZ + VARHDRSZ);
+ raw_page_data = VARDATA(raw_page);
+
+ buf = ReadBufferExtended(indexRel, MAIN_FORKNUM, ovflblkno, RBM_NORMAL, NULL);
+ LockBuffer(buf, BUFFER_LOCK_SHARE);
+
+ memcpy(raw_page_data, BufferGetPage(buf), BLCKSZ);
+
+ LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+ ReleaseBuffer(buf);
+
+ verify_hash_page(raw_page, LH_OVERFLOW_PAGE);
+
+ /* 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);
+
+ /* Check the status of bitmap bit for overflow page */
+ mapbuf = _hash_getbuf(indexRel, bitmapblkno, HASH_READ, LH_BITMAP_PAGE);
+ mappage = BufferGetPage(mapbuf);
+ freep = HashPageGetBitmap(mappage);
+
+ if (ISSET(freep, bitmapbit))
+ bit = 1;
+
+ _hash_relbuf(indexRel, mapbuf);
+
+ 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++] = UInt32GetDatum(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];
+ char *spares;
+ char *mapp;
+ char *s;
+
+ 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++] = CStringGetTextDatum(psprintf("0x%08X", 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);
+
+ spares = palloc0(HASH_MAX_SPLITPOINTS * 5 + 1);
+ s = spares;
+ for (i = 0; i < HASH_MAX_SPLITPOINTS; i++)
+ {
+ if (i > 0)
+ *spares++ = ' ';
+
+ sprintf(spares, "%04x", metad->hashm_spares[i]);
+ spares += 4;
+ }
+ values[j++] = CStringGetTextDatum(s);
+ pfree(s);
+
+ mapp = palloc0(HASH_MAX_BITMAPS * 5 + 1);
+ s = mapp;
+ for (i = 0; i < HASH_MAX_BITMAPS; i++)
+ {
+ if (i > 0)
+ *mapp++ = ' ';
+
+ sprintf(mapp, "%04x", metad->hashm_mapp[i]);
+ mapp += 4;
+ }
+ values[j++] = CStringGetTextDatum(s);
+ pfree(s);
+
+ 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 0000000..e159099
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.5--1.6.sql
@@ -0,0 +1,76 @@
+/* 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 int4,
+ OUT dead_items int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT hasho_prevblkno int4,
+ OUT hasho_nextblkno int4,
+ OUT hasho_bucket int4,
+ OUT hasho_flag int4,
+ OUT hasho_page_id int8)
+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 int4,
+ OUT bitmapblkno int4,
+ OUT bitmapbit int4)
+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 text,
+ OUT version int4,
+ OUT ntuples double precision,
+ OUT ffactor int2,
+ OUT bsize int2,
+ OUT bmsize int2,
+ OUT bmshift int2,
+ OUT maxbucket int4,
+ OUT highmask int4,
+ OUT lowmask int4,
+ OUT ovflpoint int4,
+ OUT firstfree int4,
+ OUT nmaps int4,
+ OUT procid int4,
+ OUT spares text,
+ OUT mapp text)
+AS 'MODULE_PATHNAME', 'hash_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control
index 23c8eff..1a61c9f 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/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml
index d12dbac..6022f27 100644
--- a/doc/src/sgml/pageinspect.sgml
+++ b/doc/src/sgml/pageinspect.sgml
@@ -493,4 +493,152 @@ test=# SELECT first_tid, nbytes, tids[0:5] AS some_tids
</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 | -1
+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));
+ itemoffset | ctid | data
+------------+-----------------+------------
+ 1 | (3407872,12584) | 1053474816
+ 2 | (3407872,12584) | 1053474816
+ 3 | (3145728,12584) | 1053474816
+ 4 | (3145728,12584) | 1053474816
+ 5 | (3407872,12584) | 1053474816
+...
+ 131 | (2883584,13352) | 2778469888
+ 132 | (2883584,12840) | 2778469888
+ 133 | (2883584,12328) | 2778469888
+</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', 2050);
+ bitmapblkno | bitmapbit
+-------------+-----------
+ 65 | 1
+</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 | 0x06440640
+version | 2
+ntuples | 686000
+ffactor | 40
+bsize | 8152
+bmsize | 4096
+bmshift | 15
+maxbucket | 17149
+highmask | 32767
+lowmask | 16383
+ovflpoint | 15
+firstfree | 2012
+nmaps | 1
+procid | 450
+spares | 0000 0000 0000 0000 0000 0000 0001 0001 0001 0001 0001 0004 003b 02c0 07c3 07dc 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+mapp | 0041 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </sect2>
+
</sect1>
diff --git a/src/backend/access/hash/hashovfl.c b/src/backend/access/hash/hashovfl.c
index e8928ef..753c8a6 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 b0a1131..60a801f 100644
--- a/src/include/access/hash.h
+++ b/src/include/access/hash.h
@@ -321,6 +321,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,
--
1.8.3.1
Hi,
On 01/18/2017 04:54 AM, Ashutosh Sharma wrote:
Is there a reason for keeping the input arguments for
hash_bitmap_info() different from hash_page_items()?Yes, there are two reasons behind it.
Firstly, we need metapage to identify the bitmap page that holds the
information about the overflow page passed as an input to this
function.Secondly, we will have to input overflow block number as an input to
this function so as to determine the overflow bit number which can be
used further to identify the bitmap page.+ /* 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];
As discussed off-list (along with my patch that included some of these
fixes), it would require calculation from the user to be able to provide
the necessary pages through get_raw_page().
Other ideas on how to improve this are most welcome.
Apart from above comments, the attached patch also handles all the
comments from Mithun.
Please, include a version number for your patch files in the future.
Fixed in this version:
* verify_hash_page: Display magic in hex, like hash_metapage_info
* Update header for hash_page_type
Moving the patch back to 'Needs Review'.
Best regards,
Jesper
Attachments:
0001-Add-support-for-hash-index-in-pageinspect-contrib-mo_v7.patchtext/x-patch; name=0001-Add-support-for-hash-index-in-pageinspect-contrib-mo_v7.patchDownload
From 8e5a68cfaf979bcab88fb9358b88a89bc780a277 Mon Sep 17 00:00:00 2001
From: jesperpedersen <jesper.pedersen@redhat.com>
Date: Wed, 18 Jan 2017 08:42:02 -0500
Subject: [PATCH] Add support for hash index in pageinspect contrib module v15
Authors: Ashutosh Sharma and Jesper Pedersen.
---
contrib/pageinspect/Makefile | 10 +-
contrib/pageinspect/hashfuncs.c | 574 ++++++++++++++++++++++++++
contrib/pageinspect/pageinspect--1.5--1.6.sql | 76 ++++
contrib/pageinspect/pageinspect.control | 2 +-
doc/src/sgml/pageinspect.sgml | 148 +++++++
src/backend/access/hash/hashovfl.c | 8 +-
src/include/access/hash.h | 1 +
7 files changed, 810 insertions(+), 9 deletions(-)
create mode 100644 contrib/pageinspect/hashfuncs.c
create mode 100644 contrib/pageinspect/pageinspect--1.5--1.6.sql
diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index 87a28e9..052a8e1 100644
--- a/contrib/pageinspect/Makefile
+++ b/contrib/pageinspect/Makefile
@@ -2,13 +2,13 @@
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
diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c
new file mode 100644
index 0000000..f157fcc
--- /dev/null
+++ b/contrib/pageinspect/hashfuncs.c
@@ -0,0 +1,574 @@
+/*
+ * 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_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
+{
+ uint32 live_items;
+ uint32 dead_items;
+ uint32 page_size;
+ uint32 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;
+ 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)));
+
+ if (!(pageopaque->hasho_flag & flags))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("page is not a hash page of expected type"),
+ errdetail("Expected hash page type: %08x got: %08x.",
+ flags, pageopaque->hasho_flag)));
+
+ /*
+ * If it is metapage, also verify magic number and version.
+ */
+ if (flags == 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 */
+ if (opaque->hasho_flag & LH_BUCKET_PAGE)
+ stat->hasho_prevblkno = InvalidBlockNumber;
+ else
+ 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, LH_META_PAGE | LH_BUCKET_PAGE |
+ LH_OVERFLOW_PAGE | LH_BITMAP_PAGE);
+ 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++] = UInt32GetDatum(stat.live_items);
+ values[j++] = UInt32GetDatum(stat.dead_items);
+ values[j++] = UInt32GetDatum(stat.page_size);
+ values[j++] = UInt32GetDatum(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];
+ 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;
+ int off;
+ int dlen;
+ char *s;
+ char *ptr;
+ char *dump;
+ int number;
+
+ 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++] = CStringGetTextDatum(psprintf("(%u,%u)",
+ BlockIdGetBlockNumber(&(itup->t_tid.ip_blkid)),
+ itup->t_tid.ip_posid));
+
+ ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
+ Assert(ptr <= uargs->page + BLCKSZ);
+ dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info);
+ dump = palloc(dlen * 2 + 1);
+ s = dump;
+ for (off = (dlen - 1); off >= 0; off--)
+ {
+ sprintf(dump, "%02x", ptr[off] & 0xff);
+ dump += 2;
+ }
+ *dump++ = '\0';
+
+ number = (int) strtol(s, NULL, 16);
+
+ values[j] = Int32GetDatum(number & 0xFFFFFFFF);
+ pfree(s);
+
+ 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);
+ bytea *raw_page;
+ char *raw_page_data;
+ HashMetaPage metap;
+ Buffer buf, metabuf, mapbuf;
+ BlockNumber bitmapblkno;
+ Page mappage;
+ TupleDesc tupleDesc;
+ Relation indexRel;
+ uint32 ovflbitno, bit = 0;
+ int32 bitmappage,bitmapbit;
+ HeapTuple tuple;
+ int j;
+ Datum values[2];
+ bool nulls[2];
+ uint32 *freep = NULL;
+
+ 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))));
+
+ /* Create a raw page */
+ raw_page = (bytea *) palloc(BLCKSZ + VARHDRSZ);
+ SET_VARSIZE(raw_page, BLCKSZ + VARHDRSZ);
+ raw_page_data = VARDATA(raw_page);
+
+ buf = ReadBufferExtended(indexRel, MAIN_FORKNUM, ovflblkno, RBM_NORMAL, NULL);
+ LockBuffer(buf, BUFFER_LOCK_SHARE);
+
+ memcpy(raw_page_data, BufferGetPage(buf), BLCKSZ);
+
+ LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+ ReleaseBuffer(buf);
+
+ verify_hash_page(raw_page, LH_OVERFLOW_PAGE);
+
+ /* 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);
+
+ /* Check the status of bitmap bit for overflow page */
+ mapbuf = _hash_getbuf(indexRel, bitmapblkno, HASH_READ, LH_BITMAP_PAGE);
+ mappage = BufferGetPage(mapbuf);
+ freep = HashPageGetBitmap(mappage);
+
+ if (ISSET(freep, bitmapbit))
+ bit = 1;
+
+ _hash_relbuf(indexRel, mapbuf);
+
+ 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++] = UInt32GetDatum(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];
+ char *spares;
+ char *mapp;
+ char *s;
+
+ 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++] = CStringGetTextDatum(psprintf("0x%08X", 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);
+
+ spares = palloc0(HASH_MAX_SPLITPOINTS * 5 + 1);
+ s = spares;
+ for (i = 0; i < HASH_MAX_SPLITPOINTS; i++)
+ {
+ if (i > 0)
+ *spares++ = ' ';
+
+ sprintf(spares, "%04x", metad->hashm_spares[i]);
+ spares += 4;
+ }
+ values[j++] = CStringGetTextDatum(s);
+ pfree(s);
+
+ mapp = palloc0(HASH_MAX_BITMAPS * 5 + 1);
+ s = mapp;
+ for (i = 0; i < HASH_MAX_BITMAPS; i++)
+ {
+ if (i > 0)
+ *mapp++ = ' ';
+
+ sprintf(mapp, "%04x", metad->hashm_mapp[i]);
+ mapp += 4;
+ }
+ values[j++] = CStringGetTextDatum(s);
+ pfree(s);
+
+ 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 0000000..e159099
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.5--1.6.sql
@@ -0,0 +1,76 @@
+/* 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 int4,
+ OUT dead_items int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT hasho_prevblkno int4,
+ OUT hasho_nextblkno int4,
+ OUT hasho_bucket int4,
+ OUT hasho_flag int4,
+ OUT hasho_page_id int8)
+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 int4,
+ OUT bitmapblkno int4,
+ OUT bitmapbit int4)
+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 text,
+ OUT version int4,
+ OUT ntuples double precision,
+ OUT ffactor int2,
+ OUT bsize int2,
+ OUT bmsize int2,
+ OUT bmshift int2,
+ OUT maxbucket int4,
+ OUT highmask int4,
+ OUT lowmask int4,
+ OUT ovflpoint int4,
+ OUT firstfree int4,
+ OUT nmaps int4,
+ OUT procid int4,
+ OUT spares text,
+ OUT mapp text)
+AS 'MODULE_PATHNAME', 'hash_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control
index 23c8eff..1a61c9f 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/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml
index d12dbac..6022f27 100644
--- a/doc/src/sgml/pageinspect.sgml
+++ b/doc/src/sgml/pageinspect.sgml
@@ -493,4 +493,152 @@ test=# SELECT first_tid, nbytes, tids[0:5] AS some_tids
</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 | -1
+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));
+ itemoffset | ctid | data
+------------+-----------------+------------
+ 1 | (3407872,12584) | 1053474816
+ 2 | (3407872,12584) | 1053474816
+ 3 | (3145728,12584) | 1053474816
+ 4 | (3145728,12584) | 1053474816
+ 5 | (3407872,12584) | 1053474816
+...
+ 131 | (2883584,13352) | 2778469888
+ 132 | (2883584,12840) | 2778469888
+ 133 | (2883584,12328) | 2778469888
+</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', 2050);
+ bitmapblkno | bitmapbit
+-------------+-----------
+ 65 | 1
+</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 | 0x06440640
+version | 2
+ntuples | 686000
+ffactor | 40
+bsize | 8152
+bmsize | 4096
+bmshift | 15
+maxbucket | 17149
+highmask | 32767
+lowmask | 16383
+ovflpoint | 15
+firstfree | 2012
+nmaps | 1
+procid | 450
+spares | 0000 0000 0000 0000 0000 0000 0001 0001 0001 0001 0001 0004 003b 02c0 07c3 07dc 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+mapp | 0041 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </sect2>
+
</sect1>
diff --git a/src/backend/access/hash/hashovfl.c b/src/backend/access/hash/hashovfl.c
index 504670b..b0ab3d3 100644
--- a/src/backend/access/hash/hashovfl.c
+++ b/src/backend/access/hash/hashovfl.c
@@ -53,10 +53,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;
@@ -558,7 +560,7 @@ _hash_freeovflpage(Relation rel, Buffer bucketbuf, Buffer ovflbuf,
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 b43d30c..7ec6ec5 100644
--- a/src/include/access/hash.h
+++ b/src/include/access/hash.h
@@ -301,6 +301,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,
--
2.7.4
Hi,
I got some 'trailing whitespace' error (shown below) when git applying
v7 patch attached upthread. I have corrected the same and also ran
pgindent on a new file 'hashfuncs.c' added as a part of this project.
Attached is the updated v8 patch.
0001-Add-support-for-hash-index-in-pageinspect-contrib-mo_v7.patch:28:
trailing whitespace.
brinfuncs.o ginfuncs.o hashfuncs.o $(WIN32RES)
0001-Add-support-for-hash-index-in-pageinspect-contrib-mo_v7.patch:35:
trailing whitespace.
DATA = pageinspect--1.5.sql pageinspect--1.5--1.6.sql \
0001-Add-support-for-hash-index-in-pageinspect-contrib-mo_v7.patch:36:
trailing whitespace.
pageinspect--1.4--1.5.sql pageinspect--1.3--1.4.sql \
0001-Add-support-for-hash-index-in-pageinspect-contrib-mo_v7.patch:37:
trailing whitespace.
pageinspect--1.2--1.3.sql pageinspect--1.1--1.2.sql \
0001-Add-support-for-hash-index-in-pageinspect-contrib-mo_v7.patch:38:
trailing whitespace.
pageinspect--1.0--1.1.sql pageinspect--unpackaged--1.0.sql
pgindent:
-------------
./src/tools/pgindent/pgindent contrib/pageinspect/hashfuncs.c
On Wed, Jan 18, 2017 at 9:15 PM, Jesper Pedersen
<jesper.pedersen@redhat.com> wrote:
Show quoted text
Hi,
On 01/18/2017 04:54 AM, Ashutosh Sharma wrote:
Is there a reason for keeping the input arguments for
hash_bitmap_info() different from hash_page_items()?Yes, there are two reasons behind it.
Firstly, we need metapage to identify the bitmap page that holds the
information about the overflow page passed as an input to this
function.Secondly, we will have to input overflow block number as an input to
this function so as to determine the overflow bit number which can be
used further to identify the bitmap page.+ /* 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];As discussed off-list (along with my patch that included some of these
fixes), it would require calculation from the user to be able to provide the
necessary pages through get_raw_page().Other ideas on how to improve this are most welcome.
Apart from above comments, the attached patch also handles all the
comments from Mithun.Please, include a version number for your patch files in the future.
Fixed in this version:
* verify_hash_page: Display magic in hex, like hash_metapage_info
* Update header for hash_page_typeMoving the patch back to 'Needs Review'.
Best regards,
Jesper
Attachments:
0001-Add-support-for-hash-index-in-pageinspect-contrib-mo_v8.patchinvalid/octet-stream; name=0001-Add-support-for-hash-index-in-pageinspect-contrib-mo_v8.patchDownload
From e857d06ffaa7798fcd6f013be2ce39b3397ba5b4 Mon Sep 17 00:00:00 2001
From: ashu <ashu@localhost.localdomain>
Date: Thu, 19 Jan 2017 16:26:30 +0530
Subject: [PATCH] Add support for hash index in pageinspect contrib module v16
Ashutosh Sharma and Jesper Pedersen.
---
contrib/pageinspect/Makefile | 10 +-
contrib/pageinspect/hashfuncs.c | 579 ++++++++++++++++++++++++++
contrib/pageinspect/pageinspect--1.5--1.6.sql | 76 ++++
contrib/pageinspect/pageinspect.control | 2 +-
doc/src/sgml/pageinspect.sgml | 148 +++++++
src/backend/access/hash/hashovfl.c | 8 +-
src/include/access/hash.h | 1 +
7 files changed, 815 insertions(+), 9 deletions(-)
create mode 100644 contrib/pageinspect/hashfuncs.c
create mode 100644 contrib/pageinspect/pageinspect--1.5--1.6.sql
diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index 87a28e9..d7f3645 100644
--- a/contrib/pageinspect/Makefile
+++ b/contrib/pageinspect/Makefile
@@ -2,13 +2,13 @@
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
diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c
new file mode 100644
index 0000000..6fc9a2e
--- /dev/null
+++ b/contrib/pageinspect/hashfuncs.c
@@ -0,0 +1,579 @@
+/*
+ * 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_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
+{
+ uint32 live_items;
+ uint32 dead_items;
+ uint32 page_size;
+ uint32 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;
+ 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)));
+
+ if (!(pageopaque->hasho_flag & flags))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("page is not a hash page of expected type"),
+ errdetail("Expected hash page type: %08x got: %08x.",
+ flags, pageopaque->hasho_flag)));
+
+ /*
+ * If it is metapage, also verify magic number and version.
+ */
+ if (flags == 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 */
+ if (opaque->hasho_flag & LH_BUCKET_PAGE)
+ stat->hasho_prevblkno = InvalidBlockNumber;
+ else
+ 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, LH_META_PAGE | LH_BUCKET_PAGE |
+ LH_OVERFLOW_PAGE | LH_BITMAP_PAGE);
+ 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++] = UInt32GetDatum(stat.live_items);
+ values[j++] = UInt32GetDatum(stat.dead_items);
+ values[j++] = UInt32GetDatum(stat.page_size);
+ values[j++] = UInt32GetDatum(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];
+ 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;
+ int off;
+ int dlen;
+ char *s;
+ char *ptr;
+ char *dump;
+ int number;
+
+ 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++] = CStringGetTextDatum(psprintf("(%u,%u)",
+ BlockIdGetBlockNumber(&(itup->t_tid.ip_blkid)),
+ itup->t_tid.ip_posid));
+
+ ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
+ Assert(ptr <= uargs->page + BLCKSZ);
+ dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info);
+ dump = palloc(dlen * 2 + 1);
+ s = dump;
+ for (off = (dlen - 1); off >= 0; off--)
+ {
+ sprintf(dump, "%02x", ptr[off] & 0xff);
+ dump += 2;
+ }
+ *dump++ = '\0';
+
+ number = (int) strtol(s, NULL, 16);
+
+ values[j] = Int32GetDatum(number & 0xFFFFFFFF);
+ pfree(s);
+
+ 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);
+ bytea *raw_page;
+ char *raw_page_data;
+ HashMetaPage metap;
+ Buffer buf,
+ metabuf,
+ mapbuf;
+ BlockNumber bitmapblkno;
+ Page mappage;
+ TupleDesc tupleDesc;
+ Relation indexRel;
+ uint32 ovflbitno,
+ bit = 0;
+ int32 bitmappage,
+ bitmapbit;
+ HeapTuple tuple;
+ int j;
+ Datum values[2];
+ bool nulls[2];
+ uint32 *freep = NULL;
+
+ 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))));
+
+ /* Create a raw page */
+ raw_page = (bytea *) palloc(BLCKSZ + VARHDRSZ);
+ SET_VARSIZE(raw_page, BLCKSZ + VARHDRSZ);
+ raw_page_data = VARDATA(raw_page);
+
+ buf = ReadBufferExtended(indexRel, MAIN_FORKNUM, ovflblkno, RBM_NORMAL, NULL);
+ LockBuffer(buf, BUFFER_LOCK_SHARE);
+
+ memcpy(raw_page_data, BufferGetPage(buf), BLCKSZ);
+
+ LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+ ReleaseBuffer(buf);
+
+ verify_hash_page(raw_page, LH_OVERFLOW_PAGE);
+
+ /* 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);
+
+ /* Check the status of bitmap bit for overflow page */
+ mapbuf = _hash_getbuf(indexRel, bitmapblkno, HASH_READ, LH_BITMAP_PAGE);
+ mappage = BufferGetPage(mapbuf);
+ freep = HashPageGetBitmap(mappage);
+
+ if (ISSET(freep, bitmapbit))
+ bit = 1;
+
+ _hash_relbuf(indexRel, mapbuf);
+
+ 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++] = UInt32GetDatum(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];
+ char *spares;
+ char *mapp;
+ char *s;
+
+ 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++] = CStringGetTextDatum(psprintf("0x%08X", 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);
+
+ spares = palloc0(HASH_MAX_SPLITPOINTS * 5 + 1);
+ s = spares;
+ for (i = 0; i < HASH_MAX_SPLITPOINTS; i++)
+ {
+ if (i > 0)
+ *spares++ = ' ';
+
+ sprintf(spares, "%04x", metad->hashm_spares[i]);
+ spares += 4;
+ }
+ values[j++] = CStringGetTextDatum(s);
+ pfree(s);
+
+ mapp = palloc0(HASH_MAX_BITMAPS * 5 + 1);
+ s = mapp;
+ for (i = 0; i < HASH_MAX_BITMAPS; i++)
+ {
+ if (i > 0)
+ *mapp++ = ' ';
+
+ sprintf(mapp, "%04x", metad->hashm_mapp[i]);
+ mapp += 4;
+ }
+ values[j++] = CStringGetTextDatum(s);
+ pfree(s);
+
+ 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 0000000..e159099
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.5--1.6.sql
@@ -0,0 +1,76 @@
+/* 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 int4,
+ OUT dead_items int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT hasho_prevblkno int4,
+ OUT hasho_nextblkno int4,
+ OUT hasho_bucket int4,
+ OUT hasho_flag int4,
+ OUT hasho_page_id int8)
+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 int4,
+ OUT bitmapblkno int4,
+ OUT bitmapbit int4)
+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 text,
+ OUT version int4,
+ OUT ntuples double precision,
+ OUT ffactor int2,
+ OUT bsize int2,
+ OUT bmsize int2,
+ OUT bmshift int2,
+ OUT maxbucket int4,
+ OUT highmask int4,
+ OUT lowmask int4,
+ OUT ovflpoint int4,
+ OUT firstfree int4,
+ OUT nmaps int4,
+ OUT procid int4,
+ OUT spares text,
+ OUT mapp text)
+AS 'MODULE_PATHNAME', 'hash_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control
index 23c8eff..1a61c9f 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/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml
index d12dbac..6022f27 100644
--- a/doc/src/sgml/pageinspect.sgml
+++ b/doc/src/sgml/pageinspect.sgml
@@ -493,4 +493,152 @@ test=# SELECT first_tid, nbytes, tids[0:5] AS some_tids
</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 | -1
+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));
+ itemoffset | ctid | data
+------------+-----------------+------------
+ 1 | (3407872,12584) | 1053474816
+ 2 | (3407872,12584) | 1053474816
+ 3 | (3145728,12584) | 1053474816
+ 4 | (3145728,12584) | 1053474816
+ 5 | (3407872,12584) | 1053474816
+...
+ 131 | (2883584,13352) | 2778469888
+ 132 | (2883584,12840) | 2778469888
+ 133 | (2883584,12328) | 2778469888
+</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', 2050);
+ bitmapblkno | bitmapbit
+-------------+-----------
+ 65 | 1
+</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 | 0x06440640
+version | 2
+ntuples | 686000
+ffactor | 40
+bsize | 8152
+bmsize | 4096
+bmshift | 15
+maxbucket | 17149
+highmask | 32767
+lowmask | 16383
+ovflpoint | 15
+firstfree | 2012
+nmaps | 1
+procid | 450
+spares | 0000 0000 0000 0000 0000 0000 0001 0001 0001 0001 0001 0004 003b 02c0 07c3 07dc 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+mapp | 0041 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </sect2>
+
</sect1>
diff --git a/src/backend/access/hash/hashovfl.c b/src/backend/access/hash/hashovfl.c
index e8928ef..753c8a6 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 69a3873..1dc6c22 100644
--- a/src/include/access/hash.h
+++ b/src/include/access/hash.h
@@ -299,6 +299,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,
--
1.8.3.1
On Wed, Jan 18, 2017 at 3:24 PM, Ashutosh Sharma <ashu.coek88@gmail.com> wrote:
4. +Datum +hash_page_items(PG_FUNCTION_ARGS) +{ + bytea *raw_page = PG_GETARG_BYTEA_P(0);+Datum +hash_bitmap_info(PG_FUNCTION_ARGS) +{ + Oid indexRelid = PG_GETARG_OID(0); + uint32 ovflblkno = PG_GETARG_UINT32(1);Is there a reason for keeping the input arguments for
hash_bitmap_info() different from hash_page_items()?Yes, there are two reasons behind it.
Firstly, we need metapage to identify the bitmap page that holds the
information about the overflow page passed as an input to this
function.
Okay, for that probably index oid is sufficient.
Secondly, we will have to input overflow block number as an input to
this function so as to determine the overflow bit number which can be
used further to identify the bitmap page.
I think you can get that from bucket number by using BUCKET_TO_BLKNO.
You can get bucket number from page's opaque data. So, if we follow
that then you can have a prototype of a function as
hash_bitmap_info(index_oid, page bytea) which will be quite similar to
other API's exposed by this module.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Secondly, we will have to input overflow block number as an input to
this function so as to determine the overflow bit number which can be
used further to identify the bitmap page.I think you can get that from bucket number by using BUCKET_TO_BLKNO.
You can get bucket number from page's opaque data. So, if we follow
that then you can have a prototype of a function as
hash_bitmap_info(index_oid, page bytea) which will be quite similar to
other API's exposed by this module.
AFAIU, BUCKET_TO_BLKNO will give us the block number of a primary
bucket page rather than overflow page and what we want is an overflow
block number so as to determine the overflow bit number which can be
used further to identify the bitmap page. If we pass overflow page as
an input to hash_bitmap_info() we won't be able to get it's block
number. Considering above facts, I think we need to input overflow
block number rather than overflow page to hash_bitmap_info(). Please
let me know your opinion on this. Thanks.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Jan 24, 2017 at 11:41 AM, Ashutosh Sharma <ashu.coek88@gmail.com> wrote:
Secondly, we will have to input overflow block number as an input to
this function so as to determine the overflow bit number which can be
used further to identify the bitmap page.I think you can get that from bucket number by using BUCKET_TO_BLKNO.
You can get bucket number from page's opaque data. So, if we follow
that then you can have a prototype of a function as
hash_bitmap_info(index_oid, page bytea) which will be quite similar to
other API's exposed by this module.AFAIU, BUCKET_TO_BLKNO will give us the block number of a primary
bucket page rather than overflow page and what we want is an overflow
block number so as to determine the overflow bit number which can be
used further to identify the bitmap page.
Right.
If we pass overflow page as
an input to hash_bitmap_info() we won't be able to get it's block
number.
I think we can get it by using its prev block number, but not sure if
it is worth the complexity, so let's keep the interface as proposed by
you. TBH, I am not sure how important is to expose this
(hash_bitmap_info()) function, but let's keep it and see if committer
has any opinion on this API.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Jan 19, 2017 at 5:08 PM, Ashutosh Sharma <ashu.coek88@gmail.com> wrote:
Thanks, Ashutosh and Jesper. I have tested the patch I do not have any
more comments so making it ready for committer.
--
Thanks and Regards
Mithun C Y
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Jan 24, 2017 at 9:59 PM, Mithun Cy <mithun.cy@enterprisedb.com> wrote:
On Thu, Jan 19, 2017 at 5:08 PM, Ashutosh Sharma <ashu.coek88@gmail.com> wrote:
Thanks, Ashutosh and Jesper. I have tested the patch I do not have any
more comments so making it ready for committer.
I took a look at this patch. I think hash_page_items() is buggy.
It's a little confused about whether it is building an array of datums
(which can be assembled into a tuple using heap_form_tuple) or an
array of cstrings (which can be assembled into a tuple using
BuildTupleFromCStrings). It's mostly the former: the array is of type
Datum, and two of the three values put into it are datums. But the
code that puts the TID in there is stolen from bt_page_items(), which
is constructing an array of cstrings, so it's wrong here. The
practical consequence of this is, I believe, that the TIDs output by
this function will be totally garbled and wrong, which is possibly why
the example in the documentation looks so wacky:
+ 1 | (3407872,12584) | 1053474816
+ 2 | (3407872,12584) | 1053474816
The heap tuple is on page 3407872 at line pointer offset 12584?
That's an awfully but not implausibly big page number (about 26GB into
the relation) and an awfully and implausibly large line pointer offset
(how do we fit 12584 index tuples into an 8192-byte page?). Also, the
fact that this example has two index entries with the same hash code
pointing at the same heap TID seems wrong; wouldn't that result in
index scans returning duplicates? I think what's actually happening
here is that (3407872,12584) is actually the attempt to interpret some
chunk of memory containing the text representation of a TID as an
actual TID. When I print those numbers as hex, I get 0x343128, and
those are the digits "4" and "1" and an opening parenthesis ")" as
ASCII, so that might fit this theory.
The code that tries to extract the hashcode from the item also looks
suspicious. I'm not 100% sure whether it's wrong or just
strangely-written, but it doesn't make much sense to extract the item
contents, then using sprintf() to turn that into a hex string of
bytes, then parse the hex string using strtol() to get an integer
back. I think what it ought to be doing is getting a pointer to the
index tuple and then calling _hash_get_indextuple_hashkey.
Another minor complaint about hash_page_items is that it doesn't
pfree(uargs->page). I'm not sure it's necessary to pfree anything
here, but if we're going to do it I think we might as well be
consistent with the btree case. Anyway it can hardly make sense to
free the 8-byte structure and not the 8192-byte page to which it's
pointing.
In hash_bimap_info(), we go to the trouble of creating a raw page but
never do anything with it. I guess the idea here is just to error out
if the supplied page number is not an overflow page, but there are no
comments explaining that. Anyway, I'm not sure it's a very good idea,
because it means that if you try to write a query to interrogate the
status of all the bitmap pages, it's going to read all of the overflow
pages to which they point, which makes the operation a LOT more
expensive. I think it would be better to leave this part out; if the
user wants to know which pages are actually overflow pages, they can
use hash_page_type() to figure it out. Let's make it the job of this
function just to check the available/free status of the page in the
bitmap, without reading the bitmap itself.
In doing that, I think it should either return (bitmapblkno,
bitmapbit, bit) or just bit. Returning bitmapblkno but not bitmapbit
seems strange. Also, I think it should return bit as a bool, not
int4.
Another general note is that, in general, if you use the
BlahGetDatum() function to construct a datum, the SQL type should be
match the macro you picked - e.g. if the SQL type is int4, the macro
used should be Int32GetDatum(), not UInt32GetDatum() or anything else.
If the SQL type is bool, you should be using BoolGetDatum().
Apart from the above, I did a little work to improve the reporting
when a page of the wrong type is verified. Updated patch with those
changes attached.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Attachments:
pageinspect-hashindex-rmh.patchapplication/octet-stream; name=pageinspect-hashindex-rmh.patchDownload
diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index 87a28e9..d7f3645 100644
--- a/contrib/pageinspect/Makefile
+++ b/contrib/pageinspect/Makefile
@@ -2,13 +2,13 @@
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
diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c
new file mode 100644
index 0000000..dc51d20
--- /dev/null
+++ b/contrib/pageinspect/hashfuncs.c
@@ -0,0 +1,604 @@
+/*
+ * 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_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
+{
+ uint32 live_items;
+ uint32 dead_items;
+ uint32 page_size;
+ uint32 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 */
+ if (opaque->hasho_flag & LH_BUCKET_PAGE)
+ stat->hasho_prevblkno = InvalidBlockNumber;
+ else
+ 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++] = UInt32GetDatum(stat.live_items);
+ values[j++] = UInt32GetDatum(stat.dead_items);
+ values[j++] = UInt32GetDatum(stat.page_size);
+ values[j++] = UInt32GetDatum(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];
+ 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;
+ int off;
+ int dlen;
+ char *s;
+ char *ptr;
+ char *dump;
+ int number;
+
+ 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++] = CStringGetTextDatum(psprintf("(%u,%u)",
+ BlockIdGetBlockNumber(&(itup->t_tid.ip_blkid)),
+ itup->t_tid.ip_posid));
+
+ ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
+ Assert(ptr <= uargs->page + BLCKSZ);
+ dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info);
+ dump = palloc(dlen * 2 + 1);
+ s = dump;
+ for (off = (dlen - 1); off >= 0; off--)
+ {
+ sprintf(dump, "%02x", ptr[off] & 0xff);
+ dump += 2;
+ }
+ *dump++ = '\0';
+
+ number = (int) strtol(s, NULL, 16);
+
+ values[j] = Int32GetDatum(number & 0xFFFFFFFF);
+ pfree(s);
+
+ 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);
+ bytea *raw_page;
+ char *raw_page_data;
+ HashMetaPage metap;
+ Buffer buf,
+ metabuf,
+ mapbuf;
+ BlockNumber bitmapblkno;
+ Page mappage;
+ TupleDesc tupleDesc;
+ Relation indexRel;
+ uint32 ovflbitno,
+ bit = 0;
+ int32 bitmappage,
+ bitmapbit;
+ HeapTuple tuple;
+ int j;
+ Datum values[2];
+ bool nulls[2];
+ uint32 *freep = NULL;
+
+ 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))));
+
+ /* Create a raw page */
+ raw_page = (bytea *) palloc(BLCKSZ + VARHDRSZ);
+ SET_VARSIZE(raw_page, BLCKSZ + VARHDRSZ);
+ raw_page_data = VARDATA(raw_page);
+
+ buf = ReadBufferExtended(indexRel, MAIN_FORKNUM, ovflblkno, RBM_NORMAL, NULL);
+ LockBuffer(buf, BUFFER_LOCK_SHARE);
+
+ memcpy(raw_page_data, BufferGetPage(buf), BLCKSZ);
+
+ LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+ ReleaseBuffer(buf);
+
+ verify_hash_page(raw_page, LH_OVERFLOW_PAGE);
+
+ /* 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);
+
+ /* Check the status of bitmap bit for overflow page */
+ mapbuf = _hash_getbuf(indexRel, bitmapblkno, HASH_READ, LH_BITMAP_PAGE);
+ mappage = BufferGetPage(mapbuf);
+ freep = HashPageGetBitmap(mappage);
+
+ if (ISSET(freep, bitmapbit))
+ bit = 1;
+
+ _hash_relbuf(indexRel, mapbuf);
+
+ 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++] = UInt32GetDatum(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];
+ char *spares;
+ char *mapp;
+ char *s;
+
+ 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++] = CStringGetTextDatum(psprintf("0x%08x", 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);
+
+ spares = palloc0(HASH_MAX_SPLITPOINTS * 5 + 1);
+ s = spares;
+ for (i = 0; i < HASH_MAX_SPLITPOINTS; i++)
+ {
+ if (i > 0)
+ *spares++ = ' ';
+
+ sprintf(spares, "%04x", metad->hashm_spares[i]);
+ spares += 4;
+ }
+ values[j++] = CStringGetTextDatum(s);
+ pfree(s);
+
+ mapp = palloc0(HASH_MAX_BITMAPS * 5 + 1);
+ s = mapp;
+ for (i = 0; i < HASH_MAX_BITMAPS; i++)
+ {
+ if (i > 0)
+ *mapp++ = ' ';
+
+ sprintf(mapp, "%04x", metad->hashm_mapp[i]);
+ mapp += 4;
+ }
+ values[j++] = CStringGetTextDatum(s);
+ pfree(s);
+
+ 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 0000000..e159099
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.5--1.6.sql
@@ -0,0 +1,76 @@
+/* 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 int4,
+ OUT dead_items int4,
+ OUT page_size int4,
+ OUT free_size int4,
+ OUT hasho_prevblkno int4,
+ OUT hasho_nextblkno int4,
+ OUT hasho_bucket int4,
+ OUT hasho_flag int4,
+ OUT hasho_page_id int8)
+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 int4,
+ OUT bitmapblkno int4,
+ OUT bitmapbit int4)
+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 text,
+ OUT version int4,
+ OUT ntuples double precision,
+ OUT ffactor int2,
+ OUT bsize int2,
+ OUT bmsize int2,
+ OUT bmshift int2,
+ OUT maxbucket int4,
+ OUT highmask int4,
+ OUT lowmask int4,
+ OUT ovflpoint int4,
+ OUT firstfree int4,
+ OUT nmaps int4,
+ OUT procid int4,
+ OUT spares text,
+ OUT mapp text)
+AS 'MODULE_PATHNAME', 'hash_metapage_info'
+LANGUAGE C STRICT PARALLEL SAFE;
diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control
index 23c8eff..1a61c9f 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/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml
index d12dbac..6022f27 100644
--- a/doc/src/sgml/pageinspect.sgml
+++ b/doc/src/sgml/pageinspect.sgml
@@ -493,4 +493,152 @@ test=# SELECT first_tid, nbytes, tids[0:5] AS some_tids
</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 | -1
+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));
+ itemoffset | ctid | data
+------------+-----------------+------------
+ 1 | (3407872,12584) | 1053474816
+ 2 | (3407872,12584) | 1053474816
+ 3 | (3145728,12584) | 1053474816
+ 4 | (3145728,12584) | 1053474816
+ 5 | (3407872,12584) | 1053474816
+...
+ 131 | (2883584,13352) | 2778469888
+ 132 | (2883584,12840) | 2778469888
+ 133 | (2883584,12328) | 2778469888
+</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', 2050);
+ bitmapblkno | bitmapbit
+-------------+-----------
+ 65 | 1
+</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 | 0x06440640
+version | 2
+ntuples | 686000
+ffactor | 40
+bsize | 8152
+bmsize | 4096
+bmshift | 15
+maxbucket | 17149
+highmask | 32767
+lowmask | 16383
+ovflpoint | 15
+firstfree | 2012
+nmaps | 1
+procid | 450
+spares | 0000 0000 0000 0000 0000 0000 0001 0001 0001 0001 0001 0004 003b 02c0 07c3 07dc 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+mapp | 0041 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </sect2>
+
</sect1>
diff --git a/src/backend/access/hash/hashovfl.c b/src/backend/access/hash/hashovfl.c
index e8928ef..753c8a6 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 69a3873..1a9b91f 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,
On 1/18/17 10:45 AM, Jesper Pedersen wrote:
Fixed in this version:
* verify_hash_page: Display magic in hex, like hash_metapage_info
* Update header for hash_page_typeMoving the patch back to 'Needs Review'.
Please include tests in your patch. I have posted a sample test suite
in
</messages/by-id/bcf8c21b-702e-20a7-a4b0-236ed2363d84@2ndquadrant.com>,
which you could use.
Also, as mentioned before, hash_metapage_info result fields spares and
mapp should be arrays of integer, not a text string.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi,
Please find my reply inline.
In hash_bimap_info(), we go to the trouble of creating a raw page but
never do anything with it. I guess the idea here is just to error out
if the supplied page number is not an overflow page, but there are no
comments explaining that. Anyway, I'm not sure it's a very good idea,
because it means that if you try to write a query to interrogate the
status of all the bitmap pages, it's going to read all of the overflow
pages to which they point, which makes the operation a LOT more
expensive. I think it would be better to leave this part out; if the
user wants to know which pages are actually overflow pages, they can
use hash_page_type() to figure it out.
Yes, the intention was to ensure that user only passes overflow page
as an input to this function. I think if we wan't to avoid creating a
raw page then we may need to find some other way to verify if it is an
overflow page or not, may be we can make use of hash_check_page().
Let's make it the job of this
function just to check the available/free status of the page in the
bitmap, without reading the bitmap itself.
okay, In that case I think we can check the previous block number that
the overflow page is pointing to in order to identify if it is free or
in-use. AFAIU, if an overflow page is free it's prev block number will
be Invalid. This way, we may not have to read bitmap page. Now the
question here is, we also have bucket pages where previous block
number is always Invalid but before checking this we ensure that we
are only dealing with an overflow page.Please let me know if you feel
we do have some better option than this to identify the status of
overflow page without reading bitmap.
In doing that, I think it should either return (bitmapblkno,
bitmapbit, bit) or just bit. Returning bitmapblkno but not bitmapbit
seems strange. Also, I think it should return bit as a bool, not
int4.
It would be good to return bitmap bit number as well along with the
bitmap block number. Also, returning bit as bool would look good. I
will do that.
I am also working on other review comments and will share the updated
patch asap. Thanks.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
The heap tuple is on page 3407872 at line pointer offset 12584?
That's an awfully but not implausibly big page number (about 26GB into
the relation) and an awfully and implausibly large line pointer offset
(how do we fit 12584 index tuples into an 8192-byte page?). Also, the
fact that this example has two index entries with the same hash code
pointing at the same heap TID seems wrong; wouldn't that result in
index scans returning duplicates? I think what's actually happening
here is that (3407872,12584) is actually the attempt to interpret some
chunk of memory containing the text representation of a TID as an
actual TID. When I print those numbers as hex, I get 0x343128, and
those are the digits "4" and "1" and an opening parenthesis ")" as
ASCII, so that might fit this theory.
I too had a similar observations when debugging hash_page_items() and
I think we were trying to represent tid as a text rather than tid
itself which was not correct. Attched patch fixes this bug. Below is
what i observed and it is somewhat similar to your explanation in the
above comment:
(gdb) p PageGetItemId(uargs->page, uargs->offset)
$2 = (ItemIdData *) 0x1d84754
(gdb) p *(ItemIdData *) 0x1d84754
$3 = {lp_off = 1664, lp_flags = 1, lp_len = 16}
(gdb) p *(IndexTuple) (page + 1664)
$4 = {t_tid = {ip_blkid = {bi_hi = 0, bi_lo = 839}, ip_posid = 137},
t_info = 16}
(gdb) p BlockIdGetBlockNumber(&(itup->t_tid.ip_blkid))
$5 = 839
(gdb) p (itup->t_tid.ip_posid)
$6 = 137
(gdb) p CStringGetTextDatum(psprintf("(%u,%u)", 839, 137))
$7 = 30959896
(gdb) p DatumGetCString(30959896)
$8 = 0x1d86918 "4"
(gdb) p (char *)0x1d86918
$23 = 0x1d86918 "4"
The code that tries to extract the hashcode from the item also looks
suspicious. I'm not 100% sure whether it's wrong or just
strangely-written, but it doesn't make much sense to extract the item
contents, then using sprintf() to turn that into a hex string of
bytes, then parse the hex string using strtol() to get an integer
back. I think what it ought to be doing is getting a pointer to the
index tuple and then calling _hash_get_indextuple_hashkey.
I think there is nothing wrong with the hashcode being shown but i do
agree that it is a bit lengthly method to find a hashcode considering
that we already have a extern function _hash_get_indextuple_hashkey()
that can be used to find the hashcode. I have corrected this in the
attached patch.
Another minor complaint about hash_page_items is that it doesn't
pfree(uargs->page). I'm not sure it's necessary to pfree anything
here, but if we're going to do it I think we might as well be
consistent with the btree case. Anyway it can hardly make sense to
free the 8-byte structure and not the 8192-byte page to which it's
pointing.
In case of btree, we are copying entire page into uargs->page.
<btreefuncs.c>
memcpy(uargs->page, BufferGetPage(buffer), BLCKSZ);
</btrrefuncs.c>
But in our case we have a raw_page and uargs->page contains pointer to
the page so no need to pfree uargs->page separately.
In hash_bimap_info(), we go to the trouble of creating a raw page but
never do anything with it. I guess the idea here is just to error out
if the supplied page number is not an overflow page, but there are no
comments explaining that. Anyway, I'm not sure it's a very good idea,
because it means that if you try to write a query to interrogate the
status of all the bitmap pages, it's going to read all of the overflow
pages to which they point, which makes the operation a LOT more
expensive. I think it would be better to leave this part out; if the
user wants to know which pages are actually overflow pages, they can
use hash_page_type() to figure it out. Let's make it the job of this
function just to check the available/free status of the page in the
bitmap, without reading the bitmap itself.
Point taken. I am now checking the status of an overflow page without
reading bitmap page.
In doing that, I think it should either return (bitmapblkno,
bitmapbit, bit) or just bit. Returning bitmapblkno but not bitmapbit
seems strange. Also, I think it should return bit as a bool, not
int4.
The new bitmap_info() now returns bitmapblkno, bitmapbit and bitstatus as bool.
Another general note is that, in general, if you use the
BlahGetDatum() function to construct a datum, the SQL type should be
match the macro you picked - e.g. if the SQL type is int4, the macro
used should be Int32GetDatum(), not UInt32GetDatum() or anything else.
If the SQL type is bool, you should be using BoolGetDatum().
Sorry to mention but I didn't find any SQL datatype equivalent to
uint32 or uint16 in 'C'. So, currently i am using int4 for uint16 and
int8 for uint32.
Apart from the above, I did a little work to improve the reporting
when a page of the wrong type is verified. Updated patch with those
changes attached.
okay. Thanks. I have done changes on top of this patch.
--
With Regards,
Ashutosh Sharma
EnterpriseDB:http://www.enterprisedb.com
Attachments:
0001-Add-support-for-hash-index-in-pageinspect-contrib-mo_v9.patchinvalid/octet-stream; name=0001-Add-support-for-hash-index-in-pageinspect-contrib-mo_v9.patchDownload
From d260c2ffcec501f96f198a9a46244939dbe26920 Mon Sep 17 00:00:00 2001
From: ashu <ashu@localhost.localdomain>
Date: Sun, 29 Jan 2017 06:39:51 +0530
Subject: [PATCH] Add support for hash index in pageinspect contrib module.
Ashutosh Sharma and Jesper Pedersen.
---
contrib/pageinspect/Makefile | 12 +-
contrib/pageinspect/expected/hash.out | 150 +++++++
contrib/pageinspect/hashfuncs.c | 566 ++++++++++++++++++++++++++
contrib/pageinspect/pageinspect--1.5--1.6.sql | 77 ++++
contrib/pageinspect/pageinspect.control | 2 +-
contrib/pageinspect/sql/hash.sql | 49 +++
doc/src/sgml/pageinspect.sgml | 144 +++++++
src/backend/access/hash/hashovfl.c | 8 +-
src/include/access/hash.h | 4 +
9 files changed, 1002 insertions(+), 10 deletions(-)
create mode 100644 contrib/pageinspect/expected/hash.out
create mode 100644 contrib/pageinspect/hashfuncs.c
create mode 100644 contrib/pageinspect/pageinspect--1.5--1.6.sql
create mode 100644 contrib/pageinspect/sql/hash.sql
diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index 87a28e9..0a3cbee 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 0000000..3abc887
--- /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 0000000..ff838d6
--- /dev/null
+++ b/contrib/pageinspect/hashfuncs.c
@@ -0,0 +1,566 @@
+/*
+ * 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 */
+ if (opaque->hasho_flag & LH_BUCKET_PAGE)
+ stat->hasho_prevblkno = InvalidBlockNumber;
+ else
+ 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;
+ char *ptr;
+
+ 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);
+
+ ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
+ Assert(ptr <= uargs->page + BLCKSZ);
+
+ 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 0000000..d0355b4
--- /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 23c8eff..1a61c9f 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 0000000..9e7635e
--- /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 d12dbac..4c201e7 100644
--- a/doc/src/sgml/pageinspect.sgml
+++ b/doc/src/sgml/pageinspect.sgml
@@ -493,4 +493,148 @@ test=# SELECT first_tid, nbytes, tids[0:5] AS some_tids
</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>
+ </varlistentry>
+ </variablelist>
+ </sect2>
+
</sect1>
diff --git a/src/backend/access/hash/hashovfl.c b/src/backend/access/hash/hashovfl.c
index e8928ef..753c8a6 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 69a3873..1a9b91f 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,
--
1.8.3.1
Hi,
Please include tests in your patch. I have posted a sample test suite
in
</messages/by-id/bcf8c21b-702e-20a7-a4b0-236ed2363d84@2ndquadrant.com>,
which you could use.Also, as mentioned before, hash_metapage_info result fields spares and
mapp should be arrays of integer, not a text string.
I have added the test-case in the v9 patch shared upthread and also
corrected datatypes for spares and mapp fields in a metapage. Now
these two fields are being shown as an array of integers rather than
text.
/messages/by-id/CAE9k0Pke046HKYfuJGcCtP77NyHrun7hBV-v20a0TW4CUg4H+A@mail.gmail.com
--
With Regards,
Ashutosh Sharma
EnterpriseDB:http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sun, Jan 29, 2017 at 11:09 AM, Ashutosh Sharma <ashu.coek88@gmail.com> wrote:
okay. Thanks. I have done changes on top of this patch.
Moved to CF 2017-03 as there is a new patch, no reviews.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sat, Jan 28, 2017 at 9:09 PM, Ashutosh Sharma <ashu.coek88@gmail.com> wrote:
okay. Thanks. I have done changes on top of this patch.
+ ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
+ Assert(ptr <= uargs->page + BLCKSZ);
I think this should be promoted to an ereport(); these functions can
accept an arbitrary bytea.
+ if (opaque->hasho_flag & LH_BUCKET_PAGE)
+ stat->hasho_prevblkno = InvalidBlockNumber;
+ else
+ stat->hasho_prevblkno = opaque->hasho_prevblkno;
I think we should return the raw value here. Mithun's patch to cache
the metapage hasn't gone in yet, but even if it does, let's assume
anyone using contrib/pageinspect wants to see the data that's
physically present, not our gloss on it.
Other than that, I don't think I have any other comments on this. The
tests that got added look a little verbose to me (do we really need to
check pages 1-4 separately in each case when they're all hash pages?
if hash_bitmap_info rejects one, surely it will reject the others) but
I'm not going to fight too hard if Peter wants it that way.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
+ ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info); + Assert(ptr <= uargs->page + BLCKSZ);I think this should be promoted to an ereport(); these functions can
accept an arbitrary bytea.
I think we had added 'ptr' variable initially just to dump hash code
in hexadecimal format but now since we have removed that logic from
current code, I think it is no more required and I have therefore
removed it from the current patch. Below is the code that used it
initially. It got changed as per your comment - [1]/messages/by-id/CA+TgmoZUjrVy52TUU3b_Q5L6jcrt7w5R4qFwMXdeCuKQBmYWqQ@mail.gmail.com
+ if (opaque->hasho_flag & LH_BUCKET_PAGE) + stat->hasho_prevblkno = InvalidBlockNumber; + else + stat->hasho_prevblkno = opaque->hasho_prevblkno;I think we should return the raw value here. Mithun's patch to cache
the metapage hasn't gone in yet, but even if it does, let's assume
anyone using contrib/pageinspect wants to see the data that's
physically present, not our gloss on it.
okay, agreed. I have corrected this in the attached v10 patch.
Other than that, I don't think I have any other comments on this. The
tests that got added look a little verbose to me (do we really need to
check pages 1-4 separately in each case when they're all hash pages?
if hash_bitmap_info rejects one, surely it will reject the others) but
I'm not going to fight too hard if Peter wants it that way.
I think it's OK to check hash_bitmap_info() or any other functions
with different page types at least once.
[1]: /messages/by-id/CA+TgmoZUjrVy52TUU3b_Q5L6jcrt7w5R4qFwMXdeCuKQBmYWqQ@mail.gmail.com
--
With Regards,
Ashutosh Sharma
EnterpriseDB:http://www.enterprisedb.com
Attachments:
0001-Add-support-for-hash-index-in-pageinspect-contrib-mo_v10.patchinvalid/octet-stream; name=0001-Add-support-for-hash-index-in-pageinspect-contrib-mo_v10.patchDownload
From d3e289b5db70dd3a78ac67a9d61291de8850312c Mon Sep 17 00:00:00 2001
From: ashu <ashu@localhost.localdomain>
Date: Thu, 2 Feb 2017 16:36:24 +0530
Subject: [PATCH] Add support for hash index in pageinspect contrib module.
Authors: Ashutosh Sharma and Jesper Pedersen.
---
contrib/pageinspect/Makefile | 12 +-
contrib/pageinspect/expected/hash.out | 150 +++++++
contrib/pageinspect/hashfuncs.c | 559 ++++++++++++++++++++++++++
contrib/pageinspect/pageinspect--1.5--1.6.sql | 77 ++++
contrib/pageinspect/pageinspect.control | 2 +-
contrib/pageinspect/sql/hash.sql | 49 +++
doc/src/sgml/pageinspect.sgml | 144 +++++++
src/backend/access/hash/hashovfl.c | 8 +-
src/include/access/hash.h | 4 +
9 files changed, 995 insertions(+), 10 deletions(-)
create mode 100644 contrib/pageinspect/expected/hash.out
create mode 100644 contrib/pageinspect/hashfuncs.c
create mode 100644 contrib/pageinspect/pageinspect--1.5--1.6.sql
create mode 100644 contrib/pageinspect/sql/hash.sql
diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index 87a28e9..0a3cbee 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 0000000..3abc887
--- /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 0000000..5812afe
--- /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 0000000..d0355b4
--- /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 23c8eff..1a61c9f 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 0000000..9e7635e
--- /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 d12dbac..4c201e7 100644
--- a/doc/src/sgml/pageinspect.sgml
+++ b/doc/src/sgml/pageinspect.sgml
@@ -493,4 +493,148 @@ test=# SELECT first_tid, nbytes, tids[0:5] AS some_tids
</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>
+ </varlistentry>
+ </variablelist>
+ </sect2>
+
</sect1>
diff --git a/src/backend/access/hash/hashovfl.c b/src/backend/access/hash/hashovfl.c
index e8928ef..753c8a6 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 69a3873..1a9b91f 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,
--
1.8.3.1
On Thu, Feb 2, 2017 at 6:29 AM, Ashutosh Sharma <ashu.coek88@gmail.com> wrote:
+ ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info); + Assert(ptr <= uargs->page + BLCKSZ);I think this should be promoted to an ereport(); these functions can
accept an arbitrary bytea.I think we had added 'ptr' variable initially just to dump hash code
in hexadecimal format but now since we have removed that logic from
current code, I think it is no more required and I have therefore
removed it from the current patch. Below is the code that used it
initially. It got changed as per your comment - [1]
OK.
I think it's OK to check hash_bitmap_info() or any other functions
with different page types at least once.[1]- /messages/by-id/CA+TgmoZUjrVy52TUU3b_Q5L6jcrt7w5R4qFwMXdeCuKQBmYWqQ@mail.gmail.com
Sure, I just don't know if we need to test them 4 or 5 times. But I
think this is good enough for now - it's not like this code is written
in stone once committed.
So, committed. Wow, I wish every patch had this many reviewers.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 02/02/2017 02:24 PM, Robert Haas wrote:
So, committed. Wow, I wish every patch had this many reviewers.
Thanks Robert !
Best regards,
Jesper
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Feb 3, 2017 at 4:28 AM, Jesper Pedersen
<jesper.pedersen@redhat.com> wrote:
On 02/02/2017 02:24 PM, Robert Haas wrote:
So, committed. Wow, I wish every patch had this many reviewers.
Thanks Robert !
9 people in total per the commit message. Yes that's rare, and thanks
for doing the effort to list everybody.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
I think it's OK to check hash_bitmap_info() or any other functions
with different page types at least once.[1]- /messages/by-id/CA+TgmoZUjrVy52TUU3b_Q5L6jcrt7w5R4qFwMXdeCuKQBmYWqQ@mail.gmail.com
Sure, I just don't know if we need to test them 4 or 5 times. But I
think this is good enough for now - it's not like this code is written
in stone once committed.So, committed. Wow, I wish every patch had this many reviewers.
Thanks Robert and Thank you to all the experts for showing their
interest towards this project.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 02/02/2017 02:28 PM, Jesper Pedersen wrote:
On 02/02/2017 02:24 PM, Robert Haas wrote:
So, committed. Wow, I wish every patch had this many reviewers.
Thanks Robert !
This message should have included a thank you to everybody who provided
valuable feedback for this feature, and for that I'm sorry.
Best regards,
Jesper
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
BTW, the buildfarm is still crashing on ia64 and sparc64 members.
I believe this is the same problem addressed in 84ad68d64 for
pageinspect's GIN functions, to wit, the payload of a "bytea"
is not maxaligned, so machines that care about alignment won't be
happy when trying to fetch 64-bit values out of a bytea page image.
Clearly, the fix should be to start using the get_page_from_raw()
function that Peter introduced in that patch. But it's static in
ginfuncs.c, which I thought was a mistake at the time, and it's
proven so now.
contrib/pageinspect actually seems to lack *any* infrastructure
for sharing functions across modules. It's time to remedy that.
I propose inventing "pageinspect.h" to hold commonly visible
declarations, and moving get_page_from_raw() into rawpage.c,
which seems like a reasonably natural home for it. (Alternatively,
we could invent a pageinspect.c file to hold utility functions,
but that might be overkill.)
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Feb 3, 2017 at 10:11 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
BTW, the buildfarm is still crashing on ia64 and sparc64 members.
I believe this is the same problem addressed in 84ad68d64 for
pageinspect's GIN functions, to wit, the payload of a "bytea"
is not maxaligned, so machines that care about alignment won't be
happy when trying to fetch 64-bit values out of a bytea page image.Clearly, the fix should be to start using the get_page_from_raw()
function that Peter introduced in that patch. But it's static in
ginfuncs.c, which I thought was a mistake at the time, and it's
proven so now.contrib/pageinspect actually seems to lack *any* infrastructure
for sharing functions across modules. It's time to remedy that.
I propose inventing "pageinspect.h" to hold commonly visible
declarations, and moving get_page_from_raw() into rawpage.c,
which seems like a reasonably natural home for it. (Alternatively,
we could invent a pageinspect.c file to hold utility functions,
but that might be overkill.)
No objections.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi,
On 02/03/2017 11:36 AM, Robert Haas wrote:
On Fri, Feb 3, 2017 at 10:11 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
BTW, the buildfarm is still crashing on ia64 and sparc64 members.
I believe this is the same problem addressed in 84ad68d64 for
pageinspect's GIN functions, to wit, the payload of a "bytea"
is not maxaligned, so machines that care about alignment won't be
happy when trying to fetch 64-bit values out of a bytea page image.Clearly, the fix should be to start using the get_page_from_raw()
function that Peter introduced in that patch. But it's static in
ginfuncs.c, which I thought was a mistake at the time, and it's
proven so now.contrib/pageinspect actually seems to lack *any* infrastructure
for sharing functions across modules. It's time to remedy that.
I propose inventing "pageinspect.h" to hold commonly visible
declarations, and moving get_page_from_raw() into rawpage.c,
which seems like a reasonably natural home for it. (Alternatively,
we could invent a pageinspect.c file to hold utility functions,
but that might be overkill.)No objections.
Attached is v1 of this w/ verify_hash_page() using get_page_from_raw().
Sorry for all the problems.
Best regards,
Jesper
Attachments:
0001-Add-pageinspect.h_v1.patchtext/x-patch; name=0001-Add-pageinspect.h_v1.patchDownload
From d674c58098580ec59f9e1e47255c68b8497178b1 Mon Sep 17 00:00:00 2001
From: jesperpedersen <jesper.pedersen@redhat.com>
Date: Fri, 3 Feb 2017 11:28:19 -0500
Subject: [PATCH] Add pageinspect.h
---
contrib/pageinspect/brinfuncs.c | 1 +
contrib/pageinspect/btreefuncs.c | 1 +
contrib/pageinspect/fsmfuncs.c | 1 +
contrib/pageinspect/ginfuncs.c | 21 +-------------
contrib/pageinspect/hashfuncs.c | 3 +-
contrib/pageinspect/pageinspect.h | 61 +++++++++++++++++++++++++++++++++++++++
contrib/pageinspect/rawpage.c | 25 ++++++++++++++++
7 files changed, 92 insertions(+), 21 deletions(-)
create mode 100644 contrib/pageinspect/pageinspect.h
diff --git a/contrib/pageinspect/brinfuncs.c b/contrib/pageinspect/brinfuncs.c
index 7b877e3..23136d2 100644
--- a/contrib/pageinspect/brinfuncs.c
+++ b/contrib/pageinspect/brinfuncs.c
@@ -9,6 +9,7 @@
*/
#include "postgres.h"
+#include "pageinspect.h"
#include "access/htup_details.h"
#include "access/brin.h"
#include "access/brin_internal.h"
diff --git a/contrib/pageinspect/btreefuncs.c b/contrib/pageinspect/btreefuncs.c
index 3f09d5f..e63b5fb 100644
--- a/contrib/pageinspect/btreefuncs.c
+++ b/contrib/pageinspect/btreefuncs.c
@@ -27,6 +27,7 @@
#include "postgres.h"
+#include "pageinspect.h"
#include "access/nbtree.h"
#include "catalog/namespace.h"
#include "catalog/pg_am.h"
diff --git a/contrib/pageinspect/fsmfuncs.c b/contrib/pageinspect/fsmfuncs.c
index 6541646..372de39 100644
--- a/contrib/pageinspect/fsmfuncs.c
+++ b/contrib/pageinspect/fsmfuncs.c
@@ -19,6 +19,7 @@
#include "postgres.h"
+#include "pageinspect.h"
#include "funcapi.h"
#include "lib/stringinfo.h"
#include "miscadmin.h"
diff --git a/contrib/pageinspect/ginfuncs.c b/contrib/pageinspect/ginfuncs.c
index cea77d3..5cfcbcb 100644
--- a/contrib/pageinspect/ginfuncs.c
+++ b/contrib/pageinspect/ginfuncs.c
@@ -9,6 +9,7 @@
*/
#include "postgres.h"
+#include "pageinspect.h"
#include "access/gin.h"
#include "access/gin_private.h"
#include "access/htup_details.h"
@@ -29,26 +30,6 @@ PG_FUNCTION_INFO_V1(gin_page_opaque_info);
PG_FUNCTION_INFO_V1(gin_leafpage_items);
-static Page
-get_page_from_raw(bytea *raw_page)
-{
- int raw_page_size;
- Page page;
-
- raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
- if (raw_page_size < BLCKSZ)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("input page too small (%d bytes)", raw_page_size)));
-
- /* make a copy so that the page is properly aligned for struct access */
- page = palloc(raw_page_size);
- memcpy(page, VARDATA(raw_page), raw_page_size);
-
- return page;
-}
-
-
Datum
gin_metapage_info(PG_FUNCTION_ARGS)
{
diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c
index 83b4698..09bb9ee 100644
--- a/contrib/pageinspect/hashfuncs.c
+++ b/contrib/pageinspect/hashfuncs.c
@@ -10,6 +10,7 @@
#include "postgres.h"
+#include "pageinspect.h"
#include "access/hash.h"
#include "access/htup_details.h"
#include "catalog/pg_type.h"
@@ -67,7 +68,7 @@ verify_hash_page(bytea *raw_page, int flags)
errdetail("Expected size %d, got %d",
BLCKSZ, raw_page_size)));
- page = VARDATA(raw_page);
+ page = get_page_from_raw(raw_page);
if (PageIsNew(page))
ereport(ERROR,
diff --git a/contrib/pageinspect/pageinspect.h b/contrib/pageinspect/pageinspect.h
new file mode 100644
index 0000000..9ec3aad
--- /dev/null
+++ b/contrib/pageinspect/pageinspect.h
@@ -0,0 +1,61 @@
+/*
+ * pageinspect.h
+ * Functions to investigate the content of indexes
+ *
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * contrib/pageinspect/pageinspect.h
+ */
+#ifndef PAGEINSPECT_H
+#define PAGEINSPECT_H
+
+#include "postgres.h"
+
+#include "funcapi.h"
+
+// Raw page functions
+
+extern Datum get_raw_page(PG_FUNCTION_ARGS);
+extern Datum get_raw_page_fork(PG_FUNCTION_ARGS);
+extern Page get_page_from_raw(bytea *raw_page);
+
+extern Datum page_header(PG_FUNCTION_ARGS);
+
+// FSM functions
+
+extern Datum fsm_page_contents(PG_FUNCTION_ARGS);
+
+// Heap functions
+
+extern Datum heap_page_items(PG_FUNCTION_ARGS);
+extern Datum tuple_data_split(PG_FUNCTION_ARGS);
+
+// B-tree functions
+
+extern Datum bt_page_stats(PG_FUNCTION_ARGS);
+extern Datum bt_page_items(PG_FUNCTION_ARGS);
+extern Datum bt_metap(PG_FUNCTION_ARGS);
+
+// GIN functions
+
+extern Datum gin_metapage_info(PG_FUNCTION_ARGS);
+extern Datum gin_page_opaque_info(PG_FUNCTION_ARGS);
+extern Datum gin_leafpage_items(PG_FUNCTION_ARGS);
+
+// BRIN functions
+
+extern Datum brin_page_type(PG_FUNCTION_ARGS);
+extern Datum brin_page_items(PG_FUNCTION_ARGS);
+extern Datum brin_metapage_info(PG_FUNCTION_ARGS);
+extern Datum brin_revmap_data(PG_FUNCTION_ARGS);
+
+// Hash functions
+
+extern Datum hash_page_type(PG_FUNCTION_ARGS);
+extern Datum hash_page_stats(PG_FUNCTION_ARGS);
+extern Datum hash_page_items(PG_FUNCTION_ARGS);
+extern Datum hash_bitmap_info(PG_FUNCTION_ARGS);
+extern Datum hash_metapage_info(PG_FUNCTION_ARGS);
+
+#endif
diff --git a/contrib/pageinspect/rawpage.c b/contrib/pageinspect/rawpage.c
index a2ac078..991ae84 100644
--- a/contrib/pageinspect/rawpage.c
+++ b/contrib/pageinspect/rawpage.c
@@ -15,6 +15,7 @@
#include "postgres.h"
+#include "pageinspect.h"
#include "access/htup_details.h"
#include "catalog/catalog.h"
#include "catalog/namespace.h"
@@ -86,6 +87,30 @@ get_raw_page_fork(PG_FUNCTION_ARGS)
}
/*
+ * get_page_from_raw
+ *
+ * Get a page
+ */
+Page
+get_page_from_raw(bytea *raw_page)
+{
+ int raw_page_size;
+ Page page;
+
+ raw_page_size = VARSIZE(raw_page) - VARHDRSZ;
+ if (raw_page_size < BLCKSZ)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("input page too small (%d bytes)", raw_page_size)));
+
+ /* make a copy so that the page is properly aligned for struct access */
+ page = palloc(raw_page_size);
+ memcpy(page, VARDATA(raw_page), raw_page_size);
+
+ return page;
+}
+
+/*
* workhorse
*/
static bytea *
--
2.7.4
On 02/03/2017 11:41 AM, Jesper Pedersen wrote:
contrib/pageinspect actually seems to lack *any* infrastructure
for sharing functions across modules. It's time to remedy that.
I propose inventing "pageinspect.h" to hold commonly visible
declarations, and moving get_page_from_raw() into rawpage.c,
which seems like a reasonably natural home for it. (Alternatively,
we could invent a pageinspect.c file to hold utility functions,
but that might be overkill.)No objections.
Attached is v1 of this w/ verify_hash_page() using get_page_from_raw().
Sorry for all the problems.
Disregard, as Tom has committed a fix.
Best regards,
Jesper
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Feb 3, 2017 at 11:49 AM, Jesper Pedersen
<jesper.pedersen@redhat.com> wrote:
Disregard, as Tom has committed a fix.
So we're six commits into this mess now and I'm hopeful that we've got
most of the problems with type selection and crashing fixed now.
However, I discovered another thing that doesn't make sense to me --
and I admit this is another thing I should have noticed before
committing, but better late than never.
As far as I can tell, the hash_bitmap_info() function is doing
something completely ridiculous. One would expect that the purpose of
this function was to tell you about the status of pages in the bitmap.
The documentation claims that this is what the function does: it
claims that this function "shows the status of a bit in the bitmap
page for a particular overflow page". So you would think that what
the function would do is:
1. Work out which bitmap page contains the bit for the page number in question.
2. Read that bitmap page.
3. Indicate the status of that bit within that page.
However, that's not what the function actually does. Instead, it does this:
1. Go examine the overflow page and see whether hasho_prevblkno. If
so, claim that the bit is set in the bitmap; if not, claim that it
isn't.
2. Work out which bitmap page contains the bit for the page number in question.
3. But don't look at it. Instead, tell the user which bitmap page and
bit you would have looked at, but instead of returning the status of
that bit, return the value you computed in step 1.
I do not think this can be the right approach.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sat, Jan 28, 2017 at 10:25 PM, Ashutosh Sharma <ashu.coek88@gmail.com> wrote:
Hi,
Please find my reply inline.
In hash_bimap_info(), we go to the trouble of creating a raw page but
never do anything with it. I guess the idea here is just to error out
if the supplied page number is not an overflow page, but there are no
comments explaining that. Anyway, I'm not sure it's a very good idea,
because it means that if you try to write a query to interrogate the
status of all the bitmap pages, it's going to read all of the overflow
pages to which they point, which makes the operation a LOT more
expensive. I think it would be better to leave this part out; if the
user wants to know which pages are actually overflow pages, they can
use hash_page_type() to figure it out.Yes, the intention was to ensure that user only passes overflow page
as an input to this function. I think if we wan't to avoid creating a
raw page then we may need to find some other way to verify if it is an
overflow page or not, may be we can make use of hash_check_page().Let's make it the job of this
function just to check the available/free status of the page in the
bitmap, without reading the bitmap itself.okay, In that case I think we can check the previous block number that
the overflow page is pointing to in order to identify if it is free or
in-use. AFAIU, if an overflow page is free it's prev block number will
be Invalid. This way, we may not have to read bitmap page. Now the
question here is, we also have bucket pages where previous block
number is always Invalid but before checking this we ensure that we
are only dealing with an overflow page.Please let me know if you feel
we do have some better option than this to identify the status of
overflow page without reading bitmap.
I think this was a very poor finding by me. If an overflow page is
freed, it is completely filled with zero values rather than marking
it's prev and next block number as invalid. Therefore, we won't be
able to read a free overflow page as it is a new page and hence, we
can't decide if an overflow page is free or not without reading the
corresponding bitmap page.
In doing that, I think it should either return (bitmapblkno,
bitmapbit, bit) or just bit. Returning bitmapblkno but not bitmapbit
seems strange. Also, I think it should return bit as a bool, not
int4.It would be good to return bitmap bit number as well along with the
bitmap block number. Also, returning bit as bool would look good. I
will do that.I am also working on other review comments and will share the updated
patch asap. Thanks.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
As far as I can tell, the hash_bitmap_info() function is doing
something completely ridiculous. One would expect that the purpose of
this function was to tell you about the status of pages in the bitmap.
The documentation claims that this is what the function does: it
claims that this function "shows the status of a bit in the bitmap
page for a particular overflow page". So you would think that what
the function would do is:1. Work out which bitmap page contains the bit for the page number in question.
2. Read that bitmap page.
3. Indicate the status of that bit within that page.However, that's not what the function actually does. Instead, it does this:
1. Go examine the overflow page and see whether hasho_prevblkno. If
so, claim that the bit is set in the bitmap; if not, claim that it
isn't.
2. Work out which bitmap page contains the bit for the page number in question.
3. But don't look at it. Instead, tell the user which bitmap page and
bit you would have looked at, but instead of returning the status of
that bit, return the value you computed in step 1.I do not think this can be the right approach.
Yes, It is not a right approach. As I mentioned in [1]/messages/by-id/CAE9k0PkiwT0qD3fdruU8bgAjTpzJpnqcT0XNWnnKxxFbogbL9A@mail.gmail.com, the overflow
page being freed is completely filled with zero values which means it
is not in a readable state. So, we won't be able to examine a free
overflow page. Considering these facts, I would take following
approach,
1) Check if an overflow page is a new page. If so, read a bitmap page
to confirm if a bit corresponding to this overflow page is clear or
not. For this, I would first add Assert statement to ensure that the
bit is clear and if it is, then set the statusbit as false indicating
that the page is free.
2) In case if an overflow page is not new, first verify if it is
really an overflow page and if so, check if the bit corresponding to
it in the bitmap page is SET. If so, set the statusbit as true; If
not, we would see an assertion failure happening.
If you are okay with this approach, please let me know I will share
you an updated patch. Thanks.
[1]: /messages/by-id/CAE9k0PkiwT0qD3fdruU8bgAjTpzJpnqcT0XNWnnKxxFbogbL9A@mail.gmail.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sat, Feb 4, 2017 at 7:06 AM, Ashutosh Sharma <ashu.coek88@gmail.com> wrote:
As far as I can tell, the hash_bitmap_info() function is doing
something completely ridiculous. One would expect that the purpose of
this function was to tell you about the status of pages in the bitmap.
The documentation claims that this is what the function does: it
claims that this function "shows the status of a bit in the bitmap
page for a particular overflow page". So you would think that what
the function would do is:1. Work out which bitmap page contains the bit for the page number in question.
2. Read that bitmap page.
3. Indicate the status of that bit within that page.However, that's not what the function actually does. Instead, it does this:
1. Go examine the overflow page and see whether hasho_prevblkno. If
so, claim that the bit is set in the bitmap; if not, claim that it
isn't.
2. Work out which bitmap page contains the bit for the page number in question.
3. But don't look at it. Instead, tell the user which bitmap page and
bit you would have looked at, but instead of returning the status of
that bit, return the value you computed in step 1.I do not think this can be the right approach.
Yes, It is not a right approach. As I mentioned in [1], the overflow
page being freed is completely filled with zero values which means it
is not in a readable state. So, we won't be able to examine a free
overflow page. Considering these facts, I would take following
approach,1) Check if an overflow page is a new page. If so, read a bitmap page
to confirm if a bit corresponding to this overflow page is clear or
not. For this, I would first add Assert statement to ensure that the
bit is clear and if it is, then set the statusbit as false indicating
that the page is free.2) In case if an overflow page is not new, first verify if it is
really an overflow page and if so, check if the bit corresponding to
it in the bitmap page is SET. If so, set the statusbit as true; If
not, we would see an assertion failure happening.
I think this is complicated and not what anybody wants. I think you
should do exactly what I said above: return true if the bit is set in
the bitmap, and false if it isn't. Full stop. Don't read or do
anything with the underlying page. Only read the bitmap page.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
1) Check if an overflow page is a new page. If so, read a bitmap page
to confirm if a bit corresponding to this overflow page is clear or
not. For this, I would first add Assert statement to ensure that the
bit is clear and if it is, then set the statusbit as false indicating
that the page is free.2) In case if an overflow page is not new, first verify if it is
really an overflow page and if so, check if the bit corresponding to
it in the bitmap page is SET. If so, set the statusbit as true; If
not, we would see an assertion failure happening.I think this is complicated and not what anybody wants. I think you
should do exactly what I said above: return true if the bit is set in
the bitmap, and false if it isn't. Full stop. Don't read or do
anything with the underlying page. Only read the bitmap page.
Okay, As per the inputs from you, I have modified hash_bitmap_info()
and have tried to keep the things simple. Attached is the patch that
has this changes. Please have a look and let me know if you feel it is
not yet in the right shape. Thanks.
Attachments:
simplify_hash_bitmap_info_pageinspect.patchapplication/x-download; name=simplify_hash_bitmap_info_pageinspect.patchDownload
diff --git a/contrib/pageinspect/expected/hash.out b/contrib/pageinspect/expected/hash.out
index 7eb1537..9630c60 100644
--- a/contrib/pageinspect/expected/hash.out
+++ b/contrib/pageinspect/expected/hash.out
@@ -29,22 +29,28 @@ 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);
+SELECT * FROM hash_bitmap_info(get_raw_page('test_hash_a_idx', 0),
+ '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);
+SELECT * FROM hash_bitmap_info(get_raw_page('test_hash_a_idx', 0),
+ '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);
+SELECT * FROM hash_bitmap_info(get_raw_page('test_hash_a_idx', 0),
+ '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);
+SELECT * FROM hash_bitmap_info(get_raw_page('test_hash_a_idx', 0),
+ '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);
+SELECT * FROM hash_bitmap_info(get_raw_page('test_hash_a_idx', 0),
+ '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);
+SELECT * FROM hash_bitmap_info(get_raw_page('test_hash_a_idx', 0),
+ 'test_hash_a_idx', 5);
ERROR: page is not an overflow page
DETAIL: Expected 00000001, got 00000004.
SELECT magic, version, ntuples, bsize, bmsize, bmshift, maxbucket, highmask,
diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c
index 08663c1..ab5ca632 100644
--- a/contrib/pageinspect/hashfuncs.c
+++ b/contrib/pageinspect/hashfuncs.c
@@ -371,19 +371,22 @@ hash_page_items(PG_FUNCTION_ARGS)
*
* Get bitmap information for a particular overflow page
*
- * Usage: SELECT * FROM hash_bitmap_info('con_hash_index'::regclass, 5);
+ * Usage: SELECT * FROM hash_bitmap_info(get_raw_page('con_hash_index', 0),
+ * 'con_hash_index'::regclass, 5);
* ------------------------------------------------
*/
Datum
hash_bitmap_info(PG_FUNCTION_ARGS)
{
- Oid indexRelid = PG_GETARG_OID(0);
- uint64 ovflblkno = PG_GETARG_INT64(1);
+ bytea *raw_page = PG_GETARG_BYTEA_P(0);
+ Oid indexRelid = PG_GETARG_OID(1);
+ uint64 ovflblkno = PG_GETARG_INT64(2);
HashMetaPage metap;
Buffer buf,
- metabuf;
+ mapbuf;
BlockNumber bitmapblkno;
- Page page;
+ Page page,
+ metapage, mappage;
bool bit = false;
HashPageOpaque opaque;
TupleDesc tupleDesc;
@@ -395,12 +398,14 @@ hash_bitmap_info(PG_FUNCTION_ARGS)
int j;
Datum values[3];
bool nulls[3];
+ uint32 *freep = NULL;
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
(errmsg("must be superuser to use raw page functions"))));
+ metapage = verify_hash_page(raw_page, LH_META_PAGE);
indexRel = index_open(indexRelid, AccessShareLock);
if (!IS_HASH(indexRel))
@@ -438,8 +443,7 @@ hash_bitmap_info(PG_FUNCTION_ARGS)
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));
+ metap = HashPageGetMeta(metapage);
/* Identify overflow bit number */
ovflbitno = _hash_ovflblkno_to_bitno(metap, (BlockNumber) ovflblkno);
@@ -454,8 +458,15 @@ hash_bitmap_info(PG_FUNCTION_ARGS)
bitmapblkno = metap->hashm_mapp[bitmappage];
- _hash_relbuf(indexRel, metabuf);
+ /* Check the status of bitmap bit for overflow page */
+ mapbuf = _hash_getbuf(indexRel, bitmapblkno, HASH_READ, LH_BITMAP_PAGE);
+ mappage = BufferGetPage(mapbuf);
+ freep = HashPageGetBitmap(mappage);
+
+ if (ISSET(freep, bitmapbit))
+ bit = 1;
+ _hash_relbuf(indexRel, mapbuf);
index_close(indexRel, AccessShareLock);
/* Build a tuple descriptor for our result type */
diff --git a/contrib/pageinspect/pageinspect--1.5--1.6.sql b/contrib/pageinspect/pageinspect--1.5--1.6.sql
index ac39568..a0cff81 100644
--- a/contrib/pageinspect/pageinspect--1.5--1.6.sql
+++ b/contrib/pageinspect/pageinspect--1.5--1.6.sql
@@ -45,7 +45,9 @@ LANGUAGE C STRICT PARALLEL SAFE;
--
-- hash_bitmap_info()
--
-CREATE FUNCTION hash_bitmap_info(IN index_oid regclass, IN blkno int8,
+CREATE FUNCTION hash_bitmap_info(IN page bytea,
+ IN index_oid regclass,
+ IN blkno int8,
OUT bitmapblkno int8,
OUT bitmapbit int4,
OUT bitstatus bool)
diff --git a/contrib/pageinspect/sql/hash.sql b/contrib/pageinspect/sql/hash.sql
index 87ee549..40be988 100644
--- a/contrib/pageinspect/sql/hash.sql
+++ b/contrib/pageinspect/sql/hash.sql
@@ -13,12 +13,23 @@ 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_bitmap_info(get_raw_page('test_hash_a_idx', 0),
+ 'test_hash_a_idx', 0);
+
+SELECT * FROM hash_bitmap_info(get_raw_page('test_hash_a_idx', 0),
+ 'test_hash_a_idx', 1);
+
+SELECT * FROM hash_bitmap_info(get_raw_page('test_hash_a_idx', 0),
+ 'test_hash_a_idx', 2);
+
+SELECT * FROM hash_bitmap_info(get_raw_page('test_hash_a_idx', 0),
+ 'test_hash_a_idx', 3);
+
+SELECT * FROM hash_bitmap_info(get_raw_page('test_hash_a_idx', 0),
+ 'test_hash_a_idx', 4);
+
+SELECT * FROM hash_bitmap_info(get_raw_page('test_hash_a_idx', 0),
+ 'test_hash_a_idx', 5);
SELECT magic, version, ntuples, bsize, bmsize, bmshift, maxbucket, highmask,
diff --git a/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml
index 5e6712f..eef6746 100644
--- a/doc/src/sgml/pageinspect.sgml
+++ b/doc/src/sgml/pageinspect.sgml
@@ -578,7 +578,7 @@ test=# SELECT * FROM hash_page_items(get_raw_page('con_hash_index', 1)) LIMIT 5;
<varlistentry>
<term>
- <function>hash_bitmap_info(index oid, blkno int) returns record</function>
+ <function>hash_bitmap_info(page bytea, index oid, blkno int) returns record</function>
<indexterm>
<primary>hash_bitmap_info</primary>
</indexterm>
@@ -590,10 +590,10 @@ test=# SELECT * FROM hash_page_items(get_raw_page('con_hash_index', 1)) LIMIT 5;
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);
+test=# SELECT * FROM hash_bitmap_info(get_raw_page('con_hash_index', 0), 'con_hash_index', 516);
bitmapblkno | bitmapbit | bitstatus
-------------+-----------+-----------
- 65 | 3 | t
+ 33 | 3 | t
</screen>
</para>
</listitem>
On Wed, Feb 8, 2017 at 9:25 AM, Ashutosh Sharma <ashu.coek88@gmail.com> wrote:
1) Check if an overflow page is a new page. If so, read a bitmap page
to confirm if a bit corresponding to this overflow page is clear or
not. For this, I would first add Assert statement to ensure that the
bit is clear and if it is, then set the statusbit as false indicating
that the page is free.2) In case if an overflow page is not new, first verify if it is
really an overflow page and if so, check if the bit corresponding to
it in the bitmap page is SET. If so, set the statusbit as true; If
not, we would see an assertion failure happening.I think this is complicated and not what anybody wants. I think you
should do exactly what I said above: return true if the bit is set in
the bitmap, and false if it isn't. Full stop. Don't read or do
anything with the underlying page. Only read the bitmap page.Okay, As per the inputs from you, I have modified hash_bitmap_info()
and have tried to keep the things simple. Attached is the patch that
has this changes. Please have a look and let me know if you feel it is
not yet in the right shape. Thanks.
Well, this changes the function signature and I don't see any
advantage in doing that. Also, it still doesn't read the code that
reads the overflow page. Any correct patch for this problem needs to
include the following hunk:
- buf = ReadBufferExtended(indexRel, MAIN_FORKNUM, (BlockNumber) 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);
And then, instead, you need to add some code to set bit based on the
bitmap page, like what you have:
+ mapbuf = _hash_getbuf(indexRel, bitmapblkno, HASH_READ, LH_BITMAP_PAGE);
+ mappage = BufferGetPage(mapbuf);
+ freep = HashPageGetBitmap(mappage);
+
+ if (ISSET(freep, bitmapbit))
+ bit = 1;
Except I would write that last line as...
bit = ISSET(freep, bitmapbit) != 0
...instead of using an if statement.
I'm sort of confused as to why the idea of not reading the underlying
page is so hard to understand.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Feb 8, 2017 at 11:26 PM, Robert Haas <robertmhaas@gmail.com> wrote:
On Wed, Feb 8, 2017 at 11:58 AM, Ashutosh Sharma <ashu.coek88@gmail.com> wrote:
And then, instead, you need to add some code to set bit based on the
bitmap page, like what you have:+ mapbuf = _hash_getbuf(indexRel, bitmapblkno, HASH_READ, LH_BITMAP_PAGE); + mappage = BufferGetPage(mapbuf); + freep = HashPageGetBitmap(mappage); + + if (ISSET(freep, bitmapbit)) + bit = 1;Except I would write that last line as...
bit = ISSET(freep, bitmapbit) != 0
...instead of using an if statement.
I'm sort of confused as to why the idea of not reading the underlying
page is so hard to understand.It was not so hard to understand your point. The only reason for
reading overflow page is to ensure that we are passing an overflow
block as input to '_hash_ovflblkno_to_bitno(metap, (BlockNumber)
ovflblkno)'. I had removed the code for reading an overflow page
assuming that _hash_ovflblkno_to_bitno() will throw an error if we
pass block number of a page other than overflow page but, it doesn't
seem to guarantee that it will always return value for an overflow
page. Here are my observations,postgres=# select hash_page_type(get_raw_page('con_hash_index', 65));
hash_page_type
----------------
bucket
(1 row)postgres=# SELECT * FROM
hash_bitmap_info(get_raw_page('con_hash_index', 0), 'con_hash_index',
65);
bitmapblkno | bitmapbit | bitstatus
-------------+-----------+-----------
33 | 0 | t
(1 row)postgres=# select hash_page_type(get_raw_page('con_hash_index', 64));
hash_page_type
----------------
bucket
(1 row)postgres=# SELECT * FROM
hash_bitmap_info(get_raw_page('con_hash_index', 0), 'con_hash_index',
64);
ERROR: invalid overflow block number 64postgres=# select hash_page_type(get_raw_page('con_hash_index', 63));
hash_page_type
----------------
bucket
(1 row)postgres=# SELECT * FROM
hash_bitmap_info(get_raw_page('con_hash_index', 0), 'con_hash_index',
63);
ERROR: invalid overflow block number 63postgres=# select hash_page_type(get_raw_page('con_hash_index', 33));
hash_page_type
----------------
bitmap
(1 row)postgres=# SELECT * FROM
hash_bitmap_info(get_raw_page('con_hash_index', 0), 'con_hash_index',
33);
bitmapblkno | bitmapbit | bitstatus
-------------+-----------+-----------
33 | 0 | t
(1 row)Right, I understand. But if the caller cares about that, they can use
hash_page_type() in order to find out whether the result of
hash_bitmap_info() will be meaningful. The problem with the way
you've done it is that if someone wants to check the status of a
million bitmap bits, that currently requires reading a million pages
(8GB) whereas if you don't read the underlying page it requires
reading only enough pages to contain a million bitmap bits (~128kB).
That's a big difference.If you want to verify that the supplied page number is an overflow
page before returning the bit, I think you should do that calculation
based on the metapage. There's enough information in hashm_spares to
figure out which pages are primary bucket pages as opposed to overflow
pages, and there's enough information in hashm_mapp to identify which
pages are bitmap pages, and the metapage is always page 0. If you
take that approach, then you can check a million bitmap bits reading
only the relevant bitmap pages plus the metapage, which is a LOT less
I/O than reading a million index pages.
Thanks for the inputs. Attached is the patch modified as per your suggestions.
--
With Regards,
Ashutosh Sharma
EnterpriseDB:http://www.enterprisedb.com
Attachments:
simplify_hash_bitmap_info_v2.patchapplication/x-download; name=simplify_hash_bitmap_info_v2.patchDownload
diff --git a/contrib/pageinspect/expected/hash.out b/contrib/pageinspect/expected/hash.out
index 7eb1537..fb0caee 100644
--- a/contrib/pageinspect/expected/hash.out
+++ b/contrib/pageinspect/expected/hash.out
@@ -30,23 +30,21 @@ 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.
+ERROR: invalid overflow block number 0
SELECT * FROM hash_bitmap_info('test_hash_a_idx', 1);
-ERROR: page is not an overflow page
-DETAIL: Expected 00000001, got 00000002.
+ERROR: invalid overflow block number 1
SELECT * FROM hash_bitmap_info('test_hash_a_idx', 2);
-ERROR: page is not an overflow page
-DETAIL: Expected 00000001, got 00000002.
+ERROR: invalid overflow block number 2
SELECT * FROM hash_bitmap_info('test_hash_a_idx', 3);
-ERROR: page is not an overflow page
-DETAIL: Expected 00000001, got 00000002.
+ERROR: invalid overflow block number 3
SELECT * FROM hash_bitmap_info('test_hash_a_idx', 4);
-ERROR: page is not an overflow page
-DETAIL: Expected 00000001, got 00000002.
+ERROR: invalid overflow block number 4
SELECT * FROM hash_bitmap_info('test_hash_a_idx', 5);
-ERROR: page is not an overflow page
-DETAIL: Expected 00000001, got 00000004.
+-[ RECORD 1 ]--
+bitmapblkno | 5
+bitmapbit | 0
+bitstatus | t
+
SELECT magic, version, ntuples, bsize, bmsize, bmshift, maxbucket, highmask,
lowmask, ovflpoint, firstfree, nmaps, procid, spares, mapp FROM
hash_metapage_info(get_raw_page('test_hash_a_idx', 0));
diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c
index 08663c1..af25506 100644
--- a/contrib/pageinspect/hashfuncs.c
+++ b/contrib/pageinspect/hashfuncs.c
@@ -170,6 +170,45 @@ GetHashPageStatistics(Page page, HashPageStat * stat)
stat->free_size = PageGetFreeSpace(page);
}
+/*
+ * _hash_ovflblkno_to_bitno
+ *
+ * Convert overflow page block number to bit number for free-page bitmap.
+ * We already have similar function defined in hashovfl.c but which does
+ * return value for some of the bucket pages along with overflow and bit-
+ * map pages and it is seems to be buggy to me. This function has been
+ * designed such that it only returns the value for overflow and bitmap
+ * pages.
+ */
+static uint32
+_hash_ovflblkno_to_bitno(HashMetaPage metap, BlockNumber ovflblkno)
+{
+ uint32 splitnum = metap->hashm_ovflpoint;
+ uint32 i;
+ uint32 bitnum;
+
+ /* Determine the split number containing this page */
+ for (i = 1; i <= splitnum; i++)
+ {
+ if (ovflblkno <= (BlockNumber) (1 << i))
+ break; /* oops */
+ bitnum = ovflblkno - (1 << i);
+ /*
+ * bitnum has to be greater than number of overflow
+ * page added in previous split point. The overflow
+ * page at this splitnum (i) if any should start from
+ * ((2 ^ i) + metap->hashm_spares[i -1] + 1).
+ */
+ if (bitnum > metap->hashm_spares[i -1] &&
+ bitnum <= metap->hashm_spares[i])
+ return bitnum - 1; /* -1 to convert 1-based to 0-based */
+ }
+
+ elog(ERROR, "invalid overflow block number %u", ovflblkno);
+ return 0; /* keep compiler quiet */
+}
+
+
/* ---------------------------------------------------
* hash_page_type()
*
@@ -380,12 +419,11 @@ hash_bitmap_info(PG_FUNCTION_ARGS)
Oid indexRelid = PG_GETARG_OID(0);
uint64 ovflblkno = PG_GETARG_INT64(1);
HashMetaPage metap;
- Buffer buf,
- metabuf;
+ Buffer metabuf,
+ mapbuf;
BlockNumber bitmapblkno;
- Page page;
+ Page mappage;
bool bit = false;
- HashPageOpaque opaque;
TupleDesc tupleDesc;
Relation indexRel;
uint32 ovflbitno;
@@ -395,6 +433,7 @@ hash_bitmap_info(PG_FUNCTION_ARGS)
int j;
Datum values[3];
bool nulls[3];
+ uint32 *freep = NULL;
if (!superuser())
ereport(ERROR,
@@ -418,25 +457,6 @@ hash_bitmap_info(PG_FUNCTION_ARGS)
errmsg("block number " UINT64_FORMAT " is out of range for relation \"%s\"",
ovflblkno, RelationGetRelationName(indexRel))));
- buf = ReadBufferExtended(indexRel, MAIN_FORKNUM, (BlockNumber) 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));
@@ -456,6 +476,14 @@ hash_bitmap_info(PG_FUNCTION_ARGS)
_hash_relbuf(indexRel, metabuf);
+ /* Check the status of bitmap bit for overflow page */
+ mapbuf = _hash_getbuf(indexRel, bitmapblkno, HASH_READ, LH_BITMAP_PAGE);
+ mappage = BufferGetPage(mapbuf);
+ freep = HashPageGetBitmap(mappage);
+
+ bit = ISSET(freep, bitmapbit) != 0;
+
+ _hash_relbuf(indexRel, mapbuf);
index_close(indexRel, AccessShareLock);
/* Build a tuple descriptor for our result type */
diff --git a/src/backend/access/hash/hashovfl.c b/src/backend/access/hash/hashovfl.c
index 753c8a6..e8928ef 100644
--- a/src/backend/access/hash/hashovfl.c
+++ b/src/backend/access/hash/hashovfl.c
@@ -52,12 +52,10 @@ bitno_to_blkno(HashMetaPage metap, uint32 ovflbitnum)
}
/*
- * _hash_ovflblkno_to_bitno
- *
* Convert overflow page block number to bit number for free-page bitmap.
*/
-uint32
-_hash_ovflblkno_to_bitno(HashMetaPage metap, BlockNumber ovflblkno)
+static uint32
+blkno_to_bitno(HashMetaPage metap, BlockNumber ovflblkno)
{
uint32 splitnum = metap->hashm_ovflpoint;
uint32 i;
@@ -487,7 +485,7 @@ _hash_freeovflpage(Relation rel, Buffer ovflbuf, Buffer wbuf,
metap = HashPageGetMeta(BufferGetPage(metabuf));
/* Identify which bit to set */
- ovflbitno = _hash_ovflblkno_to_bitno(metap, ovflblkno);
+ ovflbitno = blkno_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 c045585..acbe0ea 100644
--- a/src/include/access/hash.h
+++ b/src/include/access/hash.h
@@ -313,7 +313,6 @@ 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,
Import Notes
Reply to msg id not found: CA+TgmoYzjgr6DDXYvE266wQxa4XDBYNRD2YTgGtCvh8qmPg@mail.gmail.com
On Thu, Feb 9, 2017 at 8:56 AM, Ashutosh Sharma <ashu.coek88@gmail.com> wrote:
If you want to verify that the supplied page number is an overflow
page before returning the bit, I think you should do that calculation
based on the metapage. There's enough information in hashm_spares to
figure out which pages are primary bucket pages as opposed to overflow
pages, and there's enough information in hashm_mapp to identify which
pages are bitmap pages, and the metapage is always page 0. If you
take that approach, then you can check a million bitmap bits reading
only the relevant bitmap pages plus the metapage, which is a LOT less
I/O than reading a million index pages.Thanks for the inputs. Attached is the patch modified as per your suggestions.
I think you should just tighten up the sanity checking in the existing
function _hash_ovflblkno_to_bitno rather than duplicating the code. I
don't think it's called often enough for one extra (cheap) test to be
an issue. (You should change the elog in that function to an ereport,
too, since it's going to be a user-facing error message now.)
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
I think you should just tighten up the sanity checking in the existing
function _hash_ovflblkno_to_bitno rather than duplicating the code. I
don't think it's called often enough for one extra (cheap) test to be
an issue. (You should change the elog in that function to an ereport,
too, since it's going to be a user-facing error message now.)
okay, I have taken care of above two points in the attached patch. Thanks.
Attachments:
simplify_hash_bitmap_info_v3.patchapplication/x-download; name=simplify_hash_bitmap_info_v3.patchDownload
diff --git a/contrib/pageinspect/expected/hash.out b/contrib/pageinspect/expected/hash.out
index 7eb1537..fb0caee 100644
--- a/contrib/pageinspect/expected/hash.out
+++ b/contrib/pageinspect/expected/hash.out
@@ -30,23 +30,21 @@ 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.
+ERROR: invalid overflow block number 0
SELECT * FROM hash_bitmap_info('test_hash_a_idx', 1);
-ERROR: page is not an overflow page
-DETAIL: Expected 00000001, got 00000002.
+ERROR: invalid overflow block number 1
SELECT * FROM hash_bitmap_info('test_hash_a_idx', 2);
-ERROR: page is not an overflow page
-DETAIL: Expected 00000001, got 00000002.
+ERROR: invalid overflow block number 2
SELECT * FROM hash_bitmap_info('test_hash_a_idx', 3);
-ERROR: page is not an overflow page
-DETAIL: Expected 00000001, got 00000002.
+ERROR: invalid overflow block number 3
SELECT * FROM hash_bitmap_info('test_hash_a_idx', 4);
-ERROR: page is not an overflow page
-DETAIL: Expected 00000001, got 00000002.
+ERROR: invalid overflow block number 4
SELECT * FROM hash_bitmap_info('test_hash_a_idx', 5);
-ERROR: page is not an overflow page
-DETAIL: Expected 00000001, got 00000004.
+-[ RECORD 1 ]--
+bitmapblkno | 5
+bitmapbit | 0
+bitstatus | t
+
SELECT magic, version, ntuples, bsize, bmsize, bmshift, maxbucket, highmask,
lowmask, ovflpoint, firstfree, nmaps, procid, spares, mapp FROM
hash_metapage_info(get_raw_page('test_hash_a_idx', 0));
diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c
index 08663c1..a645e04 100644
--- a/contrib/pageinspect/hashfuncs.c
+++ b/contrib/pageinspect/hashfuncs.c
@@ -380,12 +380,11 @@ hash_bitmap_info(PG_FUNCTION_ARGS)
Oid indexRelid = PG_GETARG_OID(0);
uint64 ovflblkno = PG_GETARG_INT64(1);
HashMetaPage metap;
- Buffer buf,
- metabuf;
+ Buffer metabuf,
+ mapbuf;
BlockNumber bitmapblkno;
- Page page;
+ Page mappage;
bool bit = false;
- HashPageOpaque opaque;
TupleDesc tupleDesc;
Relation indexRel;
uint32 ovflbitno;
@@ -395,6 +394,7 @@ hash_bitmap_info(PG_FUNCTION_ARGS)
int j;
Datum values[3];
bool nulls[3];
+ uint32 *freep = NULL;
if (!superuser())
ereport(ERROR,
@@ -418,25 +418,6 @@ hash_bitmap_info(PG_FUNCTION_ARGS)
errmsg("block number " UINT64_FORMAT " is out of range for relation \"%s\"",
ovflblkno, RelationGetRelationName(indexRel))));
- buf = ReadBufferExtended(indexRel, MAIN_FORKNUM, (BlockNumber) 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));
@@ -456,6 +437,14 @@ hash_bitmap_info(PG_FUNCTION_ARGS)
_hash_relbuf(indexRel, metabuf);
+ /* Check the status of bitmap bit for overflow page */
+ mapbuf = _hash_getbuf(indexRel, bitmapblkno, HASH_READ, LH_BITMAP_PAGE);
+ mappage = BufferGetPage(mapbuf);
+ freep = HashPageGetBitmap(mappage);
+
+ bit = ISSET(freep, bitmapbit) != 0;
+
+ _hash_relbuf(indexRel, mapbuf);
index_close(indexRel, AccessShareLock);
/* Build a tuple descriptor for our result type */
diff --git a/src/backend/access/hash/hashovfl.c b/src/backend/access/hash/hashovfl.c
index 753c8a6..9a13f6b 100644
--- a/src/backend/access/hash/hashovfl.c
+++ b/src/backend/access/hash/hashovfl.c
@@ -69,11 +69,20 @@ _hash_ovflblkno_to_bitno(HashMetaPage metap, BlockNumber ovflblkno)
if (ovflblkno <= (BlockNumber) (1 << i))
break; /* oops */
bitnum = ovflblkno - (1 << i);
- if (bitnum <= metap->hashm_spares[i])
+ /*
+ * bitnum has to be greater than number of overflow
+ * page added in previous split point. The overflow
+ * page at this splitnum (i) if any should start from
+ * ((2 ^ i) + metap->hashm_spares[i -1] + 1).
+ */
+ if (bitnum > metap->hashm_spares[i -1] &&
+ bitnum <= metap->hashm_spares[i])
return bitnum - 1; /* -1 to convert 1-based to 0-based */
}
- elog(ERROR, "invalid overflow block number %u", ovflblkno);
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid overflow block number %u", ovflblkno)));
return 0; /* keep compiler quiet */
}
On Thu, Feb 9, 2017 at 1:11 PM, Ashutosh Sharma <ashu.coek88@gmail.com> wrote:
I think you should just tighten up the sanity checking in the existing
function _hash_ovflblkno_to_bitno rather than duplicating the code. I
don't think it's called often enough for one extra (cheap) test to be
an issue. (You should change the elog in that function to an ereport,
too, since it's going to be a user-facing error message now.)okay, I have taken care of above two points in the attached patch. Thanks.
Alright, committed with a little further hacking. That would rejected
using hash_bitmap_info() on primary bucket pages and the metapage, but
not on bitmap pages, which seems weird. So I fixed that and pushed
this.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Feb 10, 2017 at 1:06 AM, Robert Haas <robertmhaas@gmail.com> wrote:
Alright, committed with a little further hacking.
I did pull the latest code, and tried
Test:
====
create table t1(t int);
create index i1 on t1 using hash(t);
insert into t1 select generate_series(1, 10000000);
postgres=# SELECT spares FROM hash_metapage_info(get_raw_page('i1', 0));
spares
----------------------------------------------------------------------------
{0,0,0,1,9,17,33,65,-127,1,1,0,1,-1,-1,-4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
spares are showing negative numbers; I think the wrong type has been
chosen, seems it is rounding at 127, spares are defined as following
uint32 hashm_spares[HASH_MAX_SPLITPOINTS]; /* spare pages before each
splitpoint */
it should be always positive.
--
Thanks and Regards
Mithun C Y
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Thanks for reporting it. This is because of incorrect data typecasting.
Attached is the patch that fixes this issue.
On Tue, Feb 21, 2017 at 2:58 PM, Mithun Cy <mithun.cy@enterprisedb.com>
wrote:
Show quoted text
On Fri, Feb 10, 2017 at 1:06 AM, Robert Haas <robertmhaas@gmail.com>
wrote:Alright, committed with a little further hacking.
I did pull the latest code, and tried
Test:
====
create table t1(t int);
create index i1 on t1 using hash(t);
insert into t1 select generate_series(1, 10000000);postgres=# SELECT spares FROM hash_metapage_info(get_raw_page('i1', 0));
spares
------------------------------------------------------------
----------------
{0,0,0,1,9,17,33,65,-127,1,1,0,1,-1,-1,-4,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0}spares are showing negative numbers; I think the wrong type has been
chosen, seems it is rounding at 127, spares are defined as following
uint32 hashm_spares[HASH_MAX_SPLITPOINTS]; /* spare pages before each
splitpoint */it should be always positive.
--
Thanks and Regards
Mithun C Y
EnterpriseDB: http://www.enterprisedb.com
Attachments:
typecast_fix.patchinvalid/octet-stream; name=typecast_fix.patchDownload
diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c
index 88c807a..812a03f 100644
--- a/contrib/pageinspect/hashfuncs.c
+++ b/contrib/pageinspect/hashfuncs.c
@@ -541,7 +541,7 @@ hash_metapage_info(PG_FUNCTION_ARGS)
values[j++] = ObjectIdGetDatum((Oid) metad->hashm_procid);
for (i = 0; i < HASH_MAX_SPLITPOINTS; i++)
- spares[i] = Int64GetDatum((int8) metad->hashm_spares[i]);
+ spares[i] = Int64GetDatum((int64) metad->hashm_spares[i]);
values[j++] = PointerGetDatum(construct_array(spares,
HASH_MAX_SPLITPOINTS,
INT8OID,
On Tue, Feb 21, 2017 at 3:14 PM, Ashutosh Sharma <ashu.coek88@gmail.com> wrote:
Thanks for reporting it. This is because of incorrect data typecasting.
Attached is the patch that fixes this issue.
Oops, that's probably my fault. Thanks for the patch; committed.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers