2 * contrib/pageinspect/btreefuncs.c
7 * Copyright (c) 2006 Satoshi Nagayasu <nagayasus@nttdata.co.jp>
9 * Permission to use, copy, modify, and distribute this software and
10 * its documentation for any purpose, without fee, and without a
11 * written agreement is hereby granted, provided that the above
12 * copyright notice and this paragraph and the following two
13 * paragraphs appear in all copies.
15 * IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT,
16 * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
17 * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
18 * DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED
19 * OF THE POSSIBILITY OF SUCH DAMAGE.
21 * THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 * A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS
24 * IS" BASIS, AND THE AUTHOR HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE,
25 * SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
30 #include "access/nbtree.h"
31 #include "access/relation.h"
32 #include "catalog/namespace.h"
33 #include "catalog/pg_am.h"
34 #include "catalog/pg_type.h"
36 #include "miscadmin.h"
37 #include "pageinspect.h"
38 #include "utils/array.h"
39 #include "utils/builtins.h"
40 #include "utils/rel.h"
41 #include "utils/varlena.h"
43 PG_FUNCTION_INFO_V1(bt_metap
);
44 PG_FUNCTION_INFO_V1(bt_page_items_1_9
);
45 PG_FUNCTION_INFO_V1(bt_page_items
);
46 PG_FUNCTION_INFO_V1(bt_page_items_bytea
);
47 PG_FUNCTION_INFO_V1(bt_page_stats_1_9
);
48 PG_FUNCTION_INFO_V1(bt_page_stats
);
49 PG_FUNCTION_INFO_V1(bt_multi_page_stats
);
51 #define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX)
52 #define IS_BTREE(r) ((r)->rd_rel->relam == BTREE_AM_OID)
54 /* ------------------------------------------------
55 * structure for single btree page statistics
56 * ------------------------------------------------
58 typedef struct BTPageStat
70 BlockNumber btpo_prev
;
71 BlockNumber btpo_next
;
74 BTCycleId btpo_cycleid
;
78 * cross-call data structure for SRF for page stats
80 typedef struct ua_page_stats
89 * cross-call data structure for SRF for page items
91 typedef struct ua_page_items
101 /* -------------------------------------------------
102 * GetBTPageStatistics()
104 * Collect statistics of single b-tree page
105 * -------------------------------------------------
108 GetBTPageStatistics(BlockNumber blkno
, Buffer buffer
, BTPageStat
*stat
)
110 Page page
= BufferGetPage(buffer
);
111 PageHeader phdr
= (PageHeader
) page
;
112 OffsetNumber maxoff
= PageGetMaxOffsetNumber(page
);
113 BTPageOpaque opaque
= BTPageGetOpaque(page
);
119 stat
->max_avail
= BLCKSZ
- (BLCKSZ
- phdr
->pd_special
+ SizeOfPageHeaderData
);
121 stat
->dead_items
= stat
->live_items
= 0;
123 stat
->page_size
= PageGetPageSize(page
);
125 /* page type (flags) */
126 if (P_ISDELETED(opaque
))
128 /* We divide deleted pages into leaf ('d') or internal ('D') */
129 if (P_ISLEAF(opaque
) || !P_HAS_FULLXID(opaque
))
135 * Report safexid in a deleted page.
137 * Handle pg_upgrade'd deleted pages that used the previous safexid
138 * representation in btpo_level field (this used to be a union type
141 if (P_HAS_FULLXID(opaque
))
143 FullTransactionId safexid
= BTPageGetDeleteXid(page
);
145 elog(DEBUG2
, "deleted page from block %u has safexid %u:%u",
146 blkno
, EpochFromFullTransactionId(safexid
),
147 XidFromFullTransactionId(safexid
));
150 elog(DEBUG2
, "deleted page from block %u has safexid %u",
151 blkno
, opaque
->btpo_level
);
153 /* Don't interpret BTDeletedPageData as index tuples */
154 maxoff
= InvalidOffsetNumber
;
156 else if (P_IGNORE(opaque
))
158 else if (P_ISLEAF(opaque
))
160 else if (P_ISROOT(opaque
))
165 /* btpage opaque data */
166 stat
->btpo_prev
= opaque
->btpo_prev
;
167 stat
->btpo_next
= opaque
->btpo_next
;
168 stat
->btpo_level
= opaque
->btpo_level
;
169 stat
->btpo_flags
= opaque
->btpo_flags
;
170 stat
->btpo_cycleid
= opaque
->btpo_cycleid
;
172 /* count live and dead tuples, and free space */
173 for (off
= FirstOffsetNumber
; off
<= maxoff
; off
++)
177 ItemId id
= PageGetItemId(page
, off
);
179 itup
= (IndexTuple
) PageGetItem(page
, id
);
181 item_size
+= IndexTupleSize(itup
);
183 if (!ItemIdIsDead(id
))
188 stat
->free_size
= PageGetFreeSpace(page
);
190 if ((stat
->live_items
+ stat
->dead_items
) > 0)
191 stat
->avg_item_size
= item_size
/ (stat
->live_items
+ stat
->dead_items
);
193 stat
->avg_item_size
= 0;
196 /* -----------------------------------------------
197 * check_relation_block_range()
199 * Verify that a block number (given as int64) is valid for the relation.
200 * -----------------------------------------------
203 check_relation_block_range(Relation rel
, int64 blkno
)
205 /* Ensure we can cast to BlockNumber */
206 if (blkno
< 0 || blkno
> MaxBlockNumber
)
208 (errcode(ERRCODE_INVALID_PARAMETER_VALUE
),
209 errmsg("invalid block number %lld",
210 (long long) blkno
)));
212 if ((BlockNumber
) (blkno
) >= RelationGetNumberOfBlocks(rel
))
214 (errcode(ERRCODE_INVALID_PARAMETER_VALUE
),
215 errmsg("block number %lld is out of range",
216 (long long) blkno
)));
219 /* -----------------------------------------------
220 * bt_index_block_validate()
222 * Validate index type is btree and block number
223 * is valid (and not the metapage).
224 * -----------------------------------------------
227 bt_index_block_validate(Relation rel
, int64 blkno
)
229 if (!IS_INDEX(rel
) || !IS_BTREE(rel
))
231 (errcode(ERRCODE_WRONG_OBJECT_TYPE
),
232 errmsg("\"%s\" is not a %s index",
233 RelationGetRelationName(rel
), "btree")));
236 * Reject attempts to read non-local temporary relations; we would be
237 * likely to get wrong data since we have no visibility into the owning
238 * session's local buffers.
240 if (RELATION_IS_OTHER_TEMP(rel
))
242 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED
),
243 errmsg("cannot access temporary tables of other sessions")));
247 (errcode(ERRCODE_INVALID_PARAMETER_VALUE
),
248 errmsg("block 0 is a meta page")));
250 check_relation_block_range(rel
, blkno
);
253 /* -----------------------------------------------
256 * Usage: SELECT * FROM bt_page_stats('t1_pkey', 1);
257 * Arguments are index relation name and block number
258 * -----------------------------------------------
261 bt_page_stats_internal(PG_FUNCTION_ARGS
, enum pageinspect_version ext_version
)
263 text
*relname
= PG_GETARG_TEXT_PP(0);
264 int64 blkno
= (ext_version
== PAGEINSPECT_V1_8
? PG_GETARG_UINT32(1) : PG_GETARG_INT64(1));
277 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE
),
278 errmsg("must be superuser to use pageinspect functions")));
280 relrv
= makeRangeVarFromNameList(textToQualifiedNameList(relname
));
281 rel
= relation_openrv(relrv
, AccessShareLock
);
283 bt_index_block_validate(rel
, blkno
);
285 buffer
= ReadBuffer(rel
, blkno
);
286 LockBuffer(buffer
, BUFFER_LOCK_SHARE
);
288 /* keep compiler quiet */
289 stat
.btpo_prev
= stat
.btpo_next
= InvalidBlockNumber
;
290 stat
.btpo_flags
= stat
.free_size
= stat
.avg_item_size
= 0;
292 GetBTPageStatistics(blkno
, buffer
, &stat
);
294 UnlockReleaseBuffer(buffer
);
295 relation_close(rel
, AccessShareLock
);
297 /* Build a tuple descriptor for our result type */
298 if (get_call_result_type(fcinfo
, NULL
, &tupleDesc
) != TYPEFUNC_COMPOSITE
)
299 elog(ERROR
, "return type must be a row type");
302 values
[j
++] = psprintf("%u", stat
.blkno
);
303 values
[j
++] = psprintf("%c", stat
.type
);
304 values
[j
++] = psprintf("%u", stat
.live_items
);
305 values
[j
++] = psprintf("%u", stat
.dead_items
);
306 values
[j
++] = psprintf("%u", stat
.avg_item_size
);
307 values
[j
++] = psprintf("%u", stat
.page_size
);
308 values
[j
++] = psprintf("%u", stat
.free_size
);
309 values
[j
++] = psprintf("%u", stat
.btpo_prev
);
310 values
[j
++] = psprintf("%u", stat
.btpo_next
);
311 values
[j
++] = psprintf("%u", stat
.btpo_level
);
312 values
[j
++] = psprintf("%d", stat
.btpo_flags
);
314 tuple
= BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc
),
317 result
= HeapTupleGetDatum(tuple
);
319 PG_RETURN_DATUM(result
);
323 bt_page_stats_1_9(PG_FUNCTION_ARGS
)
325 return bt_page_stats_internal(fcinfo
, PAGEINSPECT_V1_9
);
328 /* entry point for old extension version */
330 bt_page_stats(PG_FUNCTION_ARGS
)
332 return bt_page_stats_internal(fcinfo
, PAGEINSPECT_V1_8
);
336 /* -----------------------------------------------
337 * bt_multi_page_stats()
339 * Usage: SELECT * FROM bt_page_stats('t1_pkey', 1, 2);
340 * Arguments are index relation name, first block number, number of blocks
341 * (but number of blocks can be negative to mean "read all the rest")
342 * -----------------------------------------------
345 bt_multi_page_stats(PG_FUNCTION_ARGS
)
348 ua_page_stats
*uargs
;
349 FuncCallContext
*fctx
;
354 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE
),
355 errmsg("must be superuser to use pageinspect functions")));
357 if (SRF_IS_FIRSTCALL())
359 text
*relname
= PG_GETARG_TEXT_PP(0);
360 int64 blkno
= PG_GETARG_INT64(1);
361 int64 blk_count
= PG_GETARG_INT64(2);
364 fctx
= SRF_FIRSTCALL_INIT();
366 relrv
= makeRangeVarFromNameList(textToQualifiedNameList(relname
));
367 rel
= relation_openrv(relrv
, AccessShareLock
);
369 /* Check that rel is a valid btree index and 1st block number is OK */
370 bt_index_block_validate(rel
, blkno
);
373 * Check if upper bound of the specified range is valid. If only one
374 * page is requested, skip as we've already validated the page. (Also,
375 * it's important to skip this if blk_count is negative.)
378 check_relation_block_range(rel
, blkno
+ blk_count
- 1);
380 /* Save arguments for reuse */
381 mctx
= MemoryContextSwitchTo(fctx
->multi_call_memory_ctx
);
383 uargs
= palloc(sizeof(ua_page_stats
));
385 uargs
->relid
= RelationGetRelid(rel
);
386 uargs
->blkno
= blkno
;
387 uargs
->blk_count
= blk_count
;
388 uargs
->allpages
= (blk_count
< 0);
390 fctx
->user_fctx
= uargs
;
392 MemoryContextSwitchTo(mctx
);
395 * To avoid possibly leaking a relcache reference if the SRF isn't run
396 * to completion, we close and re-open the index rel each time
397 * through, using the index's OID for re-opens to ensure we get the
398 * same rel. Keep the AccessShareLock though, to ensure it doesn't go
399 * away underneath us.
401 relation_close(rel
, NoLock
);
404 fctx
= SRF_PERCALL_SETUP();
405 uargs
= fctx
->user_fctx
;
407 /* We should have lock already */
408 rel
= relation_open(uargs
->relid
, NoLock
);
410 /* In all-pages mode, recheck the index length each time */
412 uargs
->blk_count
= RelationGetNumberOfBlocks(rel
) - uargs
->blkno
;
414 if (uargs
->blk_count
> 0)
416 /* We need to fetch next block statistics */
425 buffer
= ReadBuffer(rel
, uargs
->blkno
);
426 LockBuffer(buffer
, BUFFER_LOCK_SHARE
);
428 /* keep compiler quiet */
429 stat
.btpo_prev
= stat
.btpo_next
= InvalidBlockNumber
;
430 stat
.btpo_flags
= stat
.free_size
= stat
.avg_item_size
= 0;
432 GetBTPageStatistics(uargs
->blkno
, buffer
, &stat
);
434 UnlockReleaseBuffer(buffer
);
435 relation_close(rel
, NoLock
);
437 /* Build a tuple descriptor for our result type */
438 if (get_call_result_type(fcinfo
, NULL
, &tupleDesc
) != TYPEFUNC_COMPOSITE
)
439 elog(ERROR
, "return type must be a row type");
442 values
[j
++] = psprintf("%u", stat
.blkno
);
443 values
[j
++] = psprintf("%c", stat
.type
);
444 values
[j
++] = psprintf("%u", stat
.live_items
);
445 values
[j
++] = psprintf("%u", stat
.dead_items
);
446 values
[j
++] = psprintf("%u", stat
.avg_item_size
);
447 values
[j
++] = psprintf("%u", stat
.page_size
);
448 values
[j
++] = psprintf("%u", stat
.free_size
);
449 values
[j
++] = psprintf("%u", stat
.btpo_prev
);
450 values
[j
++] = psprintf("%u", stat
.btpo_next
);
451 values
[j
++] = psprintf("%u", stat
.btpo_level
);
452 values
[j
++] = psprintf("%d", stat
.btpo_flags
);
454 /* Construct tuple to be returned */
455 tuple
= BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc
),
458 result
= HeapTupleGetDatum(tuple
);
461 * Move to the next block number and decrement the number of blocks
462 * still to be fetched
467 SRF_RETURN_NEXT(fctx
, result
);
470 /* Done, so finally we can release the index lock */
471 relation_close(rel
, AccessShareLock
);
472 SRF_RETURN_DONE(fctx
);
475 /*-------------------------------------------------------
476 * bt_page_print_tuples()
478 * Form a tuple describing index tuple at a given offset
479 * ------------------------------------------------------
482 bt_page_print_tuples(ua_page_items
*uargs
)
484 Page page
= uargs
->page
;
485 OffsetNumber offset
= uargs
->offset
;
486 bool leafpage
= uargs
->leafpage
;
487 bool rightmost
= uargs
->rightmost
;
502 id
= PageGetItemId(page
, offset
);
504 if (!ItemIdIsValid(id
))
505 elog(ERROR
, "invalid ItemId");
507 itup
= (IndexTuple
) PageGetItem(page
, id
);
510 memset(nulls
, 0, sizeof(nulls
));
511 values
[j
++] = DatumGetInt16(offset
);
512 values
[j
++] = ItemPointerGetDatum(&itup
->t_tid
);
513 values
[j
++] = Int32GetDatum((int) IndexTupleSize(itup
));
514 values
[j
++] = BoolGetDatum(IndexTupleHasNulls(itup
));
515 values
[j
++] = BoolGetDatum(IndexTupleHasVarwidths(itup
));
517 ptr
= (char *) itup
+ IndexInfoFindDataOffset(itup
->t_info
);
518 dlen
= IndexTupleSize(itup
) - IndexInfoFindDataOffset(itup
->t_info
);
521 * Make sure that "data" column does not include posting list or pivot
522 * tuple representation of heap TID(s).
524 * Note: BTreeTupleIsPivot() won't work reliably on !heapkeyspace indexes
525 * (those built before BTREE_VERSION 4), but we have no way of determining
526 * if this page came from a !heapkeyspace index. We may only have a bytea
527 * nbtree page image to go on, so in general there is no metapage that we
530 * That's okay here because BTreeTupleIsPivot() can only return false for
531 * a !heapkeyspace pivot, never true for a !heapkeyspace non-pivot. Since
532 * heap TID isn't part of the keyspace in a !heapkeyspace index anyway,
533 * there cannot possibly be a pivot tuple heap TID representation that we
534 * fail to make an adjustment for. A !heapkeyspace index can have
535 * BTreeTupleIsPivot() return true (due to things like suffix truncation
536 * for INCLUDE indexes in Postgres v11), but when that happens
537 * BTreeTupleGetHeapTID() can be trusted to work reliably (i.e. return
540 * Note: BTreeTupleIsPosting() always works reliably, even with
541 * !heapkeyspace indexes.
543 if (BTreeTupleIsPosting(itup
))
544 dlen
-= IndexTupleSize(itup
) - BTreeTupleGetPostingOffset(itup
);
545 else if (BTreeTupleIsPivot(itup
) && BTreeTupleGetHeapTID(itup
) != NULL
)
546 dlen
-= MAXALIGN(sizeof(ItemPointerData
));
548 if (dlen
< 0 || dlen
> INDEX_SIZE_MASK
)
549 elog(ERROR
, "invalid tuple length %d for tuple at offset number %u",
551 dump
= palloc0(dlen
* 3 + 1);
553 for (off
= 0; off
< dlen
; off
++)
557 sprintf(dump
, "%02x", *(ptr
+ off
) & 0xff);
560 values
[j
++] = CStringGetTextDatum(datacstring
);
564 * We need to work around the BTreeTupleIsPivot() !heapkeyspace limitation
565 * again. Deduce whether or not tuple must be a pivot tuple based on
566 * whether or not the page is a leaf page, as well as the page offset
567 * number of the tuple.
569 ispivottuple
= (!leafpage
|| (!rightmost
&& offset
== P_HIKEY
));
571 /* LP_DEAD bit can never be set for pivot tuples, so show a NULL there */
573 values
[j
++] = BoolGetDatum(ItemIdIsDead(id
));
576 Assert(!ItemIdIsDead(id
));
580 htid
= BTreeTupleGetHeapTID(itup
);
581 if (ispivottuple
&& !BTreeTupleIsPivot(itup
))
583 /* Don't show bogus heap TID in !heapkeyspace pivot tuple */
588 values
[j
++] = ItemPointerGetDatum(htid
);
592 if (BTreeTupleIsPosting(itup
))
594 /* Build an array of item pointers */
599 tids
= BTreeTupleGetPosting(itup
);
600 nposting
= BTreeTupleGetNPosting(itup
);
601 tids_datum
= (Datum
*) palloc(nposting
* sizeof(Datum
));
602 for (int i
= 0; i
< nposting
; i
++)
603 tids_datum
[i
] = ItemPointerGetDatum(&tids
[i
]);
604 values
[j
++] = PointerGetDatum(construct_array_builtin(tids_datum
, nposting
, TIDOID
));
610 /* Build and return the result tuple */
611 tuple
= heap_form_tuple(uargs
->tupd
, values
, nulls
);
613 return HeapTupleGetDatum(tuple
);
616 /*-------------------------------------------------------
619 * Get IndexTupleData set in a btree page
621 * Usage: SELECT * FROM bt_page_items('t1_pkey', 1);
622 *-------------------------------------------------------
625 bt_page_items_internal(PG_FUNCTION_ARGS
, enum pageinspect_version ext_version
)
627 text
*relname
= PG_GETARG_TEXT_PP(0);
628 int64 blkno
= (ext_version
== PAGEINSPECT_V1_8
? PG_GETARG_UINT32(1) : PG_GETARG_INT64(1));
630 FuncCallContext
*fctx
;
632 ua_page_items
*uargs
;
636 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE
),
637 errmsg("must be superuser to use pageinspect functions")));
639 if (SRF_IS_FIRSTCALL())
647 fctx
= SRF_FIRSTCALL_INIT();
649 relrv
= makeRangeVarFromNameList(textToQualifiedNameList(relname
));
650 rel
= relation_openrv(relrv
, AccessShareLock
);
652 bt_index_block_validate(rel
, blkno
);
654 buffer
= ReadBuffer(rel
, blkno
);
655 LockBuffer(buffer
, BUFFER_LOCK_SHARE
);
658 * We copy the page into local storage to avoid holding pin on the
659 * buffer longer than we must, and possibly failing to release it at
660 * all if the calling query doesn't fetch all rows.
662 mctx
= MemoryContextSwitchTo(fctx
->multi_call_memory_ctx
);
664 uargs
= palloc(sizeof(ua_page_items
));
666 uargs
->page
= palloc(BLCKSZ
);
667 memcpy(uargs
->page
, BufferGetPage(buffer
), BLCKSZ
);
669 UnlockReleaseBuffer(buffer
);
670 relation_close(rel
, AccessShareLock
);
672 uargs
->offset
= FirstOffsetNumber
;
674 opaque
= BTPageGetOpaque(uargs
->page
);
676 if (!P_ISDELETED(opaque
))
677 fctx
->max_calls
= PageGetMaxOffsetNumber(uargs
->page
);
680 /* Don't interpret BTDeletedPageData as index tuples */
681 elog(NOTICE
, "page from block " INT64_FORMAT
" is deleted", blkno
);
684 uargs
->leafpage
= P_ISLEAF(opaque
);
685 uargs
->rightmost
= P_RIGHTMOST(opaque
);
687 /* Build a tuple descriptor for our result type */
688 if (get_call_result_type(fcinfo
, NULL
, &tupleDesc
) != TYPEFUNC_COMPOSITE
)
689 elog(ERROR
, "return type must be a row type");
690 tupleDesc
= BlessTupleDesc(tupleDesc
);
692 uargs
->tupd
= tupleDesc
;
694 fctx
->user_fctx
= uargs
;
696 MemoryContextSwitchTo(mctx
);
699 fctx
= SRF_PERCALL_SETUP();
700 uargs
= fctx
->user_fctx
;
702 if (fctx
->call_cntr
< fctx
->max_calls
)
704 result
= bt_page_print_tuples(uargs
);
706 SRF_RETURN_NEXT(fctx
, result
);
709 SRF_RETURN_DONE(fctx
);
713 bt_page_items_1_9(PG_FUNCTION_ARGS
)
715 return bt_page_items_internal(fcinfo
, PAGEINSPECT_V1_9
);
718 /* entry point for old extension version */
720 bt_page_items(PG_FUNCTION_ARGS
)
722 return bt_page_items_internal(fcinfo
, PAGEINSPECT_V1_8
);
725 /*-------------------------------------------------------
726 * bt_page_items_bytea()
728 * Get IndexTupleData set in a btree page
730 * Usage: SELECT * FROM bt_page_items(get_raw_page('t1_pkey', 1));
731 *-------------------------------------------------------
735 bt_page_items_bytea(PG_FUNCTION_ARGS
)
737 bytea
*raw_page
= PG_GETARG_BYTEA_P(0);
739 FuncCallContext
*fctx
;
740 ua_page_items
*uargs
;
744 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE
),
745 errmsg("must be superuser to use raw page functions")));
747 if (SRF_IS_FIRSTCALL())
753 fctx
= SRF_FIRSTCALL_INIT();
754 mctx
= MemoryContextSwitchTo(fctx
->multi_call_memory_ctx
);
756 uargs
= palloc(sizeof(ua_page_items
));
758 uargs
->page
= get_page_from_raw(raw_page
);
760 if (PageIsNew(uargs
->page
))
762 MemoryContextSwitchTo(mctx
);
766 uargs
->offset
= FirstOffsetNumber
;
768 /* verify the special space has the expected size */
769 if (PageGetSpecialSize(uargs
->page
) != MAXALIGN(sizeof(BTPageOpaqueData
)))
771 (errcode(ERRCODE_INVALID_PARAMETER_VALUE
),
772 errmsg("input page is not a valid %s page", "btree"),
773 errdetail("Expected special size %d, got %d.",
774 (int) MAXALIGN(sizeof(BTPageOpaqueData
)),
775 (int) PageGetSpecialSize(uargs
->page
))));
777 opaque
= BTPageGetOpaque(uargs
->page
);
779 if (P_ISMETA(opaque
))
781 (errcode(ERRCODE_INVALID_PARAMETER_VALUE
),
782 errmsg("block is a meta page")));
784 if (P_ISLEAF(opaque
) && opaque
->btpo_level
!= 0)
786 (errcode(ERRCODE_INVALID_PARAMETER_VALUE
),
787 errmsg("block is not a valid btree leaf page")));
789 if (P_ISDELETED(opaque
))
790 elog(NOTICE
, "page is deleted");
792 if (!P_ISDELETED(opaque
))
793 fctx
->max_calls
= PageGetMaxOffsetNumber(uargs
->page
);
796 /* Don't interpret BTDeletedPageData as index tuples */
797 elog(NOTICE
, "page from block is deleted");
800 uargs
->leafpage
= P_ISLEAF(opaque
);
801 uargs
->rightmost
= P_RIGHTMOST(opaque
);
803 /* Build a tuple descriptor for our result type */
804 if (get_call_result_type(fcinfo
, NULL
, &tupleDesc
) != TYPEFUNC_COMPOSITE
)
805 elog(ERROR
, "return type must be a row type");
806 tupleDesc
= BlessTupleDesc(tupleDesc
);
808 uargs
->tupd
= tupleDesc
;
810 fctx
->user_fctx
= uargs
;
812 MemoryContextSwitchTo(mctx
);
815 fctx
= SRF_PERCALL_SETUP();
816 uargs
= fctx
->user_fctx
;
818 if (fctx
->call_cntr
< fctx
->max_calls
)
820 result
= bt_page_print_tuples(uargs
);
822 SRF_RETURN_NEXT(fctx
, result
);
825 SRF_RETURN_DONE(fctx
);
828 /* Number of output arguments (columns) for bt_metap() */
829 #define BT_METAP_COLS_V1_8 9
831 /* ------------------------------------------------
834 * Get a btree's meta-page information
836 * Usage: SELECT * FROM bt_metap('t1_pkey')
837 * ------------------------------------------------
840 bt_metap(PG_FUNCTION_ARGS
)
842 text
*relname
= PG_GETARG_TEXT_PP(0);
846 BTMetaPageData
*metad
;
856 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE
),
857 errmsg("must be superuser to use pageinspect functions")));
859 relrv
= makeRangeVarFromNameList(textToQualifiedNameList(relname
));
860 rel
= relation_openrv(relrv
, AccessShareLock
);
862 if (!IS_INDEX(rel
) || !IS_BTREE(rel
))
864 (errcode(ERRCODE_WRONG_OBJECT_TYPE
),
865 errmsg("\"%s\" is not a %s index",
866 RelationGetRelationName(rel
), "btree")));
869 * Reject attempts to read non-local temporary relations; we would be
870 * likely to get wrong data since we have no visibility into the owning
871 * session's local buffers.
873 if (RELATION_IS_OTHER_TEMP(rel
))
875 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED
),
876 errmsg("cannot access temporary tables of other sessions")));
878 buffer
= ReadBuffer(rel
, 0);
879 LockBuffer(buffer
, BUFFER_LOCK_SHARE
);
881 page
= BufferGetPage(buffer
);
882 metad
= BTPageGetMeta(page
);
884 /* Build a tuple descriptor for our result type */
885 if (get_call_result_type(fcinfo
, NULL
, &tupleDesc
) != TYPEFUNC_COMPOSITE
)
886 elog(ERROR
, "return type must be a row type");
889 * We need a kluge here to detect API versions prior to 1.8. Earlier
890 * versions incorrectly used int4 for certain columns.
892 * There is no way to reliably avoid the problems created by the old
893 * function definition at this point, so insist that the user update the
896 if (tupleDesc
->natts
< BT_METAP_COLS_V1_8
)
898 (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION
),
899 errmsg("function has wrong number of declared columns"),
900 errhint("To resolve the problem, update the \"pageinspect\" extension to the latest version.")));
903 values
[j
++] = psprintf("%d", metad
->btm_magic
);
904 values
[j
++] = psprintf("%d", metad
->btm_version
);
905 values
[j
++] = psprintf(INT64_FORMAT
, (int64
) metad
->btm_root
);
906 values
[j
++] = psprintf(INT64_FORMAT
, (int64
) metad
->btm_level
);
907 values
[j
++] = psprintf(INT64_FORMAT
, (int64
) metad
->btm_fastroot
);
908 values
[j
++] = psprintf(INT64_FORMAT
, (int64
) metad
->btm_fastlevel
);
911 * Get values of extended metadata if available, use default values
912 * otherwise. Note that we rely on the assumption that btm_allequalimage
913 * is initialized to zero with indexes that were built on versions prior
914 * to Postgres 13 (just like _bt_metaversion()).
916 if (metad
->btm_version
>= BTREE_NOVAC_VERSION
)
918 values
[j
++] = psprintf(INT64_FORMAT
,
919 (int64
) metad
->btm_last_cleanup_num_delpages
);
920 values
[j
++] = psprintf("%f", metad
->btm_last_cleanup_num_heap_tuples
);
921 values
[j
++] = metad
->btm_allequalimage
? "t" : "f";
930 tuple
= BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc
),
933 result
= HeapTupleGetDatum(tuple
);
935 UnlockReleaseBuffer(buffer
);
936 relation_close(rel
, AccessShareLock
);
938 PG_RETURN_DATUM(result
);