diff options
Diffstat (limited to 'contrib/pageinspect/hashfuncs.c')
-rw-r--r-- | contrib/pageinspect/hashfuncs.c | 586 |
1 files changed, 586 insertions, 0 deletions
diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c new file mode 100644 index 0000000..69af7b9 --- /dev/null +++ b/contrib/pageinspect/hashfuncs.c @@ -0,0 +1,586 @@ +/* + * hashfuncs.c + * Functions to investigate the content of HASH indexes + * + * Copyright (c) 2017-2022, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/pageinspect/hashfuncs.c + */ + +#include "postgres.h" + +#include "access/hash.h" +#include "access/htup_details.h" +#include "catalog/pg_am.h" +#include "catalog/pg_type.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "pageinspect.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/rel.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 +{ + int live_items; + int dead_items; + int page_size; + int 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 a palloc'd, properly aligned copy of the page is returned. + */ +static Page +verify_hash_page(bytea *raw_page, int flags) +{ + Page page = get_page_from_raw(raw_page); + int pagetype = LH_UNUSED_PAGE; + + /* Treat new pages as unused. */ + if (!PageIsNew(page)) + { + HashPageOpaque pageopaque; + + if (PageGetSpecialSize(page) != MAXALIGN(sizeof(HashPageOpaqueData))) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("input page is not a valid %s page", "hash"), + errdetail("Expected special size %d, got %d.", + (int) MAXALIGN(sizeof(HashPageOpaqueData)), + (int) PageGetSpecialSize(page)))); + + pageopaque = HashPageGetOpaque(page); + if (pageopaque->hasho_page_id != HASHO_PAGE_ID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("input page is not a valid %s page", "hash"), + errdetail("Expected %08x, got %08x.", + HASHO_PAGE_ID, pageopaque->hasho_page_id))); + + pagetype = pageopaque->hasho_flag & LH_PAGE_TYPE; + } + + /* Check that page type is sane. */ + if (pagetype != LH_OVERFLOW_PAGE && pagetype != LH_BUCKET_PAGE && + pagetype != LH_BITMAP_PAGE && pagetype != LH_META_PAGE && + pagetype != LH_UNUSED_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"))); + break; + case LH_BUCKET_PAGE | LH_OVERFLOW_PAGE: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("page is not a hash bucket or overflow page"))); + break; + case LH_OVERFLOW_PAGE: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("page is not a hash overflow page"))); + break; + default: + elog(ERROR, + "hash page of type %08x not in mask %08x", + pagetype, flags); + break; + } + } + + /* + * 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 = HashPageGetOpaque(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; + int pagetype; + const 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); + + if (PageIsNew(page)) + type = "unused"; + else + { + opaque = HashPageGetOpaque(page); + + /* page type (flags) */ + pagetype = opaque->hasho_flag & LH_PAGE_TYPE; + if (pagetype == LH_META_PAGE) + type = "metapage"; + else if (pagetype == LH_OVERFLOW_PAGE) + type = "overflow"; + else if (pagetype == LH_BUCKET_PAGE) + type = "bucket"; + else if (pagetype == 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++] = Int32GetDatum(stat.live_items); + values[j++] = Int32GetDatum(stat.dead_items); + values[j++] = Int32GetDatum(stat.page_size); + values[j++] = Int32GetDatum(stat.free_size); + values[j++] = Int64GetDatum((int64) stat.hasho_prevblkno); + values[j++] = Int64GetDatum((int64) stat.hasho_nextblkno); + values[j++] = Int64GetDatum((int64) stat.hasho_bucket); + values[j++] = Int32GetDatum((int32) stat.hasho_flag); + values[j++] = Int32GetDatum((int32) 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(); + + mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx); + + page = verify_hash_page(raw_page, LH_BUCKET_PAGE | LH_OVERFLOW_PAGE); + + 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++] = Int32GetDatum((int32) uargs->offset); + values[j++] = PointerGetDatum(&itup->t_tid); + + hashkey = _hash_get_indextuple_hashkey(itup); + values[j] = Int64GetDatum((int64) hashkey); + + tuple = heap_form_tuple(fctx->attinmeta->tupdesc, values, nulls); + result = HeapTupleGetDatum(tuple); + + uargs->offset = uargs->offset + 1; + + SRF_RETURN_NEXT(fctx, result); + } + + 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); + int64 ovflblkno = PG_GETARG_INT64(1); + HashMetaPage metap; + Buffer metabuf, + mapbuf; + BlockNumber bitmapblkno; + Page mappage; + bool bit = false; + TupleDesc tupleDesc; + Relation indexRel; + uint32 ovflbitno; + int32 bitmappage, + bitmapbit; + HeapTuple tuple; + int i, + j; + Datum values[3]; + bool nulls[3]; + uint32 *freep; + + 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)) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a %s index", + RelationGetRelationName(indexRel), "hash"))); + + if (RELATION_IS_OTHER_TEMP(indexRel)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot access temporary tables of other sessions"))); + + if (ovflblkno < 0 || ovflblkno > MaxBlockNumber) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid block number"))); + + if (ovflblkno >= RelationGetNumberOfBlocks(indexRel)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("block number %lld is out of range for relation \"%s\"", + (long long int) ovflblkno, RelationGetRelationName(indexRel)))); + + /* 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)); + + /* + * Reject attempt to read the bit for a metapage or bitmap page; this is + * only meaningful for overflow pages. + */ + if (ovflblkno == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid overflow block number %u", + (BlockNumber) ovflblkno))); + for (i = 0; i < metap->hashm_nmaps; i++) + if (metap->hashm_mapp[i] == ovflblkno) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid overflow block number %u", + (BlockNumber) ovflblkno))); + + /* + * Identify overflow bit number. This will error out for primary bucket + * pages, and we've already rejected the metapage and bitmap pages above. + */ + ovflbitno = _hash_ovflblkno_to_bitno(metap, (BlockNumber) 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 block number %u", + (BlockNumber) ovflblkno))); + + bitmapblkno = metap->hashm_mapp[bitmappage]; + + _hash_relbuf(indexRel, metabuf); + + /* Check the status of bitmap bit for overflow page */ + mapbuf = _hash_getbuf(indexRel, bitmapblkno, HASH_READ, LH_BITMAP_PAGE); + mappage = BufferGetPage(mapbuf); + freep = HashPageGetBitmap(mappage); + + bit = ISSET(freep, bitmapbit) != 0; + + _hash_relbuf(indexRel, mapbuf); + index_close(indexRel, AccessShareLock); + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + tupleDesc = BlessTupleDesc(tupleDesc); + + MemSet(nulls, 0, sizeof(nulls)); + + j = 0; + values[j++] = Int64GetDatum((int64) 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++] = Int64GetDatum((int64) metad->hashm_magic); + values[j++] = Int64GetDatum((int64) metad->hashm_version); + values[j++] = Float8GetDatum(metad->hashm_ntuples); + values[j++] = Int32GetDatum((int32) metad->hashm_ffactor); + values[j++] = Int32GetDatum((int32) metad->hashm_bsize); + values[j++] = Int32GetDatum((int32) metad->hashm_bmsize); + values[j++] = Int32GetDatum((int32) metad->hashm_bmshift); + values[j++] = Int64GetDatum((int64) metad->hashm_maxbucket); + values[j++] = Int64GetDatum((int64) metad->hashm_highmask); + values[j++] = Int64GetDatum((int64) metad->hashm_lowmask); + values[j++] = Int64GetDatum((int64) metad->hashm_ovflpoint); + values[j++] = Int64GetDatum((int64) metad->hashm_firstfree); + values[j++] = Int64GetDatum((int64) metad->hashm_nmaps); + values[j++] = ObjectIdGetDatum((Oid) metad->hashm_procid); + + for (i = 0; i < HASH_MAX_SPLITPOINTS; i++) + spares[i] = Int64GetDatum((int64) metad->hashm_spares[i]); + values[j++] = PointerGetDatum(construct_array(spares, + HASH_MAX_SPLITPOINTS, + INT8OID, + sizeof(int64), + FLOAT8PASSBYVAL, + TYPALIGN_DOUBLE)); + + for (i = 0; i < HASH_MAX_BITMAPS; i++) + mapp[i] = Int64GetDatum((int64) metad->hashm_mapp[i]); + values[j++] = PointerGetDatum(construct_array(mapp, + HASH_MAX_BITMAPS, + INT8OID, + sizeof(int64), + FLOAT8PASSBYVAL, + TYPALIGN_DOUBLE)); + + tuple = heap_form_tuple(tupleDesc, values, nulls); + + PG_RETURN_DATUM(HeapTupleGetDatum(tuple)); +} |