From: Robert Haas Date: Thu, 2 Feb 2017 19:12:58 +0000 (-0500) Subject: pageinspect: Support hash indexes. X-Git-Tag: REL_10_BETA1~964 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=08bf6e529587e1e9075d013d859af2649c32a511;p=postgresql 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 --- diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile index 87a28e98c2..0a3cbeeb10 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 0000000000..3abc887800 --- /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 0000000000..5812afe936 --- /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 0000000000..d0355b4c7e --- /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 23c8eff9cd..1a61c9f5ad 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 0000000000..9e7635e36d --- /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 d12dbac32d..4c201e75b0 100644 --- a/doc/src/sgml/pageinspect.sgml +++ b/doc/src/sgml/pageinspect.sgml @@ -486,6 +486,150 @@ test=# SELECT first_tid, nbytes, tids[0:5] AS some_tids (170,30) | 376 | {"(170,30)","(170,31)","(170,32)","(170,33)","(170,34)"} (173,44) | 197 | {"(173,44)","(173,45)","(173,46)","(173,47)","(173,48)"} (7 rows) + + + + + + + + + Hash Functions + + + + + hash_page_type(page bytea) returns text + + hash_page_type + + + + + + hash_page_type returns page type of + the given HASH index page. For example: + +test=# SELECT hash_page_type(get_raw_page('con_hash_index', 0)); + hash_page_type +---------------- + metapage + + + + + + + + hash_page_stats(page bytea) returns setof record + + hash_page_stats + + + + + + hash_page_stats returns information about + a bucket or overflow page of a HASH index. + For example: + +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 + + + + + + + + hash_page_items(page bytea) returns setof record + + hash_page_items + + + + + + hash_page_items returns information about + the data stored in a bucket or overflow page of a HASH + index page. For example: + +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 + + + + + + + + hash_bitmap_info(index oid, blkno int) returns record + + hash_bitmap_info + + + + + + hash_bitmap_info shows the status of a bit + in the bitmap page for a particular overflow page of HASH + index. For example: + +test=# SELECT * FROM hash_bitmap_info('con_hash_index', 2052); + bitmapblkno | bitmapbit | bitstatus +-------------+-----------+----------- + 65 | 3 | t + + + + + + + + hash_metapage_info(page bytea) returns record + + hash_metapage_info + + + + + + hash_metapage_info returns information stored + in meta page of a HASH index. For example: + +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} diff --git a/src/backend/access/hash/hashovfl.c b/src/backend/access/hash/hashovfl.c index e8928efc1a..753c8a6a13 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 69a3873fac..1a9b91f9f5 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,