/* * contrib/pageinspect/btreefuncs.c * * * btreefuncs.c * * Copyright (c) 2006 Satoshi Nagayasu * * Permission to use, copy, modify, and distribute this software and * its documentation for any purpose, without fee, and without a * written agreement is hereby granted, provided that the above * copyright notice and this paragraph and the following two * paragraphs appear in all copies. * * IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS * DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS * IS" BASIS, AND THE AUTHOR HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, * SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. */ #include "postgres.h" #include "access/nbtree.h" #include "access/relation.h" #include "catalog/namespace.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" #include "utils/varlena.h" PG_FUNCTION_INFO_V1(bt_metap); PG_FUNCTION_INFO_V1(bt_page_items_1_9); PG_FUNCTION_INFO_V1(bt_page_items); PG_FUNCTION_INFO_V1(bt_page_items_bytea); PG_FUNCTION_INFO_V1(bt_page_stats_1_9); PG_FUNCTION_INFO_V1(bt_page_stats); PG_FUNCTION_INFO_V1(bt_multi_page_stats); #define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX) #define IS_BTREE(r) ((r)->rd_rel->relam == BTREE_AM_OID) /* ------------------------------------------------ * structure for single btree page statistics * ------------------------------------------------ */ typedef struct BTPageStat { uint32 blkno; uint32 live_items; uint32 dead_items; uint32 page_size; uint32 max_avail; uint32 free_size; uint32 avg_item_size; char type; /* opaque data */ BlockNumber btpo_prev; BlockNumber btpo_next; uint32 btpo_level; uint16 btpo_flags; BTCycleId btpo_cycleid; } BTPageStat; /* * cross-call data structure for SRF for page stats */ typedef struct ua_page_stats { Oid relid; int64 blkno; int64 blk_count; bool allpages; } ua_page_stats; /* * cross-call data structure for SRF for page items */ typedef struct ua_page_items { Page page; OffsetNumber offset; bool leafpage; bool rightmost; TupleDesc tupd; } ua_page_items; /* ------------------------------------------------- * GetBTPageStatistics() * * Collect statistics of single b-tree page * ------------------------------------------------- */ static void GetBTPageStatistics(BlockNumber blkno, Buffer buffer, BTPageStat *stat) { Page page = BufferGetPage(buffer); PageHeader phdr = (PageHeader) page; OffsetNumber maxoff = PageGetMaxOffsetNumber(page); BTPageOpaque opaque = BTPageGetOpaque(page); int item_size = 0; int off; stat->blkno = blkno; stat->max_avail = BLCKSZ - (BLCKSZ - phdr->pd_special + SizeOfPageHeaderData); stat->dead_items = stat->live_items = 0; stat->page_size = PageGetPageSize(page); /* page type (flags) */ if (P_ISDELETED(opaque)) { /* We divide deleted pages into leaf ('d') or internal ('D') */ if (P_ISLEAF(opaque) || !P_HAS_FULLXID(opaque)) stat->type = 'd'; else stat->type = 'D'; /* * Report safexid in a deleted page. * * Handle pg_upgrade'd deleted pages that used the previous safexid * representation in btpo_level field (this used to be a union type * called "bpto"). */ if (P_HAS_FULLXID(opaque)) { FullTransactionId safexid = BTPageGetDeleteXid(page); elog(DEBUG2, "deleted page from block %u has safexid %u:%u", blkno, EpochFromFullTransactionId(safexid), XidFromFullTransactionId(safexid)); } else elog(DEBUG2, "deleted page from block %u has safexid %u", blkno, opaque->btpo_level); /* Don't interpret BTDeletedPageData as index tuples */ maxoff = InvalidOffsetNumber; } else if (P_IGNORE(opaque)) stat->type = 'e'; else if (P_ISLEAF(opaque)) stat->type = 'l'; else if (P_ISROOT(opaque)) stat->type = 'r'; else stat->type = 'i'; /* btpage opaque data */ stat->btpo_prev = opaque->btpo_prev; stat->btpo_next = opaque->btpo_next; stat->btpo_level = opaque->btpo_level; stat->btpo_flags = opaque->btpo_flags; stat->btpo_cycleid = opaque->btpo_cycleid; /* count live and dead tuples, and free space */ for (off = FirstOffsetNumber; off <= maxoff; off++) { IndexTuple itup; ItemId id = PageGetItemId(page, off); itup = (IndexTuple) PageGetItem(page, id); item_size += IndexTupleSize(itup); if (!ItemIdIsDead(id)) stat->live_items++; else stat->dead_items++; } stat->free_size = PageGetFreeSpace(page); if ((stat->live_items + stat->dead_items) > 0) stat->avg_item_size = item_size / (stat->live_items + stat->dead_items); else stat->avg_item_size = 0; } /* ----------------------------------------------- * check_relation_block_range() * * Verify that a block number (given as int64) is valid for the relation. * ----------------------------------------------- */ static void check_relation_block_range(Relation rel, int64 blkno) { /* Ensure we can cast to BlockNumber */ if (blkno < 0 || blkno > MaxBlockNumber) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid block number %lld", (long long) blkno))); if ((BlockNumber) (blkno) >= RelationGetNumberOfBlocks(rel)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("block number %lld is out of range", (long long) blkno))); } /* ----------------------------------------------- * bt_index_block_validate() * * Validate index type is btree and block number * is valid (and not the metapage). * ----------------------------------------------- */ static void bt_index_block_validate(Relation rel, int64 blkno) { if (!IS_INDEX(rel) || !IS_BTREE(rel)) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a %s index", RelationGetRelationName(rel), "btree"))); /* * Reject attempts to read non-local temporary relations; we would be * likely to get wrong data since we have no visibility into the owning * session's local buffers. */ if (RELATION_IS_OTHER_TEMP(rel)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot access temporary tables of other sessions"))); if (blkno == 0) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("block 0 is a meta page"))); check_relation_block_range(rel, blkno); } /* ----------------------------------------------- * bt_page_stats() * * Usage: SELECT * FROM bt_page_stats('t1_pkey', 1); * Arguments are index relation name and block number * ----------------------------------------------- */ static Datum bt_page_stats_internal(PG_FUNCTION_ARGS, enum pageinspect_version ext_version) { text *relname = PG_GETARG_TEXT_PP(0); int64 blkno = (ext_version == PAGEINSPECT_V1_8 ? PG_GETARG_UINT32(1) : PG_GETARG_INT64(1)); Buffer buffer; Relation rel; RangeVar *relrv; Datum result; HeapTuple tuple; TupleDesc tupleDesc; int j; char *values[11]; BTPageStat stat; if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to use pageinspect functions"))); relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); rel = relation_openrv(relrv, AccessShareLock); bt_index_block_validate(rel, blkno); buffer = ReadBuffer(rel, blkno); LockBuffer(buffer, BUFFER_LOCK_SHARE); /* keep compiler quiet */ stat.btpo_prev = stat.btpo_next = InvalidBlockNumber; stat.btpo_flags = stat.free_size = stat.avg_item_size = 0; GetBTPageStatistics(blkno, buffer, &stat); UnlockReleaseBuffer(buffer); relation_close(rel, 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"); j = 0; values[j++] = psprintf("%u", stat.blkno); values[j++] = psprintf("%c", stat.type); values[j++] = psprintf("%u", stat.live_items); values[j++] = psprintf("%u", stat.dead_items); values[j++] = psprintf("%u", stat.avg_item_size); values[j++] = psprintf("%u", stat.page_size); values[j++] = psprintf("%u", stat.free_size); values[j++] = psprintf("%u", stat.btpo_prev); values[j++] = psprintf("%u", stat.btpo_next); values[j++] = psprintf("%u", stat.btpo_level); values[j++] = psprintf("%d", stat.btpo_flags); tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc), values); result = HeapTupleGetDatum(tuple); PG_RETURN_DATUM(result); } Datum bt_page_stats_1_9(PG_FUNCTION_ARGS) { return bt_page_stats_internal(fcinfo, PAGEINSPECT_V1_9); } /* entry point for old extension version */ Datum bt_page_stats(PG_FUNCTION_ARGS) { return bt_page_stats_internal(fcinfo, PAGEINSPECT_V1_8); } /* ----------------------------------------------- * bt_multi_page_stats() * * Usage: SELECT * FROM bt_page_stats('t1_pkey', 1, 2); * Arguments are index relation name, first block number, number of blocks * (but number of blocks can be negative to mean "read all the rest") * ----------------------------------------------- */ Datum bt_multi_page_stats(PG_FUNCTION_ARGS) { Relation rel; ua_page_stats *uargs; FuncCallContext *fctx; MemoryContext mctx; if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to use pageinspect functions"))); if (SRF_IS_FIRSTCALL()) { text *relname = PG_GETARG_TEXT_PP(0); int64 blkno = PG_GETARG_INT64(1); int64 blk_count = PG_GETARG_INT64(2); RangeVar *relrv; fctx = SRF_FIRSTCALL_INIT(); relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); rel = relation_openrv(relrv, AccessShareLock); /* Check that rel is a valid btree index and 1st block number is OK */ bt_index_block_validate(rel, blkno); /* * Check if upper bound of the specified range is valid. If only one * page is requested, skip as we've already validated the page. (Also, * it's important to skip this if blk_count is negative.) */ if (blk_count > 1) check_relation_block_range(rel, blkno + blk_count - 1); /* Save arguments for reuse */ mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx); uargs = palloc(sizeof(ua_page_stats)); uargs->relid = RelationGetRelid(rel); uargs->blkno = blkno; uargs->blk_count = blk_count; uargs->allpages = (blk_count < 0); fctx->user_fctx = uargs; MemoryContextSwitchTo(mctx); /* * To avoid possibly leaking a relcache reference if the SRF isn't run * to completion, we close and re-open the index rel each time * through, using the index's OID for re-opens to ensure we get the * same rel. Keep the AccessShareLock though, to ensure it doesn't go * away underneath us. */ relation_close(rel, NoLock); } fctx = SRF_PERCALL_SETUP(); uargs = fctx->user_fctx; /* We should have lock already */ rel = relation_open(uargs->relid, NoLock); /* In all-pages mode, recheck the index length each time */ if (uargs->allpages) uargs->blk_count = RelationGetNumberOfBlocks(rel) - uargs->blkno; if (uargs->blk_count > 0) { /* We need to fetch next block statistics */ Buffer buffer; Datum result; HeapTuple tuple; int j; char *values[11]; BTPageStat stat; TupleDesc tupleDesc; buffer = ReadBuffer(rel, uargs->blkno); LockBuffer(buffer, BUFFER_LOCK_SHARE); /* keep compiler quiet */ stat.btpo_prev = stat.btpo_next = InvalidBlockNumber; stat.btpo_flags = stat.free_size = stat.avg_item_size = 0; GetBTPageStatistics(uargs->blkno, buffer, &stat); UnlockReleaseBuffer(buffer); relation_close(rel, NoLock); /* 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"); j = 0; values[j++] = psprintf("%u", stat.blkno); values[j++] = psprintf("%c", stat.type); values[j++] = psprintf("%u", stat.live_items); values[j++] = psprintf("%u", stat.dead_items); values[j++] = psprintf("%u", stat.avg_item_size); values[j++] = psprintf("%u", stat.page_size); values[j++] = psprintf("%u", stat.free_size); values[j++] = psprintf("%u", stat.btpo_prev); values[j++] = psprintf("%u", stat.btpo_next); values[j++] = psprintf("%u", stat.btpo_level); values[j++] = psprintf("%d", stat.btpo_flags); /* Construct tuple to be returned */ tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc), values); result = HeapTupleGetDatum(tuple); /* * Move to the next block number and decrement the number of blocks * still to be fetched */ uargs->blkno++; uargs->blk_count--; SRF_RETURN_NEXT(fctx, result); } /* Done, so finally we can release the index lock */ relation_close(rel, AccessShareLock); SRF_RETURN_DONE(fctx); } /*------------------------------------------------------- * bt_page_print_tuples() * * Form a tuple describing index tuple at a given offset * ------------------------------------------------------ */ static Datum bt_page_print_tuples(ua_page_items *uargs) { Page page = uargs->page; OffsetNumber offset = uargs->offset; bool leafpage = uargs->leafpage; bool rightmost = uargs->rightmost; bool ispivottuple; Datum values[9]; bool nulls[9]; HeapTuple tuple; ItemId id; IndexTuple itup; int j; int off; int dlen; char *dump, *datacstring; char *ptr; ItemPointer htid; id = PageGetItemId(page, offset); if (!ItemIdIsValid(id)) elog(ERROR, "invalid ItemId"); itup = (IndexTuple) PageGetItem(page, id); j = 0; memset(nulls, 0, sizeof(nulls)); values[j++] = DatumGetInt16(offset); values[j++] = ItemPointerGetDatum(&itup->t_tid); values[j++] = Int32GetDatum((int) IndexTupleSize(itup)); values[j++] = BoolGetDatum(IndexTupleHasNulls(itup)); values[j++] = BoolGetDatum(IndexTupleHasVarwidths(itup)); ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info); dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info); /* * Make sure that "data" column does not include posting list or pivot * tuple representation of heap TID(s). * * Note: BTreeTupleIsPivot() won't work reliably on !heapkeyspace indexes * (those built before BTREE_VERSION 4), but we have no way of determining * if this page came from a !heapkeyspace index. We may only have a bytea * nbtree page image to go on, so in general there is no metapage that we * can check. * * That's okay here because BTreeTupleIsPivot() can only return false for * a !heapkeyspace pivot, never true for a !heapkeyspace non-pivot. Since * heap TID isn't part of the keyspace in a !heapkeyspace index anyway, * there cannot possibly be a pivot tuple heap TID representation that we * fail to make an adjustment for. A !heapkeyspace index can have * BTreeTupleIsPivot() return true (due to things like suffix truncation * for INCLUDE indexes in Postgres v11), but when that happens * BTreeTupleGetHeapTID() can be trusted to work reliably (i.e. return * NULL). * * Note: BTreeTupleIsPosting() always works reliably, even with * !heapkeyspace indexes. */ if (BTreeTupleIsPosting(itup)) dlen -= IndexTupleSize(itup) - BTreeTupleGetPostingOffset(itup); else if (BTreeTupleIsPivot(itup) && BTreeTupleGetHeapTID(itup) != NULL) dlen -= MAXALIGN(sizeof(ItemPointerData)); if (dlen < 0 || dlen > INDEX_SIZE_MASK) elog(ERROR, "invalid tuple length %d for tuple at offset number %u", dlen, offset); dump = palloc0(dlen * 3 + 1); datacstring = dump; for (off = 0; off < dlen; off++) { if (off > 0) *dump++ = ' '; sprintf(dump, "%02x", *(ptr + off) & 0xff); dump += 2; } values[j++] = CStringGetTextDatum(datacstring); pfree(datacstring); /* * We need to work around the BTreeTupleIsPivot() !heapkeyspace limitation * again. Deduce whether or not tuple must be a pivot tuple based on * whether or not the page is a leaf page, as well as the page offset * number of the tuple. */ ispivottuple = (!leafpage || (!rightmost && offset == P_HIKEY)); /* LP_DEAD bit can never be set for pivot tuples, so show a NULL there */ if (!ispivottuple) values[j++] = BoolGetDatum(ItemIdIsDead(id)); else { Assert(!ItemIdIsDead(id)); nulls[j++] = true; } htid = BTreeTupleGetHeapTID(itup); if (ispivottuple && !BTreeTupleIsPivot(itup)) { /* Don't show bogus heap TID in !heapkeyspace pivot tuple */ htid = NULL; } if (htid) values[j++] = ItemPointerGetDatum(htid); else nulls[j++] = true; if (BTreeTupleIsPosting(itup)) { /* Build an array of item pointers */ ItemPointer tids; Datum *tids_datum; int nposting; tids = BTreeTupleGetPosting(itup); nposting = BTreeTupleGetNPosting(itup); tids_datum = (Datum *) palloc(nposting * sizeof(Datum)); for (int i = 0; i < nposting; i++) tids_datum[i] = ItemPointerGetDatum(&tids[i]); values[j++] = PointerGetDatum(construct_array_builtin(tids_datum, nposting, TIDOID)); pfree(tids_datum); } else nulls[j++] = true; /* Build and return the result tuple */ tuple = heap_form_tuple(uargs->tupd, values, nulls); return HeapTupleGetDatum(tuple); } /*------------------------------------------------------- * bt_page_items() * * Get IndexTupleData set in a btree page * * Usage: SELECT * FROM bt_page_items('t1_pkey', 1); *------------------------------------------------------- */ static Datum bt_page_items_internal(PG_FUNCTION_ARGS, enum pageinspect_version ext_version) { text *relname = PG_GETARG_TEXT_PP(0); int64 blkno = (ext_version == PAGEINSPECT_V1_8 ? PG_GETARG_UINT32(1) : PG_GETARG_INT64(1)); Datum result; FuncCallContext *fctx; MemoryContext mctx; ua_page_items *uargs; if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to use pageinspect functions"))); if (SRF_IS_FIRSTCALL()) { RangeVar *relrv; Relation rel; Buffer buffer; BTPageOpaque opaque; TupleDesc tupleDesc; fctx = SRF_FIRSTCALL_INIT(); relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); rel = relation_openrv(relrv, AccessShareLock); bt_index_block_validate(rel, blkno); buffer = ReadBuffer(rel, blkno); LockBuffer(buffer, BUFFER_LOCK_SHARE); /* * 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(ua_page_items)); uargs->page = palloc(BLCKSZ); memcpy(uargs->page, BufferGetPage(buffer), BLCKSZ); UnlockReleaseBuffer(buffer); relation_close(rel, AccessShareLock); uargs->offset = FirstOffsetNumber; opaque = BTPageGetOpaque(uargs->page); if (!P_ISDELETED(opaque)) fctx->max_calls = PageGetMaxOffsetNumber(uargs->page); else { /* Don't interpret BTDeletedPageData as index tuples */ elog(NOTICE, "page from block " INT64_FORMAT " is deleted", blkno); fctx->max_calls = 0; } uargs->leafpage = P_ISLEAF(opaque); uargs->rightmost = P_RIGHTMOST(opaque); /* 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); uargs->tupd = tupleDesc; fctx->user_fctx = uargs; MemoryContextSwitchTo(mctx); } fctx = SRF_PERCALL_SETUP(); uargs = fctx->user_fctx; if (fctx->call_cntr < fctx->max_calls) { result = bt_page_print_tuples(uargs); uargs->offset++; SRF_RETURN_NEXT(fctx, result); } SRF_RETURN_DONE(fctx); } Datum bt_page_items_1_9(PG_FUNCTION_ARGS) { return bt_page_items_internal(fcinfo, PAGEINSPECT_V1_9); } /* entry point for old extension version */ Datum bt_page_items(PG_FUNCTION_ARGS) { return bt_page_items_internal(fcinfo, PAGEINSPECT_V1_8); } /*------------------------------------------------------- * bt_page_items_bytea() * * Get IndexTupleData set in a btree page * * Usage: SELECT * FROM bt_page_items(get_raw_page('t1_pkey', 1)); *------------------------------------------------------- */ Datum bt_page_items_bytea(PG_FUNCTION_ARGS) { bytea *raw_page = PG_GETARG_BYTEA_P(0); Datum result; FuncCallContext *fctx; ua_page_items *uargs; if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to use raw page functions"))); if (SRF_IS_FIRSTCALL()) { BTPageOpaque opaque; MemoryContext mctx; TupleDesc tupleDesc; fctx = SRF_FIRSTCALL_INIT(); mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx); uargs = palloc(sizeof(ua_page_items)); uargs->page = get_page_from_raw(raw_page); if (PageIsNew(uargs->page)) { MemoryContextSwitchTo(mctx); PG_RETURN_NULL(); } uargs->offset = FirstOffsetNumber; /* verify the special space has the expected size */ if (PageGetSpecialSize(uargs->page) != MAXALIGN(sizeof(BTPageOpaqueData))) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("input page is not a valid %s page", "btree"), errdetail("Expected special size %d, got %d.", (int) MAXALIGN(sizeof(BTPageOpaqueData)), (int) PageGetSpecialSize(uargs->page)))); opaque = BTPageGetOpaque(uargs->page); if (P_ISMETA(opaque)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("block is a meta page"))); if (P_ISLEAF(opaque) && opaque->btpo_level != 0) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("block is not a valid btree leaf page"))); if (P_ISDELETED(opaque)) elog(NOTICE, "page is deleted"); if (!P_ISDELETED(opaque)) fctx->max_calls = PageGetMaxOffsetNumber(uargs->page); else { /* Don't interpret BTDeletedPageData as index tuples */ elog(NOTICE, "page from block is deleted"); fctx->max_calls = 0; } uargs->leafpage = P_ISLEAF(opaque); uargs->rightmost = P_RIGHTMOST(opaque); /* 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); uargs->tupd = tupleDesc; fctx->user_fctx = uargs; MemoryContextSwitchTo(mctx); } fctx = SRF_PERCALL_SETUP(); uargs = fctx->user_fctx; if (fctx->call_cntr < fctx->max_calls) { result = bt_page_print_tuples(uargs); uargs->offset++; SRF_RETURN_NEXT(fctx, result); } SRF_RETURN_DONE(fctx); } /* Number of output arguments (columns) for bt_metap() */ #define BT_METAP_COLS_V1_8 9 /* ------------------------------------------------ * bt_metap() * * Get a btree's meta-page information * * Usage: SELECT * FROM bt_metap('t1_pkey') * ------------------------------------------------ */ Datum bt_metap(PG_FUNCTION_ARGS) { text *relname = PG_GETARG_TEXT_PP(0); Datum result; Relation rel; RangeVar *relrv; BTMetaPageData *metad; TupleDesc tupleDesc; int j; char *values[9]; Buffer buffer; Page page; HeapTuple tuple; if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to use pageinspect functions"))); relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); rel = relation_openrv(relrv, AccessShareLock); if (!IS_INDEX(rel) || !IS_BTREE(rel)) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a %s index", RelationGetRelationName(rel), "btree"))); /* * Reject attempts to read non-local temporary relations; we would be * likely to get wrong data since we have no visibility into the owning * session's local buffers. */ if (RELATION_IS_OTHER_TEMP(rel)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot access temporary tables of other sessions"))); buffer = ReadBuffer(rel, 0); LockBuffer(buffer, BUFFER_LOCK_SHARE); page = BufferGetPage(buffer); metad = BTPageGetMeta(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"); /* * We need a kluge here to detect API versions prior to 1.8. Earlier * versions incorrectly used int4 for certain columns. * * There is no way to reliably avoid the problems created by the old * function definition at this point, so insist that the user update the * extension. */ if (tupleDesc->natts < BT_METAP_COLS_V1_8) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("function has wrong number of declared columns"), errhint("To resolve the problem, update the \"pageinspect\" extension to the latest version."))); j = 0; values[j++] = psprintf("%d", metad->btm_magic); values[j++] = psprintf("%d", metad->btm_version); values[j++] = psprintf(INT64_FORMAT, (int64) metad->btm_root); values[j++] = psprintf(INT64_FORMAT, (int64) metad->btm_level); values[j++] = psprintf(INT64_FORMAT, (int64) metad->btm_fastroot); values[j++] = psprintf(INT64_FORMAT, (int64) metad->btm_fastlevel); /* * Get values of extended metadata if available, use default values * otherwise. Note that we rely on the assumption that btm_allequalimage * is initialized to zero with indexes that were built on versions prior * to Postgres 13 (just like _bt_metaversion()). */ if (metad->btm_version >= BTREE_NOVAC_VERSION) { values[j++] = psprintf(INT64_FORMAT, (int64) metad->btm_last_cleanup_num_delpages); values[j++] = psprintf("%f", metad->btm_last_cleanup_num_heap_tuples); values[j++] = metad->btm_allequalimage ? "t" : "f"; } else { values[j++] = "0"; values[j++] = "-1"; values[j++] = "f"; } tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc), values); result = HeapTupleGetDatum(tuple); UnlockReleaseBuffer(buffer); relation_close(rel, AccessShareLock); PG_RETURN_DATUM(result); }