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,