/* * hashfuncs.c * Functions to investigate the content of HASH indexes * * Copyright (c) 2017-2024, PostgreSQL Global Development Group * * IDENTIFICATION * contrib/pageinspect/hashfuncs.c */ #include "postgres.h" #include "access/hash.h" #include "access/htup_details.h" #include "access/relation.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_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX) #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] = {0}; 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); 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] = {0}; 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); 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] = {0}; uint32 *freep; if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to use raw page functions"))); indexRel = relation_open(indexRelid, AccessShareLock); if (!IS_INDEX(indexRel) || !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); 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] = {0}; 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); 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_builtin(spares, HASH_MAX_SPLITPOINTS, INT8OID)); for (i = 0; i < HASH_MAX_BITMAPS; i++) mapp[i] = Int64GetDatum((int64) metad->hashm_mapp[i]); values[j++] = PointerGetDatum(construct_array_builtin(mapp, HASH_MAX_BITMAPS, INT8OID)); tuple = heap_form_tuple(tupleDesc, values, nulls); PG_RETURN_DATUM(HeapTupleGetDatum(tuple)); }