]> granicus.if.org Git - postgresql/commitdiff
pageinspect: Support hash indexes.
authorRobert Haas <rhaas@postgresql.org>
Thu, 2 Feb 2017 19:12:58 +0000 (14:12 -0500)
committerRobert Haas <rhaas@postgresql.org>
Thu, 2 Feb 2017 19:19:32 +0000 (14:19 -0500)
Patch by Jesper Pedersen and Ashutosh Sharma, with some error handling
improvements by me.  Tests from Peter Eisentraut.  Reviewed by Álvaro
Herrera, Michael Paquier, Jesper Pedersen, Jeff Janes, Peter
Eisentraut, Amit Kapila, Mithun Cy, and me.

Discussion: http://postgr.es/m/e2ac6c58-b93f-9dd9-f4e6-d6d30add7fdf@redhat.com

contrib/pageinspect/Makefile
contrib/pageinspect/expected/hash.out [new file with mode: 0644]
contrib/pageinspect/hashfuncs.c [new file with mode: 0644]
contrib/pageinspect/pageinspect--1.5--1.6.sql [new file with mode: 0644]
contrib/pageinspect/pageinspect.control
contrib/pageinspect/sql/hash.sql [new file with mode: 0644]
doc/src/sgml/pageinspect.sgml
src/backend/access/hash/hashovfl.c
src/include/access/hash.h

index 87a28e98c29a9dd786ebbace4d08a1ad804274c8..0a3cbeeb108ce3cb4ad6509c7db0b8395b905e9e 100644 (file)
@@ -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 (file)
index 0000000..3abc887
--- /dev/null
@@ -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 (file)
index 0000000..5812afe
--- /dev/null
@@ -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 (file)
index 0000000..d0355b4
--- /dev/null
@@ -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;
index 23c8eff9cd7dde9065eb9909a5ed98100e8061cc..1a61c9f5ad31b80b399f6779a0a729a5b56c16b9 100644 (file)
@@ -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 (file)
index 0000000..9e7635e
--- /dev/null
@@ -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;
index d12dbac32db9a98ba06b4af2940d5647604264b2..4c201e75b0de33bb5ca36ec568b159daf7ac7a8c 100644 (file)
@@ -486,6 +486,150 @@ test=# SELECT first_tid, nbytes, tids[0:5] AS some_tids
  (170,30)  |    376 | {"(170,30)","(170,31)","(170,32)","(170,33)","(170,34)"}
  (173,44)  |    197 | {"(173,44)","(173,45)","(173,46)","(173,47)","(173,48)"}
 (7 rows)
+</screen>
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </sect2>
+
+ <sect2>
+  <title>Hash Functions</title>
+
+  <variablelist>
+   <varlistentry>
+    <term>
+     <function>hash_page_type(page bytea) returns text</function>
+     <indexterm>
+      <primary>hash_page_type</primary>
+     </indexterm>
+    </term>
+
+    <listitem>
+     <para>
+      <function>hash_page_type</function> returns page type of
+      the given <acronym>HASH</acronym> index page.  For example:
+<screen>
+test=# SELECT hash_page_type(get_raw_page('con_hash_index', 0));
+ hash_page_type 
+----------------
+ metapage
+</screen>
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term>
+     <function>hash_page_stats(page bytea) returns setof record</function>
+     <indexterm>
+      <primary>hash_page_stats</primary>
+     </indexterm>
+    </term>
+
+    <listitem>
+     <para>
+      <function>hash_page_stats</function> returns information about
+      a bucket or overflow page of a <acronym>HASH</acronym> index.
+      For example:
+<screen>
+test=# SELECT * FROM hash_page_stats(get_raw_page('con_hash_index', 1));
+-[ RECORD 1 ]---+-----------
+live_items      | 407
+dead_items      | 0
+page_size       | 8192
+free_size       | 8
+hasho_prevblkno | 4294967295
+hasho_nextblkno | 8474
+hasho_bucket    | 0
+hasho_flag      | 66
+hasho_page_id   | 65408
+</screen>
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term>
+     <function>hash_page_items(page bytea) returns setof record</function>
+     <indexterm>
+      <primary>hash_page_items</primary>
+     </indexterm>
+    </term>
+
+    <listitem>
+     <para>
+      <function>hash_page_items</function> returns information about
+      the data stored in a bucket or overflow page of a <acronym>HASH</acronym>
+      index page.  For example:
+<screen>
+test=# SELECT * FROM hash_page_items(get_raw_page('con_hash_index', 1)) LIMIT 5;
+ itemoffset |   ctid    |    data    
+------------+-----------+------------
+          1 | (899,77)  | 1053474816
+          2 | (897,29)  | 1053474816
+          3 | (894,207) | 1053474816
+          4 | (892,159) | 1053474816
+          5 | (890,111) | 1053474816
+</screen>
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term>
+     <function>hash_bitmap_info(index oid, blkno int) returns record</function>
+     <indexterm>
+      <primary>hash_bitmap_info</primary>
+     </indexterm>
+    </term>
+
+    <listitem>
+     <para>
+      <function>hash_bitmap_info</function> shows the status of a bit
+      in the bitmap page for a particular overflow page of <acronym>HASH</acronym>
+      index. For example:
+<screen>
+test=# SELECT * FROM hash_bitmap_info('con_hash_index', 2052);
+ bitmapblkno | bitmapbit | bitstatus 
+-------------+-----------+-----------
+          65 |         3 | t
+</screen>
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term>
+     <function>hash_metapage_info(page bytea) returns record</function>
+     <indexterm>
+      <primary>hash_metapage_info</primary>
+     </indexterm>
+    </term>
+
+    <listitem>
+     <para>
+      <function>hash_metapage_info</function> returns information stored
+      in meta page of a <acronym>HASH</acronym> index.  For example:
+<screen>
+test=# SELECT * FROM hash_metapage_info(get_raw_page('con_hash_index', 0));
+-[ RECORD 1 ]-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+magic     | 105121344
+version   | 2
+ntuples   | 500500
+ffactor   | 40
+bsize     | 8152
+bmsize    | 4096
+bmshift   | 15
+maxbucket | 12512
+highmask  | 16383
+lowmask   | 8191
+ovflpoint | 14
+firstfree | 1204
+nmaps     | 1
+procid    | 450
+spares    | {0,0,0,0,0,0,1,1,1,1,1,4,59,704,1204,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
+mapp      | {65,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
 </screen>
      </para>
     </listitem>
index e8928efc1aae1b92eafa8f251abac2e109f3fad9..753c8a6a134166fa5bf148f912a15e5759c27268 100644 (file)
@@ -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);
index 69a3873facec3fb167d93fffaf470b6d8be4bd1e..1a9b91f9f53dc64db64e2c39d1ea51c684be763f 100644 (file)
@@ -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,