pageinspect: Support hash indexes.

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
This commit is contained in:
Robert Haas 2017-02-02 14:12:58 -05:00
parent acd73ad1a1
commit 08bf6e5295
9 changed files with 995 additions and 10 deletions

View 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

View File

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

View File

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

View File

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

View 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

View File

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

View 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>

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

View 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,