agora inbox for pgsql-hackers@postgresql.orghelp / color / mirror / Atom feed
[PATCH] Add support for hash index in pageinspect contrib module v11 3+ messages / 2 participants [nested] [flat]
* [PATCH] Add support for hash index in pageinspect contrib module v11 @ 2017-01-03 12:49 jesperpedersen <jesper.pedersen@redhat.com> 0 siblings, 0 replies; 3+ messages in thread From: jesperpedersen @ 2017-01-03 12:49 UTC (permalink / raw) Authors: Ashutosh Sharma and Jesper Pedersen. --- contrib/pageinspect/Makefile | 10 +- contrib/pageinspect/hashfuncs.c | 558 ++++++++++++++++++++++++++ contrib/pageinspect/pageinspect--1.5--1.6.sql | 76 ++++ contrib/pageinspect/pageinspect.control | 2 +- doc/src/sgml/pageinspect.sgml | 149 +++++++ src/backend/access/hash/hashovfl.c | 52 +-- src/include/access/hash.h | 1 + 7 files changed, 817 insertions(+), 31 deletions(-) create mode 100644 contrib/pageinspect/hashfuncs.c create mode 100644 contrib/pageinspect/pageinspect--1.5--1.6.sql diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile index 87a28e9..d7f3645 100644 --- a/contrib/pageinspect/Makefile +++ b/contrib/pageinspect/Makefile @@ -2,13 +2,13 @@ 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 diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c new file mode 100644 index 0000000..525e780 --- /dev/null +++ b/contrib/pageinspect/hashfuncs.c @@ -0,0 +1,558 @@ +/* + * 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_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 +{ + uint32 live_items; + uint32 dead_items; + uint32 page_size; + uint32 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; + HashPageOpaque pageopaque; + + raw_page_size = VARSIZE(raw_page) - VARHDRSZ; + + if (raw_page_size != BLCKSZ) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("input page too small"), + 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 unexpected 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))); + + if (!(pageopaque->hasho_flag & flags)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("page is not a hash page of expected type"), + errdetail("Expected hash page type: %08x got: %08x.", + flags, pageopaque->hasho_flag))); + 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 */ + if (opaque->hasho_flag & LH_BUCKET_PAGE) + stat->hasho_prevblkno = InvalidBlockNumber; + else + 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 * FROM 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, LH_META_PAGE | LH_BUCKET_PAGE | + LH_OVERFLOW_PAGE | LH_BITMAP_PAGE); + 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 pageinspect 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++] = UInt32GetDatum(stat.live_items); + values[j++] = UInt32GetDatum(stat.dead_items); + values[j++] = UInt32GetDatum(stat.page_size); + values[j++] = UInt32GetDatum(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]; + HeapTuple tuple; + FuncCallContext *fctx; + MemoryContext mctx; + struct user_args *uargs; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + (errmsg("must be superuser to use pageinspect functions")))); + + if (SRF_IS_FIRSTCALL()) + { + TupleDesc tupleDesc; + + fctx = SRF_FIRSTCALL_INIT(); + + page = verify_hash_page(raw_page, LH_BUCKET_PAGE | LH_OVERFLOW_PAGE); + + /* + * We copy the page into local storage to avoid holding pin on the + * buffer longer than we must, and possibly failing to release it at + * all if the calling query doesn't fetch all rows. + */ + mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx); + + uargs = palloc(sizeof(struct user_args)); + + uargs->page = palloc(BLCKSZ); + memcpy(uargs->page, page, BLCKSZ); + + 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; + int off; + int dlen; + char *dump; + char *s; + char *ptr; + + 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++] = CStringGetTextDatum(psprintf("(%u,%u)", + BlockIdGetBlockNumber(&(itup->t_tid.ip_blkid)), + itup->t_tid.ip_posid)); + + ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info); + dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info); + dump = palloc0(dlen * 3 + 1); + s = dump; + for (off = 0; off < dlen; off++) + { + if (off > 0) + *dump++ = ' '; + sprintf(dump, "%02x", *(ptr + off) & 0xff); + dump += 2; + } + values[j] = CStringGetTextDatum(s); + + 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->page); + 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); + bytea *raw_page; + char *raw_page_data; + HashMetaPage metap; + Buffer buf, metabuf, mapbuf; + BlockNumber bitmapblkno; + Page page, mappage; + HashPageOpaque pageopaque; + TupleDesc tupleDesc; + Relation indexRel; + uint32 ovflbitno, bit = 0; + int32 bitmappage,bitmapbit; + HeapTuple tuple; + int j; + Datum values[2]; + bool nulls[2]; + uint32 *freep = NULL; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + (errmsg("must be superuser to use pageinspect 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)))); + + /* Create a raw page */ + raw_page = (bytea *) palloc(BLCKSZ + VARHDRSZ); + SET_VARSIZE(raw_page, BLCKSZ + VARHDRSZ); + raw_page_data = VARDATA(raw_page); + + buf = ReadBufferExtended(indexRel, MAIN_FORKNUM, ovflblkno, RBM_NORMAL, NULL); + LockBuffer(buf, BUFFER_LOCK_SHARE); + + memcpy(raw_page_data, BufferGetPage(buf), BLCKSZ); + + LockBuffer(buf, BUFFER_LOCK_UNLOCK); + ReleaseBuffer(buf); + + page = verify_hash_page(raw_page, LH_OVERFLOW_PAGE); + pageopaque = (HashPageOpaque) PageGetSpecialPointer(page); + + if (pageopaque->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, pageopaque->hasho_flag))); + + /* 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) + elog(ERROR, "invalid overflow bit number %u", ovflbitno); + + bitmapblkno = metap->hashm_mapp[bitmappage]; + + /* Check the status of bitmap bit for overflow page */ + mapbuf = _hash_getbuf(indexRel, bitmapblkno, HASH_WRITE, LH_BITMAP_PAGE); + mappage = BufferGetPage(mapbuf); + freep = HashPageGetBitmap(mappage); + + if (ISSET(freep, bitmapbit)) + bit = 1; + + _hash_relbuf(indexRel, metabuf); + + _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++] = UInt32GetDatum(bitmapblkno); + values[j++] = UInt32GetDatum(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]; + char *spares; + char *mapp; + char *s; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + (errmsg("must be superuser to use pageinspect 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++] = CStringGetTextDatum(psprintf("0x%08X", 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); + + spares = palloc0(HASH_MAX_SPLITPOINTS * 5 + 1); + s = spares; + for (i = 0; i < HASH_MAX_SPLITPOINTS; i++) + { + if (i > 0) + *spares++ = ' '; + + sprintf(spares, "%04x", metad->hashm_spares[i]); + spares += 4; + } + values[j++] = CStringGetTextDatum(s); + + mapp = palloc0(HASH_MAX_BITMAPS * 5 + 1); + s = mapp; + for (i = 0; i < HASH_MAX_BITMAPS; i++) + { + if (i > 0) + *mapp++ = ' '; + + sprintf(mapp, "%04x", metad->hashm_mapp[i]); + mapp += 4; + } + values[j++] = CStringGetTextDatum(s); + + 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 0000000..08d844c --- /dev/null +++ b/contrib/pageinspect/pageinspect--1.5--1.6.sql @@ -0,0 +1,76 @@ +/* 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 int4, + OUT dead_items int4, + OUT page_size int4, + OUT free_size int4, + OUT hasho_prevblkno int4, + OUT hasho_nextblkno int4, + OUT hasho_bucket int4, + OUT hasho_flag int4, + 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 text) +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 int4, + OUT bitmapblkno int4, + OUT bitmapbit int4) +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 text, + OUT version int4, + OUT ntuples double precision, + OUT ffactor int2, + OUT bsize int2, + OUT bmsize int2, + OUT bmshift int2, + OUT maxbucket int4, + OUT highmask int4, + OUT lowmask int4, + OUT ovflpoint int4, + OUT firstfree int4, + OUT nmaps int4, + OUT procid int4, + OUT spares text, + OUT mapp text) +AS 'MODULE_PATHNAME', 'hash_metapage_info' +LANGUAGE C STRICT PARALLEL SAFE; diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control index 23c8eff..1a61c9f 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/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml index d12dbac..aff299a 100644 --- a/doc/src/sgml/pageinspect.sgml +++ b/doc/src/sgml/pageinspect.sgml @@ -493,4 +493,153 @@ test=# SELECT first_tid, nbytes, tids[0:5] AS some_tids </variablelist> </sect2> + <sect2> + <title>Hash Functions</title> + + <variablelist> + <varlistentry> + <term> + <function>hash_page_type(page bytea) returns text</function> + <indexterm> + <primary>hash_page_type</primary> + </indexterm> + </term> + + <listitem> + <para> + <function>hash_page_type</function> returns page type of + the given <acronym>HASH</acronym> index page. For example: +<screen> +test=# SELECT * FROM hash_page_type(get_raw_page('con_hash_index', 0)); + hash_page_type +---------------- + metapage +</screen> + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term> + <function>hash_page_stats(page bytea) returns setof record</function> + <indexterm> + <primary>hash_page_stats</primary> + </indexterm> + </term> + + <listitem> + <para> + <function>hash_page_stats</function> returns information about + a bucket or overflow page of a <acronym>HASH</acronym> index. + For example: +<screen> +test=# SELECT * FROM hash_page_stats(get_raw_page('con_hash_index', 1)); +-[ RECORD 1 ]---+------ +live_items | 407 +dead_items | 0 +page_size | 8192 +free_size | 8 +hasho_prevblkno | -1 +hasho_nextblkno | 8474 +hasho_bucket | 0 +hasho_flag | 66 +hasho_page_id | 65408 +</screen> + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term> + <function>hash_page_items(page bytea) returns setof record</function> + <indexterm> + <primary>hash_page_items</primary> + </indexterm> + </term> + + <listitem> + <para> + <function>hash_page_items</function> returns information about + the data stored in a bucket or overflow page of a <acronym>HASH</acronym> + index page. For example: +<screen> +test=# SELECT * FROM hash_page_items(get_raw_page('con_hash_index', 1)); + itemoffset | ctid | data +------------+-----------------+------------------------- + 1 | (3145728,14376) | 00 c0 ca 3e 00 00 00 00 + 2 | (3145728,14376) | 00 c0 ca 3e 00 00 00 00 + 3 | (3407872,14376) | 00 c0 ca 3e 00 00 00 00 + 4 | (3407872,14376) | 00 c0 ca 3e 00 00 00 00 + 5 | (3407872,14376) | 00 c0 ca 3e 00 00 00 00 +... + 404 | (2883584,14120) | 00 c0 ca 3e 00 00 00 00 + 405 | (2883584,13608) | 00 c0 ca 3e 00 00 00 00 + 406 | (2621440,13096) | 00 c0 ca 3e 00 00 00 00 + 407 | (2621440,12584) | 00 c0 ca 3e 00 00 00 00 +</screen> + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term> + <function>hash_bitmap_info(index oid, blkno int) returns record</function> + <indexterm> + <primary>hash_bitmap_info</primary> + </indexterm> + </term> + + <listitem> + <para> + <function>hash_bitmap_info</function> returns information about + the status of a bit for an overflow page in bitmap page of a <acronym>HASH</acronym> + index. For example: +<screen> +test=# SELECT * FROM hash_bitmap_info('con_hash_index', 2050); + bitmapblkno | bitmapbit +-------------+----------- + 65 | 1 +</screen> + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term> + <function>hash_metapage_info(page bytea) returns record</function> + <indexterm> + <primary>hash_metapage_info</primary> + </indexterm> + </term> + + <listitem> + <para> + <function>hash_metapage_info</function> returns information stored + in meta page of a <acronym>HASH</acronym> index. For example: +<screen> +test=# SELECT * FROM hash_metapage_info(get_raw_page('con_hash_index', 0)); +-[ RECORD 1 ]----------- +magic | 0x06440640 +version | 2 +ntuples | 686000 +ffactor | 40 +bsize | 8152 +bmsize | 4096 +bmshift | 15 +maxbucket | 17149 +highmask | 32767 +lowmask | 16383 +ovflpoint | 15 +firstfree | 2012 +nmaps | 1 +procid | 450 +spares | 0000 0000 0000 0000 0000 0000 0001 0001 0001 0001 0001 0004 003b 02c0 07c3 07dc 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 +mapp | 0041 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 +</screen> + </para> + </listitem> + </varlistentry> + </variablelist> + </sect2> + </sect1> diff --git a/src/backend/access/hash/hashovfl.c b/src/backend/access/hash/hashovfl.c index 504670b..658249e 100644 --- a/src/backend/access/hash/hashovfl.c +++ b/src/backend/access/hash/hashovfl.c @@ -53,30 +53,6 @@ bitno_to_blkno(HashMetaPage metap, uint32 ovflbitnum) } /* - * Convert overflow page block number to bit number for free-page bitmap. - */ -static uint32 -blkno_to_bitno(HashMetaPage metap, BlockNumber ovflblkno) -{ - uint32 splitnum = metap->hashm_ovflpoint; - uint32 i; - uint32 bitnum; - - /* Determine the split number containing this page */ - for (i = 1; i <= splitnum; i++) - { - if (ovflblkno <= (BlockNumber) (1 << i)) - break; /* oops */ - bitnum = ovflblkno - (1 << i); - if (bitnum <= metap->hashm_spares[i]) - return bitnum - 1; /* -1 to convert 1-based to 0-based */ - } - - elog(ERROR, "invalid overflow block number %u", ovflblkno); - return 0; /* keep compiler quiet */ -} - -/* * _hash_addovflpage * * Add an overflow page to the bucket whose last page is pointed to by 'buf'. @@ -558,7 +534,7 @@ _hash_freeovflpage(Relation rel, Buffer bucketbuf, Buffer ovflbuf, 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); @@ -1093,3 +1069,29 @@ readpage: /* NOTREACHED */ } + +/* + * _hash_ovflblkno_to_bitno + * + * Convert overflow page block number to bit number for free-page bitmap. + */ +uint32 +_hash_ovflblkno_to_bitno(HashMetaPage metap, BlockNumber ovflblkno) +{ + uint32 splitnum = metap->hashm_ovflpoint; + uint32 i; + uint32 bitnum; + + /* Determine the split number containing this page */ + for (i = 1; i <= splitnum; i++) + { + if (ovflblkno <= (BlockNumber) (1 << i)) + break; /* oops */ + bitnum = ovflblkno - (1 << i); + if (bitnum <= metap->hashm_spares[i]) + return bitnum - 1; /* -1 to convert 1-based to 0-based */ + } + + elog(ERROR, "invalid overflow block number %u", ovflblkno); + return 0; /* keep compiler quiet */ +} diff --git a/src/include/access/hash.h b/src/include/access/hash.h index 8328fc5..687b3d5 100644 --- a/src/include/access/hash.h +++ b/src/include/access/hash.h @@ -323,6 +323,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, -- 2.7.4 --------------F740619ED6CE10ACFBAC29C8 Content-Type: text/plain Content-Disposition: inline Content-Transfer-Encoding: 8bit MIME-Version: 1.0 -- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers --------------F740619ED6CE10ACFBAC29C8-- ^ permalink raw reply [nested|flat] 3+ messages in thread
* [PATCH] Add support for hash index in pageinspect contrib module v13 @ 2017-01-12 19:00 jesperpedersen <jesper.pedersen@redhat.com> 0 siblings, 0 replies; 3+ messages in thread From: jesperpedersen @ 2017-01-12 19:00 UTC (permalink / raw) Authors: Ashutosh Sharma and Jesper Pedersen. --- contrib/pageinspect/Makefile | 10 +- contrib/pageinspect/hashfuncs.c | 548 ++++++++++++++++++++++++++ contrib/pageinspect/pageinspect--1.5--1.6.sql | 76 ++++ contrib/pageinspect/pageinspect.control | 2 +- doc/src/sgml/pageinspect.sgml | 148 +++++++ src/backend/access/hash/hashovfl.c | 52 +-- src/include/access/hash.h | 1 + 7 files changed, 806 insertions(+), 31 deletions(-) create mode 100644 contrib/pageinspect/hashfuncs.c create mode 100644 contrib/pageinspect/pageinspect--1.5--1.6.sql diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile index 87a28e9..d7f3645 100644 --- a/contrib/pageinspect/Makefile +++ b/contrib/pageinspect/Makefile @@ -2,13 +2,13 @@ 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 diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c new file mode 100644 index 0000000..25e9641 --- /dev/null +++ b/contrib/pageinspect/hashfuncs.c @@ -0,0 +1,548 @@ +/* + * 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_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 +{ + uint32 live_items; + uint32 dead_items; + uint32 page_size; + uint32 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; + HashPageOpaque pageopaque; + + raw_page_size = VARSIZE(raw_page) - VARHDRSZ; + + if (raw_page_size != BLCKSZ) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("input page too small"), + 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 unexpected 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))); + + if (!(pageopaque->hasho_flag & flags)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("page is not a hash page of expected type"), + errdetail("Expected hash page type: %08x got: %08x.", + flags, pageopaque->hasho_flag))); + 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 */ + if (opaque->hasho_flag & LH_BUCKET_PAGE) + stat->hasho_prevblkno = InvalidBlockNumber; + else + 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 * FROM 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, LH_META_PAGE | LH_BUCKET_PAGE | + LH_OVERFLOW_PAGE | LH_BITMAP_PAGE); + 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 pageinspect 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++] = UInt32GetDatum(stat.live_items); + values[j++] = UInt32GetDatum(stat.dead_items); + values[j++] = UInt32GetDatum(stat.page_size); + values[j++] = UInt32GetDatum(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]; + HeapTuple tuple; + FuncCallContext *fctx; + MemoryContext mctx; + struct user_args *uargs; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + (errmsg("must be superuser to use pageinspect 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; + int off; + int dlen; + char *s; + char *ptr; + char *dump; + int number; + + 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++] = CStringGetTextDatum(psprintf("(%u,%u)", + BlockIdGetBlockNumber(&(itup->t_tid.ip_blkid)), + itup->t_tid.ip_posid)); + + ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info); + Assert(ptr <= uargs->page + BLCKSZ); + dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info); + dump = palloc(dlen * 2 + 1); + s = dump; + for (off = (dlen - 1); off >= 0; off--) + { + sprintf(dump, "%02x", ptr[off] & 0xff); + dump += 2; + } + *dump++ = '\0'; + + number = (int) strtol(s, NULL, 16); + + values[j] = Int32GetDatum(number & 0xFFFFFFFF); + + 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); + bytea *raw_page; + char *raw_page_data; + HashMetaPage metap; + Buffer buf, metabuf, mapbuf; + BlockNumber bitmapblkno; + Page mappage; + TupleDesc tupleDesc; + Relation indexRel; + uint32 ovflbitno, bit = 0; + int32 bitmappage,bitmapbit; + HeapTuple tuple; + int j; + Datum values[2]; + bool nulls[2]; + uint32 *freep = NULL; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + (errmsg("must be superuser to use pageinspect 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)))); + + /* Create a raw page */ + raw_page = (bytea *) palloc(BLCKSZ + VARHDRSZ); + SET_VARSIZE(raw_page, BLCKSZ + VARHDRSZ); + raw_page_data = VARDATA(raw_page); + + buf = ReadBufferExtended(indexRel, MAIN_FORKNUM, ovflblkno, RBM_NORMAL, NULL); + LockBuffer(buf, BUFFER_LOCK_SHARE); + + memcpy(raw_page_data, BufferGetPage(buf), BLCKSZ); + + LockBuffer(buf, BUFFER_LOCK_UNLOCK); + ReleaseBuffer(buf); + + verify_hash_page(raw_page, LH_OVERFLOW_PAGE); + + /* 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]; + + /* Check the status of bitmap bit for overflow page */ + mapbuf = _hash_getbuf(indexRel, bitmapblkno, HASH_WRITE, LH_BITMAP_PAGE); + mappage = BufferGetPage(mapbuf); + freep = HashPageGetBitmap(mappage); + + if (ISSET(freep, bitmapbit)) + bit = 1; + + _hash_relbuf(indexRel, metabuf); + + _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++] = UInt32GetDatum(bitmapblkno); + values[j++] = UInt32GetDatum(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]; + char *spares; + char *mapp; + char *s; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + (errmsg("must be superuser to use pageinspect 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++] = CStringGetTextDatum(psprintf("0x%08X", 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); + + spares = palloc0(HASH_MAX_SPLITPOINTS * 5 + 1); + s = spares; + for (i = 0; i < HASH_MAX_SPLITPOINTS; i++) + { + if (i > 0) + *spares++ = ' '; + + sprintf(spares, "%04x", metad->hashm_spares[i]); + spares += 4; + } + values[j++] = CStringGetTextDatum(s); + + mapp = palloc0(HASH_MAX_BITMAPS * 5 + 1); + s = mapp; + for (i = 0; i < HASH_MAX_BITMAPS; i++) + { + if (i > 0) + *mapp++ = ' '; + + sprintf(mapp, "%04x", metad->hashm_mapp[i]); + mapp += 4; + } + values[j++] = CStringGetTextDatum(s); + + 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 0000000..e159099 --- /dev/null +++ b/contrib/pageinspect/pageinspect--1.5--1.6.sql @@ -0,0 +1,76 @@ +/* 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 int4, + OUT dead_items int4, + OUT page_size int4, + OUT free_size int4, + OUT hasho_prevblkno int4, + OUT hasho_nextblkno int4, + OUT hasho_bucket int4, + OUT hasho_flag int4, + OUT hasho_page_id int8) +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 int4, + OUT bitmapblkno int4, + OUT bitmapbit int4) +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 text, + OUT version int4, + OUT ntuples double precision, + OUT ffactor int2, + OUT bsize int2, + OUT bmsize int2, + OUT bmshift int2, + OUT maxbucket int4, + OUT highmask int4, + OUT lowmask int4, + OUT ovflpoint int4, + OUT firstfree int4, + OUT nmaps int4, + OUT procid int4, + OUT spares text, + OUT mapp text) +AS 'MODULE_PATHNAME', 'hash_metapage_info' +LANGUAGE C STRICT PARALLEL SAFE; diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control index 23c8eff..1a61c9f 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/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml index d12dbac..3eba20c 100644 --- a/doc/src/sgml/pageinspect.sgml +++ b/doc/src/sgml/pageinspect.sgml @@ -493,4 +493,152 @@ test=# SELECT first_tid, nbytes, tids[0:5] AS some_tids </variablelist> </sect2> + <sect2> + <title>Hash Functions</title> + + <variablelist> + <varlistentry> + <term> + <function>hash_page_type(page bytea) returns text</function> + <indexterm> + <primary>hash_page_type</primary> + </indexterm> + </term> + + <listitem> + <para> + <function>hash_page_type</function> returns page type of + the given <acronym>HASH</acronym> index page. For example: +<screen> +test=# SELECT * FROM hash_page_type(get_raw_page('con_hash_index', 0)); + hash_page_type +---------------- + metapage +</screen> + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term> + <function>hash_page_stats(page bytea) returns setof record</function> + <indexterm> + <primary>hash_page_stats</primary> + </indexterm> + </term> + + <listitem> + <para> + <function>hash_page_stats</function> returns information about + a bucket or overflow page of a <acronym>HASH</acronym> index. + For example: +<screen> +test=# SELECT * FROM hash_page_stats(get_raw_page('con_hash_index', 1)); +-[ RECORD 1 ]---+------ +live_items | 407 +dead_items | 0 +page_size | 8192 +free_size | 8 +hasho_prevblkno | -1 +hasho_nextblkno | 8474 +hasho_bucket | 0 +hasho_flag | 66 +hasho_page_id | 65408 +</screen> + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term> + <function>hash_page_items(page bytea) returns setof record</function> + <indexterm> + <primary>hash_page_items</primary> + </indexterm> + </term> + + <listitem> + <para> + <function>hash_page_items</function> returns information about + the data stored in a bucket or overflow page of a <acronym>HASH</acronym> + index page. For example: +<screen> +test=# SELECT * FROM hash_page_items(get_raw_page('con_hash_index', 1)); + itemoffset | ctid | data +------------+-----------------+------------ + 1 | (3407872,12584) | 1053474816 + 2 | (3407872,12584) | 1053474816 + 3 | (3145728,12584) | 1053474816 + 4 | (3145728,12584) | 1053474816 + 5 | (3407872,12584) | 1053474816 +... + 131 | (2883584,13352) | 2778469888 + 132 | (2883584,12840) | 2778469888 + 133 | (2883584,12328) | 2778469888 +</screen> + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term> + <function>hash_bitmap_info(index oid, blkno int) returns record</function> + <indexterm> + <primary>hash_bitmap_info</primary> + </indexterm> + </term> + + <listitem> + <para> + <function>hash_bitmap_info</function> shows the status of a bit + in the bitmap page for a particular overflow page of <acronym>HASH</acronym> + index. For example: +<screen> +test=# SELECT * FROM hash_bitmap_info('con_hash_index', 2050); + bitmapblkno | bitmapbit +-------------+----------- + 65 | 1 +</screen> + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term> + <function>hash_metapage_info(page bytea) returns record</function> + <indexterm> + <primary>hash_metapage_info</primary> + </indexterm> + </term> + + <listitem> + <para> + <function>hash_metapage_info</function> returns information stored + in meta page of a <acronym>HASH</acronym> index. For example: +<screen> +test=# SELECT * FROM hash_metapage_info(get_raw_page('con_hash_index', 0)); +-[ RECORD 1 ]----------- +magic | 0x06440640 +version | 2 +ntuples | 686000 +ffactor | 40 +bsize | 8152 +bmsize | 4096 +bmshift | 15 +maxbucket | 17149 +highmask | 32767 +lowmask | 16383 +ovflpoint | 15 +firstfree | 2012 +nmaps | 1 +procid | 450 +spares | 0000 0000 0000 0000 0000 0000 0001 0001 0001 0001 0001 0004 003b 02c0 07c3 07dc 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 +mapp | 0041 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 +</screen> + </para> + </listitem> + </varlistentry> + </variablelist> + </sect2> + </sect1> diff --git a/src/backend/access/hash/hashovfl.c b/src/backend/access/hash/hashovfl.c index e8928ef..756cce2 100644 --- a/src/backend/access/hash/hashovfl.c +++ b/src/backend/access/hash/hashovfl.c @@ -52,30 +52,6 @@ bitno_to_blkno(HashMetaPage metap, uint32 ovflbitnum) } /* - * Convert overflow page block number to bit number for free-page bitmap. - */ -static uint32 -blkno_to_bitno(HashMetaPage metap, BlockNumber ovflblkno) -{ - uint32 splitnum = metap->hashm_ovflpoint; - uint32 i; - uint32 bitnum; - - /* Determine the split number containing this page */ - for (i = 1; i <= splitnum; i++) - { - if (ovflblkno <= (BlockNumber) (1 << i)) - break; /* oops */ - bitnum = ovflblkno - (1 << i); - if (bitnum <= metap->hashm_spares[i]) - return bitnum - 1; /* -1 to convert 1-based to 0-based */ - } - - elog(ERROR, "invalid overflow block number %u", ovflblkno); - return 0; /* keep compiler quiet */ -} - -/* * _hash_addovflpage * * Add an overflow page to the bucket whose last page is pointed to by 'buf'. @@ -485,7 +461,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); @@ -814,3 +790,29 @@ _hash_squeezebucket(Relation rel, /* NOTREACHED */ } + +/* + * _hash_ovflblkno_to_bitno + * + * Convert overflow page block number to bit number for free-page bitmap. + */ +uint32 +_hash_ovflblkno_to_bitno(HashMetaPage metap, BlockNumber ovflblkno) +{ + uint32 splitnum = metap->hashm_ovflpoint; + uint32 i; + uint32 bitnum; + + /* Determine the split number containing this page */ + for (i = 1; i <= splitnum; i++) + { + if (ovflblkno <= (BlockNumber) (1 << i)) + break; /* oops */ + bitnum = ovflblkno - (1 << i); + if (bitnum <= metap->hashm_spares[i]) + return bitnum - 1; /* -1 to convert 1-based to 0-based */ + } + + elog(ERROR, "invalid overflow block number %u", ovflblkno); + return 0; /* keep compiler quiet */ +} diff --git a/src/include/access/hash.h b/src/include/access/hash.h index b0a1131..60a801f 100644 --- a/src/include/access/hash.h +++ b/src/include/access/hash.h @@ -321,6 +321,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, -- 2.7.4 --------------E3F2B78EF4FEAA85B2598ED8 Content-Type: text/plain Content-Disposition: inline Content-Transfer-Encoding: 8bit MIME-Version: 1.0 -- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers --------------E3F2B78EF4FEAA85B2598ED8-- ^ permalink raw reply [nested|flat] 3+ messages in thread
* [PATCH v1 2/3] Bump transaction ID limit to warn at 100M. @ 2025-11-14 16:28 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 3+ messages in thread From: Nathan Bossart @ 2025-11-14 16:28 UTC (permalink / raw) --- doc/src/sgml/maintenance.sgml | 4 ++-- src/backend/access/transam/multixact.c | 6 +++--- src/backend/access/transam/varsup.c | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml index b89278ef032..8a12bd5f65a 100644 --- a/doc/src/sgml/maintenance.sgml +++ b/doc/src/sgml/maintenance.sgml @@ -670,7 +670,7 @@ SELECT datname, age(datfrozenxid) FROM pg_database; <para> If for some reason autovacuum fails to clear old XIDs from a table, the system will begin to emit warning messages like this when the database's - oldest XIDs reach forty million transactions from the wraparound point: + oldest XIDs reach one hundred million transactions from the wraparound point: <programlisting> WARNING: database "mydb" must be vacuumed within 39985967 transactions @@ -824,7 +824,7 @@ HINT: Execute a database-wide VACUUM in that database. <para> Similar to the XID case, if autovacuum fails to clear old MXIDs from a table, the - system will begin to emit warning messages when the database's oldest MXIDs reach forty + system will begin to emit warning messages when the database's oldest MXIDs reach one hundred million transactions from the wraparound point. And, just as in the XID case, if these warnings are ignored, the system will refuse to generate new MXIDs once there are fewer than three million left until wraparound. diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c index 63eb2548da1..67810ea489a 100644 --- a/src/backend/access/transam/multixact.c +++ b/src/backend/access/transam/multixact.c @@ -2327,16 +2327,16 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid, multiStopLimit -= FirstMultiXactId; /* - * We'll start complaining loudly when we get within 40M multis of data + * We'll start complaining loudly when we get within 100M multis of data * loss. This is kind of arbitrary, but if you let your gas gauge get - * down to 2% of full, would you be looking for the next gas station? We + * down to 5% of full, would you be looking for the next gas station? We * need to be fairly liberal about this number because there are lots of * scenarios where most transactions are done by automatic clients that * won't pay attention to warnings. (No, we're not gonna make this * configurable. If you know enough to configure it, you know enough to * not get in this kind of trouble in the first place.) */ - multiWarnLimit = multiWrapLimit - 40000000; + multiWarnLimit = multiWrapLimit - 100000000; if (multiWarnLimit < FirstMultiXactId) multiWarnLimit -= FirstMultiXactId; diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c index 962396bae10..5585381bc8c 100644 --- a/src/backend/access/transam/varsup.c +++ b/src/backend/access/transam/varsup.c @@ -411,16 +411,16 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid) xidStopLimit -= FirstNormalTransactionId; /* - * We'll start complaining loudly when we get within 40M transactions of + * We'll start complaining loudly when we get within 100M transactions of * data loss. This is kind of arbitrary, but if you let your gas gauge - * get down to 2% of full, would you be looking for the next gas station? + * get down to 5% of full, would you be looking for the next gas station? * We need to be fairly liberal about this number because there are lots * of scenarios where most transactions are done by automatic clients that * won't pay attention to warnings. (No, we're not gonna make this * configurable. If you know enough to configure it, you know enough to * not get in this kind of trouble in the first place.) */ - xidWarnLimit = xidWrapLimit - 40000000; + xidWarnLimit = xidWrapLimit - 100000000; if (xidWarnLimit < FirstNormalTransactionId) xidWarnLimit -= FirstNormalTransactionId; -- 2.39.5 (Apple Git-154) --i7+bPtDlPvAbCgMY Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0003-Perodically-emit-server-logs-when-fewer-than-500M.patch ^ permalink raw reply [nested|flat] 3+ messages in thread
end of thread, other threads:[~2025-11-14 16:28 UTC | newest] Thread overview: 3+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2017-01-03 12:49 [PATCH] Add support for hash index in pageinspect contrib module v11 jesperpedersen <jesper.pedersen@redhat.com> 2017-01-12 19:00 [PATCH] Add support for hash index in pageinspect contrib module v13 jesperpedersen <jesper.pedersen@redhat.com> 2025-11-14 16:28 [PATCH v1 2/3] Bump transaction ID limit to warn at 100M. Nathan Bossart <nathan@postgresql.org>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox