psql: Add missing punctuation in help output
[pgsql.git] / contrib / pageinspect / btreefuncs.c
blob9cdc8e182b48363e843aa09e9bc5e072e4b32d30
1 /*
2 * contrib/pageinspect/btreefuncs.c
5 * 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.
28 #include "postgres.h"
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"
35 #include "funcapi.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
60 uint32 blkno;
61 uint32 live_items;
62 uint32 dead_items;
63 uint32 page_size;
64 uint32 max_avail;
65 uint32 free_size;
66 uint32 avg_item_size;
67 char type;
69 /* opaque data */
70 BlockNumber btpo_prev;
71 BlockNumber btpo_next;
72 uint32 btpo_level;
73 uint16 btpo_flags;
74 BTCycleId btpo_cycleid;
75 } BTPageStat;
78 * cross-call data structure for SRF for page stats
80 typedef struct ua_page_stats
82 Oid relid;
83 int64 blkno;
84 int64 blk_count;
85 bool allpages;
86 } ua_page_stats;
89 * cross-call data structure for SRF for page items
91 typedef struct ua_page_items
93 Page page;
94 OffsetNumber offset;
95 bool leafpage;
96 bool rightmost;
97 TupleDesc tupd;
98 } ua_page_items;
101 /* -------------------------------------------------
102 * GetBTPageStatistics()
104 * Collect statistics of single b-tree page
105 * -------------------------------------------------
107 static void
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);
114 int item_size = 0;
115 int off;
117 stat->blkno = blkno;
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))
130 stat->type = 'd';
131 else
132 stat->type = 'D';
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
139 * called "bpto").
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));
149 else
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))
157 stat->type = 'e';
158 else if (P_ISLEAF(opaque))
159 stat->type = 'l';
160 else if (P_ISROOT(opaque))
161 stat->type = 'r';
162 else
163 stat->type = 'i';
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++)
175 IndexTuple itup;
177 ItemId id = PageGetItemId(page, off);
179 itup = (IndexTuple) PageGetItem(page, id);
181 item_size += IndexTupleSize(itup);
183 if (!ItemIdIsDead(id))
184 stat->live_items++;
185 else
186 stat->dead_items++;
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);
192 else
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 * -----------------------------------------------
202 static void
203 check_relation_block_range(Relation rel, int64 blkno)
205 /* Ensure we can cast to BlockNumber */
206 if (blkno < 0 || blkno > MaxBlockNumber)
207 ereport(ERROR,
208 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
209 errmsg("invalid block number %lld",
210 (long long) blkno)));
212 if ((BlockNumber) (blkno) >= RelationGetNumberOfBlocks(rel))
213 ereport(ERROR,
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 * -----------------------------------------------
226 static void
227 bt_index_block_validate(Relation rel, int64 blkno)
229 if (!IS_INDEX(rel) || !IS_BTREE(rel))
230 ereport(ERROR,
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))
241 ereport(ERROR,
242 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
243 errmsg("cannot access temporary tables of other sessions")));
245 if (blkno == 0)
246 ereport(ERROR,
247 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
248 errmsg("block 0 is a meta page")));
250 check_relation_block_range(rel, blkno);
253 /* -----------------------------------------------
254 * bt_page_stats()
256 * Usage: SELECT * FROM bt_page_stats('t1_pkey', 1);
257 * Arguments are index relation name and block number
258 * -----------------------------------------------
260 static Datum
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));
265 Buffer buffer;
266 Relation rel;
267 RangeVar *relrv;
268 Datum result;
269 HeapTuple tuple;
270 TupleDesc tupleDesc;
271 int j;
272 char *values[11];
273 BTPageStat stat;
275 if (!superuser())
276 ereport(ERROR,
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");
301 j = 0;
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),
315 values);
317 result = HeapTupleGetDatum(tuple);
319 PG_RETURN_DATUM(result);
322 Datum
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 */
329 Datum
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 * -----------------------------------------------
344 Datum
345 bt_multi_page_stats(PG_FUNCTION_ARGS)
347 Relation rel;
348 ua_page_stats *uargs;
349 FuncCallContext *fctx;
350 MemoryContext mctx;
352 if (!superuser())
353 ereport(ERROR,
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);
362 RangeVar *relrv;
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.)
377 if (blk_count > 1)
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 */
411 if (uargs->allpages)
412 uargs->blk_count = RelationGetNumberOfBlocks(rel) - uargs->blkno;
414 if (uargs->blk_count > 0)
416 /* We need to fetch next block statistics */
417 Buffer buffer;
418 Datum result;
419 HeapTuple tuple;
420 int j;
421 char *values[11];
422 BTPageStat stat;
423 TupleDesc tupleDesc;
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");
441 j = 0;
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),
456 values);
458 result = HeapTupleGetDatum(tuple);
461 * Move to the next block number and decrement the number of blocks
462 * still to be fetched
464 uargs->blkno++;
465 uargs->blk_count--;
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 * ------------------------------------------------------
481 static Datum
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;
488 bool ispivottuple;
489 Datum values[9];
490 bool nulls[9];
491 HeapTuple tuple;
492 ItemId id;
493 IndexTuple itup;
494 int j;
495 int off;
496 int dlen;
497 char *dump,
498 *datacstring;
499 char *ptr;
500 ItemPointer htid;
502 id = PageGetItemId(page, offset);
504 if (!ItemIdIsValid(id))
505 elog(ERROR, "invalid ItemId");
507 itup = (IndexTuple) PageGetItem(page, id);
509 j = 0;
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
528 * can check.
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
538 * NULL).
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",
550 dlen, offset);
551 dump = palloc0(dlen * 3 + 1);
552 datacstring = dump;
553 for (off = 0; off < dlen; off++)
555 if (off > 0)
556 *dump++ = ' ';
557 sprintf(dump, "%02x", *(ptr + off) & 0xff);
558 dump += 2;
560 values[j++] = CStringGetTextDatum(datacstring);
561 pfree(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 */
572 if (!ispivottuple)
573 values[j++] = BoolGetDatum(ItemIdIsDead(id));
574 else
576 Assert(!ItemIdIsDead(id));
577 nulls[j++] = true;
580 htid = BTreeTupleGetHeapTID(itup);
581 if (ispivottuple && !BTreeTupleIsPivot(itup))
583 /* Don't show bogus heap TID in !heapkeyspace pivot tuple */
584 htid = NULL;
587 if (htid)
588 values[j++] = ItemPointerGetDatum(htid);
589 else
590 nulls[j++] = true;
592 if (BTreeTupleIsPosting(itup))
594 /* Build an array of item pointers */
595 ItemPointer tids;
596 Datum *tids_datum;
597 int nposting;
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));
605 pfree(tids_datum);
607 else
608 nulls[j++] = true;
610 /* Build and return the result tuple */
611 tuple = heap_form_tuple(uargs->tupd, values, nulls);
613 return HeapTupleGetDatum(tuple);
616 /*-------------------------------------------------------
617 * bt_page_items()
619 * Get IndexTupleData set in a btree page
621 * Usage: SELECT * FROM bt_page_items('t1_pkey', 1);
622 *-------------------------------------------------------
624 static Datum
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));
629 Datum result;
630 FuncCallContext *fctx;
631 MemoryContext mctx;
632 ua_page_items *uargs;
634 if (!superuser())
635 ereport(ERROR,
636 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
637 errmsg("must be superuser to use pageinspect functions")));
639 if (SRF_IS_FIRSTCALL())
641 RangeVar *relrv;
642 Relation rel;
643 Buffer buffer;
644 BTPageOpaque opaque;
645 TupleDesc tupleDesc;
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);
678 else
680 /* Don't interpret BTDeletedPageData as index tuples */
681 elog(NOTICE, "page from block " INT64_FORMAT " is deleted", blkno);
682 fctx->max_calls = 0;
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);
705 uargs->offset++;
706 SRF_RETURN_NEXT(fctx, result);
709 SRF_RETURN_DONE(fctx);
712 Datum
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 */
719 Datum
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 *-------------------------------------------------------
734 Datum
735 bt_page_items_bytea(PG_FUNCTION_ARGS)
737 bytea *raw_page = PG_GETARG_BYTEA_P(0);
738 Datum result;
739 FuncCallContext *fctx;
740 ua_page_items *uargs;
742 if (!superuser())
743 ereport(ERROR,
744 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
745 errmsg("must be superuser to use raw page functions")));
747 if (SRF_IS_FIRSTCALL())
749 BTPageOpaque opaque;
750 MemoryContext mctx;
751 TupleDesc tupleDesc;
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);
763 PG_RETURN_NULL();
766 uargs->offset = FirstOffsetNumber;
768 /* verify the special space has the expected size */
769 if (PageGetSpecialSize(uargs->page) != MAXALIGN(sizeof(BTPageOpaqueData)))
770 ereport(ERROR,
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))
780 ereport(ERROR,
781 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
782 errmsg("block is a meta page")));
784 if (P_ISLEAF(opaque) && opaque->btpo_level != 0)
785 ereport(ERROR,
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);
794 else
796 /* Don't interpret BTDeletedPageData as index tuples */
797 elog(NOTICE, "page from block is deleted");
798 fctx->max_calls = 0;
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);
821 uargs->offset++;
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 /* ------------------------------------------------
832 * bt_metap()
834 * Get a btree's meta-page information
836 * Usage: SELECT * FROM bt_metap('t1_pkey')
837 * ------------------------------------------------
839 Datum
840 bt_metap(PG_FUNCTION_ARGS)
842 text *relname = PG_GETARG_TEXT_PP(0);
843 Datum result;
844 Relation rel;
845 RangeVar *relrv;
846 BTMetaPageData *metad;
847 TupleDesc tupleDesc;
848 int j;
849 char *values[9];
850 Buffer buffer;
851 Page page;
852 HeapTuple tuple;
854 if (!superuser())
855 ereport(ERROR,
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))
863 ereport(ERROR,
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))
874 ereport(ERROR,
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
894 * extension.
896 if (tupleDesc->natts < BT_METAP_COLS_V1_8)
897 ereport(ERROR,
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.")));
902 j = 0;
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";
923 else
925 values[j++] = "0";
926 values[j++] = "-1";
927 values[j++] = "f";
930 tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
931 values);
933 result = HeapTupleGetDatum(tuple);
935 UnlockReleaseBuffer(buffer);
936 relation_close(rel, AccessShareLock);
938 PG_RETURN_DATUM(result);