Age Owner TLA Line data Source code
1 : /*
2 : * contrib/pageinspect/btreefuncs.c
3 : *
4 : *
5 : * btreefuncs.c
6 : *
7 : * Copyright (c) 2006 Satoshi Nagayasu <nagayasus@nttdata.co.jp>
8 : *
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.
14 : *
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.
20 : *
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.
26 : */
27 :
28 : #include "postgres.h"
29 :
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"
42 :
5705 tgl 43 CBC 25 : PG_FUNCTION_INFO_V1(bt_metap);
810 peter 44 7 : PG_FUNCTION_INFO_V1(bt_page_items_1_9);
5705 tgl 45 13 : PG_FUNCTION_INFO_V1(bt_page_items);
2196 peter_e 46 13 : PG_FUNCTION_INFO_V1(bt_page_items_bytea);
810 peter 47 7 : PG_FUNCTION_INFO_V1(bt_page_stats_1_9);
5705 tgl 48 7 : PG_FUNCTION_INFO_V1(bt_page_stats);
97 tgl 49 GNC 6 : PG_FUNCTION_INFO_V1(bt_multi_page_stats);
5806 bruce 50 ECB :
51 : #define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX)
52 : #define IS_BTREE(r) ((r)->rd_rel->relam == BTREE_AM_OID)
53 :
54 : /* ------------------------------------------------
55 : * structure for single btree page statistics
56 : * ------------------------------------------------
57 : */
58 : typedef struct BTPageStat
59 : {
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;
68 :
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;
76 :
77 : /*
78 : * cross-call data structure for SRF for page stats
79 : */
80 : typedef struct ua_page_stats
81 : {
82 : Oid relid;
83 : int64 blkno;
84 : int64 blk_count;
85 : bool allpages;
86 : } ua_page_stats;
87 :
88 : /*
89 : * cross-call data structure for SRF for page items
90 : */
91 : typedef struct ua_page_items
92 : {
93 : Page page;
94 : OffsetNumber offset;
95 : bool leafpage;
96 : bool rightmost;
97 : TupleDesc tupd;
98 : } ua_page_items;
99 :
100 :
101 : /* -------------------------------------------------
102 : * GetBTPageStatistics()
103 : *
104 : * Collect statistics of single b-tree page
105 : * -------------------------------------------------
106 : */
107 : static void
5050 bruce 108 GIC 10 : GetBTPageStatistics(BlockNumber blkno, Buffer buffer, BTPageStat *stat)
109 : {
2545 kgrittn 110 10 : Page page = BufferGetPage(buffer);
5806 bruce 111 10 : PageHeader phdr = (PageHeader) page;
112 10 : OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
373 michael 113 10 : BTPageOpaque opaque = BTPageGetOpaque(page);
5806 bruce 114 10 : int item_size = 0;
115 : int off;
116 :
117 10 : stat->blkno = blkno;
118 :
119 10 : stat->max_avail = BLCKSZ - (BLCKSZ - phdr->pd_special + SizeOfPageHeaderData);
120 :
121 10 : stat->dead_items = stat->live_items = 0;
122 :
123 10 : stat->page_size = PageGetPageSize(page);
124 :
5806 bruce 125 ECB : /* page type (flags) */
5806 bruce 126 GIC 10 : if (P_ISDELETED(opaque))
5806 bruce 127 ECB : {
774 pg 128 : /* We divide deleted pages into leaf ('d') or internal ('D') */
774 pg 129 LBC 0 : if (P_ISLEAF(opaque) || !P_HAS_FULLXID(opaque))
130 0 : stat->type = 'd';
774 pg 131 ECB : else
774 pg 132 UIC 0 : stat->type = 'D';
133 :
774 pg 134 ECB : /*
135 : * Report safexid in a deleted page.
136 : *
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").
140 : */
774 pg 141 UIC 0 : if (P_HAS_FULLXID(opaque))
142 : {
774 pg 143 LBC 0 : FullTransactionId safexid = BTPageGetDeleteXid(page);
144 :
569 pg 145 UIC 0 : elog(DEBUG2, "deleted page from block %u has safexid %u:%u",
774 pg 146 EUB : blkno, EpochFromFullTransactionId(safexid),
147 : XidFromFullTransactionId(safexid));
148 : }
149 : else
569 pg 150 UIC 0 : elog(DEBUG2, "deleted page from block %u has safexid %u",
151 : blkno, opaque->btpo_level);
152 :
153 : /* Don't interpret BTDeletedPageData as index tuples */
774 154 0 : maxoff = InvalidOffsetNumber;
155 : }
5806 bruce 156 GIC 10 : else if (P_IGNORE(opaque))
5806 bruce 157 UIC 0 : stat->type = 'e';
5806 bruce 158 GBC 10 : else if (P_ISLEAF(opaque))
5806 bruce 159 GIC 8 : stat->type = 'l';
5806 bruce 160 GBC 2 : else if (P_ISROOT(opaque))
5806 bruce 161 GIC 2 : stat->type = 'r';
5806 bruce 162 EUB : else
5806 bruce 163 UIC 0 : stat->type = 'i';
164 :
165 : /* btpage opaque data */
5806 bruce 166 GIC 10 : stat->btpo_prev = opaque->btpo_prev;
5806 bruce 167 GBC 10 : stat->btpo_next = opaque->btpo_next;
774 pg 168 GIC 10 : stat->btpo_level = opaque->btpo_level;
5806 bruce 169 10 : stat->btpo_flags = opaque->btpo_flags;
170 10 : stat->btpo_cycleid = opaque->btpo_cycleid;
5806 bruce 171 EUB :
172 : /* count live and dead tuples, and free space */
5806 bruce 173 CBC 2022 : for (off = FirstOffsetNumber; off <= maxoff; off++)
5806 bruce 174 EUB : {
5806 bruce 175 ECB : IndexTuple itup;
176 :
5806 bruce 177 CBC 2012 : ItemId id = PageGetItemId(page, off);
5806 bruce 178 ECB :
5806 bruce 179 GIC 2012 : itup = (IndexTuple) PageGetItem(page, id);
5806 bruce 180 EUB :
5806 bruce 181 GIC 2012 : item_size += IndexTupleSize(itup);
182 :
5688 tgl 183 CBC 2012 : if (!ItemIdIsDead(id))
5806 bruce 184 2012 : stat->live_items++;
5806 bruce 185 ECB : else
5806 bruce 186 LBC 0 : stat->dead_items++;
5806 bruce 187 ECB : }
5806 bruce 188 GIC 10 : stat->free_size = PageGetFreeSpace(page);
189 :
5806 bruce 190 CBC 10 : if ((stat->live_items + stat->dead_items) > 0)
5806 bruce 191 GIC 10 : stat->avg_item_size = item_size / (stat->live_items + stat->dead_items);
192 : else
5806 bruce 193 UIC 0 : stat->avg_item_size = 0;
5806 bruce 194 CBC 10 : }
195 :
196 : /* -----------------------------------------------
197 : * check_relation_block_range()
198 : *
199 : * Verify that a block number (given as int64) is valid for the relation.
200 : * -----------------------------------------------
201 : */
202 : static void
97 tgl 203 GNC 15 : check_relation_block_range(Relation rel, int64 blkno)
204 : {
205 : /* Ensure we can cast to BlockNumber */
206 15 : if (blkno < 0 || blkno > MaxBlockNumber)
5705 207 2 : ereport(ERROR,
208 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
209 : errmsg("invalid block number %lld",
210 : (long long) blkno)));
211 :
97 212 13 : if ((BlockNumber) (blkno) >= RelationGetNumberOfBlocks(rel))
213 3 : ereport(ERROR,
214 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
215 : errmsg("block number %lld is out of range",
216 : (long long) blkno)));
217 10 : }
218 :
219 : /* -----------------------------------------------
220 : * bt_index_block_validate()
221 : *
222 : * Validate index type is btree and block number
223 : * is valid (and not the metapage).
224 : * -----------------------------------------------
225 : */
226 : static void
227 18 : bt_index_block_validate(Relation rel, int64 blkno)
228 : {
5806 bruce 229 18 : if (!IS_INDEX(rel) || !IS_BTREE(rel))
389 michael 230 2 : ereport(ERROR,
231 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
232 : errmsg("\"%s\" is not a %s index",
233 : RelationGetRelationName(rel), "btree")));
234 :
235 : /*
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.
239 : */
5122 tgl 240 16 : if (RELATION_IS_OTHER_TEMP(rel))
5122 tgl 241 UNC 0 : ereport(ERROR,
242 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
243 : errmsg("cannot access temporary tables of other sessions")));
244 :
5806 bruce 245 GNC 16 : if (blkno == 0)
810 peter 246 3 : ereport(ERROR,
247 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
248 : errmsg("block 0 is a meta page")));
249 :
97 tgl 250 13 : check_relation_block_range(rel, blkno);
251 8 : }
252 :
97 tgl 253 ECB : /* -----------------------------------------------
254 : * bt_page_stats()
255 : *
256 : * Usage: SELECT * FROM bt_page_stats('t1_pkey', 1);
257 : * Arguments are index relation name and block number
258 : * -----------------------------------------------
259 : */
260 : static Datum
97 tgl 261 GBC 6 : bt_page_stats_internal(PG_FUNCTION_ARGS, enum pageinspect_version ext_version)
262 : {
97 tgl 263 CBC 6 : text *relname = PG_GETARG_TEXT_PP(0);
97 tgl 264 GIC 6 : int64 blkno = (ext_version == PAGEINSPECT_V1_8 ? PG_GETARG_UINT32(1) : PG_GETARG_INT64(1));
97 tgl 265 ECB : Buffer buffer;
266 : Relation rel;
267 : RangeVar *relrv;
97 tgl 268 EUB : Datum result;
97 tgl 269 ECB : HeapTuple tuple;
270 : TupleDesc tupleDesc;
271 : int j;
272 : char *values[11];
273 : BTPageStat stat;
274 :
97 tgl 275 GIC 6 : if (!superuser())
97 tgl 276 UIC 0 : ereport(ERROR,
277 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
97 tgl 278 ECB : errmsg("must be superuser to use pageinspect functions")));
279 :
97 tgl 280 GIC 6 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
97 tgl 281 CBC 6 : rel = relation_openrv(relrv, AccessShareLock);
97 tgl 282 ECB :
97 tgl 283 GNC 6 : bt_index_block_validate(rel, blkno);
284 :
5705 tgl 285 GIC 2 : buffer = ReadBuffer(rel, blkno);
3782 286 2 : LockBuffer(buffer, BUFFER_LOCK_SHARE);
287 :
288 : /* keep compiler quiet */
5705 tgl 289 CBC 2 : stat.btpo_prev = stat.btpo_next = InvalidBlockNumber;
5705 tgl 290 GBC 2 : stat.btpo_flags = stat.free_size = stat.avg_item_size = 0;
291 :
5705 tgl 292 GIC 2 : GetBTPageStatistics(blkno, buffer, &stat);
293 :
3782 tgl 294 CBC 2 : UnlockReleaseBuffer(buffer);
295 2 : relation_close(rel, AccessShareLock);
296 :
297 : /* Build a tuple descriptor for our result type */
5705 tgl 298 GIC 2 : if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
5705 tgl 299 LBC 0 : elog(ERROR, "return type must be a row type");
5705 tgl 300 ECB :
5705 tgl 301 GIC 2 : j = 0;
810 peter 302 2 : values[j++] = psprintf("%u", stat.blkno);
3380 peter_e 303 2 : values[j++] = psprintf("%c", stat.type);
810 peter 304 2 : values[j++] = psprintf("%u", stat.live_items);
305 2 : values[j++] = psprintf("%u", stat.dead_items);
306 2 : values[j++] = psprintf("%u", stat.avg_item_size);
307 2 : values[j++] = psprintf("%u", stat.page_size);
308 2 : values[j++] = psprintf("%u", stat.free_size);
309 2 : values[j++] = psprintf("%u", stat.btpo_prev);
810 peter 310 CBC 2 : values[j++] = psprintf("%u", stat.btpo_next);
774 pg 311 GIC 2 : values[j++] = psprintf("%u", stat.btpo_level);
3380 peter_e 312 CBC 2 : values[j++] = psprintf("%d", stat.btpo_flags);
5806 bruce 313 ECB :
5705 tgl 314 GIC 2 : tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
315 : values);
316 :
317 2 : result = HeapTupleGetDatum(tuple);
318 :
5806 bruce 319 2 : PG_RETURN_DATUM(result);
320 : }
321 :
322 : Datum
810 peter 323 5 : bt_page_stats_1_9(PG_FUNCTION_ARGS)
810 peter 324 ECB : {
810 peter 325 GBC 5 : return bt_page_stats_internal(fcinfo, PAGEINSPECT_V1_9);
326 : }
327 :
328 : /* entry point for old extension version */
810 peter 329 ECB : Datum
810 peter 330 CBC 1 : bt_page_stats(PG_FUNCTION_ARGS)
331 : {
332 1 : return bt_page_stats_internal(fcinfo, PAGEINSPECT_V1_8);
333 : }
810 peter 334 ECB :
5705 tgl 335 :
336 : /* -----------------------------------------------
337 : * bt_multi_page_stats()
338 : *
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 : * -----------------------------------------------
5806 bruce 343 : */
344 : Datum
97 tgl 345 GNC 14 : bt_multi_page_stats(PG_FUNCTION_ARGS)
346 : {
347 : Relation rel;
348 : ua_page_stats *uargs;
349 : FuncCallContext *fctx;
350 : MemoryContext mctx;
351 :
352 14 : if (!superuser())
97 tgl 353 UNC 0 : ereport(ERROR,
354 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
355 : errmsg("must be superuser to use pageinspect functions")));
356 :
97 tgl 357 GNC 14 : if (SRF_IS_FIRSTCALL())
358 : {
359 6 : text *relname = PG_GETARG_TEXT_PP(0);
360 6 : int64 blkno = PG_GETARG_INT64(1);
361 6 : int64 blk_count = PG_GETARG_INT64(2);
362 : RangeVar *relrv;
363 :
364 6 : fctx = SRF_FIRSTCALL_INIT();
365 :
366 6 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
367 6 : rel = relation_openrv(relrv, AccessShareLock);
368 :
369 : /* Check that rel is a valid btree index and 1st block number is OK */
370 6 : bt_index_block_validate(rel, blkno);
371 :
372 : /*
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.)
376 : */
377 4 : if (blk_count > 1)
378 2 : check_relation_block_range(rel, blkno + blk_count - 1);
379 :
380 : /* Save arguments for reuse */
381 4 : mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
382 :
383 4 : uargs = palloc(sizeof(ua_page_stats));
384 :
385 4 : uargs->relid = RelationGetRelid(rel);
386 4 : uargs->blkno = blkno;
387 4 : uargs->blk_count = blk_count;
388 4 : uargs->allpages = (blk_count < 0);
389 :
390 4 : fctx->user_fctx = uargs;
391 :
392 4 : MemoryContextSwitchTo(mctx);
393 :
394 : /*
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.
400 : */
401 4 : relation_close(rel, NoLock);
402 : }
403 :
404 12 : fctx = SRF_PERCALL_SETUP();
405 12 : uargs = fctx->user_fctx;
406 :
407 : /* We should have lock already */
408 12 : rel = relation_open(uargs->relid, NoLock);
409 :
410 : /* In all-pages mode, recheck the index length each time */
411 12 : if (uargs->allpages)
412 5 : uargs->blk_count = RelationGetNumberOfBlocks(rel) - uargs->blkno;
413 :
414 12 : if (uargs->blk_count > 0)
415 : {
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;
424 :
425 8 : buffer = ReadBuffer(rel, uargs->blkno);
426 8 : LockBuffer(buffer, BUFFER_LOCK_SHARE);
427 :
428 : /* keep compiler quiet */
429 8 : stat.btpo_prev = stat.btpo_next = InvalidBlockNumber;
430 8 : stat.btpo_flags = stat.free_size = stat.avg_item_size = 0;
431 :
432 8 : GetBTPageStatistics(uargs->blkno, buffer, &stat);
433 :
434 8 : UnlockReleaseBuffer(buffer);
435 8 : relation_close(rel, NoLock);
436 :
437 : /* Build a tuple descriptor for our result type */
438 8 : if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
97 tgl 439 UNC 0 : elog(ERROR, "return type must be a row type");
440 :
97 tgl 441 GNC 8 : j = 0;
442 8 : values[j++] = psprintf("%u", stat.blkno);
443 8 : values[j++] = psprintf("%c", stat.type);
444 8 : values[j++] = psprintf("%u", stat.live_items);
445 8 : values[j++] = psprintf("%u", stat.dead_items);
446 8 : values[j++] = psprintf("%u", stat.avg_item_size);
447 8 : values[j++] = psprintf("%u", stat.page_size);
448 8 : values[j++] = psprintf("%u", stat.free_size);
449 8 : values[j++] = psprintf("%u", stat.btpo_prev);
450 8 : values[j++] = psprintf("%u", stat.btpo_next);
451 8 : values[j++] = psprintf("%u", stat.btpo_level);
452 8 : values[j++] = psprintf("%d", stat.btpo_flags);
453 :
454 : /* Construct tuple to be returned */
455 8 : tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
456 : values);
457 :
458 8 : result = HeapTupleGetDatum(tuple);
459 :
460 : /*
461 : * Move to the next block number and decrement the number of blocks
462 : * still to be fetched
463 : */
464 8 : uargs->blkno++;
465 8 : uargs->blk_count--;
466 :
467 8 : SRF_RETURN_NEXT(fctx, result);
468 : }
469 :
470 : /* Done, so finally we can release the index lock */
471 4 : relation_close(rel, AccessShareLock);
472 4 : SRF_RETURN_DONE(fctx);
473 : }
5806 bruce 474 ECB :
2196 peter_e 475 EUB : /*-------------------------------------------------------
476 : * bt_page_print_tuples()
2196 peter_e 477 ECB : *
478 : * Form a tuple describing index tuple at a given offset
479 : * ------------------------------------------------------
480 : */
481 : static Datum
97 tgl 482 GNC 3 : bt_page_print_tuples(ua_page_items *uargs)
2196 peter_e 483 ECB : {
1135 pg 484 CBC 3 : Page page = uargs->page;
485 3 : OffsetNumber offset = uargs->offset;
486 3 : bool leafpage = uargs->leafpage;
487 3 : bool rightmost = uargs->rightmost;
1135 pg 488 ECB : bool ispivottuple;
489 : Datum values[9];
490 : bool nulls[9];
491 : HeapTuple tuple;
492 : ItemId id;
2196 peter_e 493 : IndexTuple itup;
494 : int j;
495 : int off;
496 : int dlen;
497 : char *dump,
498 : *datacstring;
499 : char *ptr;
500 : ItemPointer htid;
501 :
2196 peter_e 502 GIC 3 : id = PageGetItemId(page, offset);
503 :
504 3 : if (!ItemIdIsValid(id))
2196 peter_e 505 UIC 0 : elog(ERROR, "invalid ItemId");
2196 peter_e 506 ECB :
2196 peter_e 507 GIC 3 : itup = (IndexTuple) PageGetItem(page, id);
2196 peter_e 508 ECB :
2196 peter_e 509 GIC 3 : j = 0;
1135 pg 510 3 : memset(nulls, 0, sizeof(nulls));
511 3 : values[j++] = DatumGetInt16(offset);
512 3 : values[j++] = ItemPointerGetDatum(&itup->t_tid);
513 3 : values[j++] = Int32GetDatum((int) IndexTupleSize(itup));
514 3 : values[j++] = BoolGetDatum(IndexTupleHasNulls(itup));
515 3 : values[j++] = BoolGetDatum(IndexTupleHasVarwidths(itup));
516 :
2196 peter_e 517 3 : ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
518 3 : dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info);
519 :
520 : /*
1135 pg 521 ECB : * Make sure that "data" column does not include posting list or pivot
522 : * tuple representation of heap TID(s).
523 : *
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.
1135 pg 529 EUB : *
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,
1135 pg 533 ECB : * 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).
539 : *
540 : * Note: BTreeTupleIsPosting() always works reliably, even with
541 : * !heapkeyspace indexes.
542 : */
1135 pg 543 CBC 3 : if (BTreeTupleIsPosting(itup))
1135 pg 544 UIC 0 : dlen -= IndexTupleSize(itup) - BTreeTupleGetPostingOffset(itup);
1135 pg 545 GIC 3 : else if (BTreeTupleIsPivot(itup) && BTreeTupleGetHeapTID(itup) != NULL)
1135 pg 546 LBC 0 : dlen -= MAXALIGN(sizeof(ItemPointerData));
547 :
1135 pg 548 GIC 3 : if (dlen < 0 || dlen > INDEX_SIZE_MASK)
1135 pg 549 UIC 0 : elog(ERROR, "invalid tuple length %d for tuple at offset number %u",
550 : dlen, offset);
2196 peter_e 551 GIC 3 : dump = palloc0(dlen * 3 + 1);
1135 pg 552 3 : datacstring = dump;
2196 peter_e 553 CBC 27 : for (off = 0; off < dlen; off++)
2196 peter_e 554 ECB : {
2196 peter_e 555 GIC 24 : if (off > 0)
556 21 : *dump++ = ' ';
2196 peter_e 557 CBC 24 : sprintf(dump, "%02x", *(ptr + off) & 0xff);
2196 peter_e 558 GIC 24 : dump += 2;
2196 peter_e 559 ECB : }
1135 pg 560 GIC 3 : values[j++] = CStringGetTextDatum(datacstring);
1135 pg 561 CBC 3 : pfree(datacstring);
1135 pg 562 ECB :
563 : /*
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.
568 : */
1135 pg 569 GIC 3 : ispivottuple = (!leafpage || (!rightmost && offset == P_HIKEY));
570 :
571 : /* LP_DEAD bit can never be set for pivot tuples, so show a NULL there */
572 3 : if (!ispivottuple)
573 3 : values[j++] = BoolGetDatum(ItemIdIsDead(id));
574 : else
575 : {
1135 pg 576 UIC 0 : Assert(!ItemIdIsDead(id));
1135 pg 577 LBC 0 : nulls[j++] = true;
578 : }
579 :
1135 pg 580 CBC 3 : htid = BTreeTupleGetHeapTID(itup);
581 3 : if (ispivottuple && !BTreeTupleIsPivot(itup))
582 : {
583 : /* Don't show bogus heap TID in !heapkeyspace pivot tuple */
1135 pg 584 LBC 0 : htid = NULL;
585 : }
586 :
1135 pg 587 CBC 3 : if (htid)
588 3 : values[j++] = ItemPointerGetDatum(htid);
589 : else
1135 pg 590 LBC 0 : nulls[j++] = true;
591 :
1135 pg 592 GIC 3 : if (BTreeTupleIsPosting(itup))
593 : {
594 : /* Build an array of item pointers */
595 : ItemPointer tids;
596 : Datum *tids_datum;
597 : int nposting;
598 :
1135 pg 599 UIC 0 : tids = BTreeTupleGetPosting(itup);
600 0 : nposting = BTreeTupleGetNPosting(itup);
1135 pg 601 LBC 0 : tids_datum = (Datum *) palloc(nposting * sizeof(Datum));
602 0 : for (int i = 0; i < nposting; i++)
1135 pg 603 UIC 0 : tids_datum[i] = ItemPointerGetDatum(&tids[i]);
282 peter 604 UNC 0 : values[j++] = PointerGetDatum(construct_array_builtin(tids_datum, nposting, TIDOID));
1135 pg 605 UIC 0 : pfree(tids_datum);
1135 pg 606 ECB : }
607 : else
1135 pg 608 GIC 3 : nulls[j++] = true;
609 :
1135 pg 610 ECB : /* Build and return the result tuple */
1135 pg 611 GBC 3 : tuple = heap_form_tuple(uargs->tupd, values, nulls);
612 :
2196 peter_e 613 CBC 3 : return HeapTupleGetDatum(tuple);
2196 peter_e 614 ECB : }
615 :
616 : /*-------------------------------------------------------
617 : * bt_page_items()
618 : *
619 : * Get IndexTupleData set in a btree page
620 : *
621 : * Usage: SELECT * FROM bt_page_items('t1_pkey', 1);
622 : *-------------------------------------------------------
623 : */
810 peter 624 : static Datum
810 peter 625 GIC 8 : bt_page_items_internal(PG_FUNCTION_ARGS, enum pageinspect_version ext_version)
626 : {
2219 noah 627 CBC 8 : text *relname = PG_GETARG_TEXT_PP(0);
810 peter 628 GIC 8 : int64 blkno = (ext_version == PAGEINSPECT_V1_8 ? PG_GETARG_UINT32(1) : PG_GETARG_INT64(1));
629 : Datum result;
5806 bruce 630 ECB : FuncCallContext *fctx;
631 : MemoryContext mctx;
632 : ua_page_items *uargs;
633 :
5705 tgl 634 GIC 8 : if (!superuser())
5705 tgl 635 UIC 0 : ereport(ERROR,
5705 tgl 636 ECB : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1165 alvherre 637 : errmsg("must be superuser to use pageinspect functions")));
638 :
5806 bruce 639 CBC 8 : if (SRF_IS_FIRSTCALL())
640 : {
641 : RangeVar *relrv;
642 : Relation rel;
5705 tgl 643 ECB : Buffer buffer;
644 : BTPageOpaque opaque;
645 : TupleDesc tupleDesc;
646 :
5806 bruce 647 GIC 6 : fctx = SRF_FIRSTCALL_INIT();
648 :
5705 tgl 649 6 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
650 6 : rel = relation_openrv(relrv, AccessShareLock);
651 :
97 tgl 652 GNC 6 : bt_index_block_validate(rel, blkno);
5806 bruce 653 ECB :
5705 tgl 654 GIC 2 : buffer = ReadBuffer(rel, blkno);
3782 tgl 655 CBC 2 : LockBuffer(buffer, BUFFER_LOCK_SHARE);
5806 bruce 656 ECB :
5705 tgl 657 : /*
5624 bruce 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.
5705 tgl 661 : */
5705 tgl 662 GIC 2 : mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
5705 tgl 663 ECB :
97 tgl 664 GNC 2 : uargs = palloc(sizeof(ua_page_items));
665 :
5705 tgl 666 GIC 2 : uargs->page = palloc(BLCKSZ);
2545 kgrittn 667 2 : memcpy(uargs->page, BufferGetPage(buffer), BLCKSZ);
668 :
3782 tgl 669 2 : UnlockReleaseBuffer(buffer);
5705 670 2 : relation_close(rel, AccessShareLock);
671 :
672 2 : uargs->offset = FirstOffsetNumber;
673 :
373 michael 674 2 : opaque = BTPageGetOpaque(uargs->page);
675 :
774 pg 676 2 : if (!P_ISDELETED(opaque))
677 2 : fctx->max_calls = PageGetMaxOffsetNumber(uargs->page);
678 : else
679 : {
680 : /* Don't interpret BTDeletedPageData as index tuples */
774 pg 681 UIC 0 : elog(NOTICE, "page from block " INT64_FORMAT " is deleted", blkno);
682 0 : fctx->max_calls = 0;
683 : }
1135 pg 684 GIC 2 : uargs->leafpage = P_ISLEAF(opaque);
685 2 : uargs->rightmost = P_RIGHTMOST(opaque);
686 :
687 : /* Build a tuple descriptor for our result type */
5705 tgl 688 2 : if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
5705 tgl 689 LBC 0 : elog(ERROR, "return type must be a row type");
1135 pg 690 GBC 2 : tupleDesc = BlessTupleDesc(tupleDesc);
5705 tgl 691 ECB :
1135 pg 692 GBC 2 : uargs->tupd = tupleDesc;
693 :
5806 bruce 694 CBC 2 : fctx->user_fctx = uargs;
5806 bruce 695 EUB :
5806 bruce 696 GIC 2 : MemoryContextSwitchTo(mctx);
5806 bruce 697 ECB : }
698 :
5806 bruce 699 CBC 4 : fctx = SRF_PERCALL_SETUP();
5806 bruce 700 GIC 4 : uargs = fctx->user_fctx;
5806 bruce 701 ECB :
5806 bruce 702 CBC 4 : if (fctx->call_cntr < fctx->max_calls)
5806 bruce 703 ECB : {
947 peter 704 CBC 2 : result = bt_page_print_tuples(uargs);
2196 peter_e 705 GIC 2 : uargs->offset++;
5806 bruce 706 CBC 2 : SRF_RETURN_NEXT(fctx, result);
5806 bruce 707 ECB : }
708 :
1119 tgl 709 GIC 2 : SRF_RETURN_DONE(fctx);
710 : }
711 :
712 : Datum
810 peter 713 6 : bt_page_items_1_9(PG_FUNCTION_ARGS)
714 : {
810 peter 715 CBC 6 : return bt_page_items_internal(fcinfo, PAGEINSPECT_V1_9);
716 : }
717 :
810 peter 718 ECB : /* entry point for old extension version */
719 : Datum
810 peter 720 GIC 2 : bt_page_items(PG_FUNCTION_ARGS)
721 : {
810 peter 722 GBC 2 : return bt_page_items_internal(fcinfo, PAGEINSPECT_V1_8);
810 peter 723 EUB : }
724 :
725 : /*-------------------------------------------------------
2196 peter_e 726 ECB : * bt_page_items_bytea()
727 : *
728 : * Get IndexTupleData set in a btree page
729 : *
2196 peter_e 730 EUB : * Usage: SELECT * FROM bt_page_items(get_raw_page('t1_pkey', 1));
731 : *-------------------------------------------------------
732 : */
2196 peter_e 733 ECB :
734 : Datum
2196 peter_e 735 GIC 9 : bt_page_items_bytea(PG_FUNCTION_ARGS)
2196 peter_e 736 EUB : {
2196 peter_e 737 GIC 9 : bytea *raw_page = PG_GETARG_BYTEA_P(0);
2196 peter_e 738 ECB : Datum result;
739 : FuncCallContext *fctx;
740 : ua_page_items *uargs;
741 :
2196 peter_e 742 GIC 9 : if (!superuser())
2196 peter_e 743 UIC 0 : ereport(ERROR,
744 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1165 alvherre 745 EUB : errmsg("must be superuser to use raw page functions")));
2196 peter_e 746 :
2196 peter_e 747 GBC 9 : if (SRF_IS_FIRSTCALL())
2196 peter_e 748 EUB : {
749 : BTPageOpaque opaque;
750 : MemoryContext mctx;
751 : TupleDesc tupleDesc;
752 :
2196 peter_e 753 GIC 8 : fctx = SRF_FIRSTCALL_INIT();
2196 peter_e 754 CBC 8 : mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
755 :
97 tgl 756 GNC 8 : uargs = palloc(sizeof(ua_page_items));
2196 peter_e 757 ECB :
389 michael 758 GIC 8 : uargs->page = get_page_from_raw(raw_page);
2196 peter_e 759 ECB :
360 michael 760 GIC 7 : if (PageIsNew(uargs->page))
761 : {
762 1 : MemoryContextSwitchTo(mctx);
763 1 : PG_RETURN_NULL();
764 : }
765 :
2196 peter_e 766 6 : uargs->offset = FirstOffsetNumber;
767 :
768 : /* verify the special space has the expected size */
378 michael 769 6 : if (PageGetSpecialSize(uargs->page) != MAXALIGN(sizeof(BTPageOpaqueData)))
770 2 : ereport(ERROR,
378 michael 771 ECB : (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))));
776 :
373 michael 777 GIC 4 : opaque = BTPageGetOpaque(uargs->page);
778 :
2196 peter_e 779 4 : if (P_ISMETA(opaque))
2196 peter_e 780 CBC 2 : ereport(ERROR,
2196 peter_e 781 EUB : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
782 : errmsg("block is a meta page")));
783 :
378 michael 784 GIC 2 : if (P_ISLEAF(opaque) && opaque->btpo_level != 0)
378 michael 785 CBC 1 : ereport(ERROR,
786 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
787 : errmsg("block is not a valid btree leaf page")));
788 :
2196 peter_e 789 GIC 1 : if (P_ISDELETED(opaque))
2196 peter_e 790 UIC 0 : elog(NOTICE, "page is deleted");
791 :
774 pg 792 GIC 1 : if (!P_ISDELETED(opaque))
774 pg 793 CBC 1 : fctx->max_calls = PageGetMaxOffsetNumber(uargs->page);
794 : else
774 pg 795 ECB : {
796 : /* Don't interpret BTDeletedPageData as index tuples */
774 pg 797 UIC 0 : elog(NOTICE, "page from block is deleted");
774 pg 798 LBC 0 : fctx->max_calls = 0;
799 : }
1135 pg 800 CBC 1 : uargs->leafpage = P_ISLEAF(opaque);
801 1 : uargs->rightmost = P_RIGHTMOST(opaque);
802 :
803 : /* Build a tuple descriptor for our result type */
2196 peter_e 804 GIC 1 : if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
2196 peter_e 805 UIC 0 : elog(ERROR, "return type must be a row type");
1135 pg 806 GIC 1 : tupleDesc = BlessTupleDesc(tupleDesc);
807 :
1135 pg 808 CBC 1 : uargs->tupd = tupleDesc;
809 :
2196 peter_e 810 1 : fctx->user_fctx = uargs;
811 :
812 1 : MemoryContextSwitchTo(mctx);
2196 peter_e 813 ECB : }
814 :
2196 peter_e 815 CBC 2 : fctx = SRF_PERCALL_SETUP();
816 2 : uargs = fctx->user_fctx;
817 :
818 2 : if (fctx->call_cntr < fctx->max_calls)
819 : {
947 peter 820 1 : result = bt_page_print_tuples(uargs);
2196 peter_e 821 GIC 1 : uargs->offset++;
2196 peter_e 822 CBC 1 : SRF_RETURN_NEXT(fctx, result);
2196 peter_e 823 ECB : }
824 :
1119 tgl 825 GIC 1 : SRF_RETURN_DONE(fctx);
826 : }
2196 peter_e 827 EUB :
1128 pg 828 : /* Number of output arguments (columns) for bt_metap() */
829 : #define BT_METAP_COLS_V1_8 9
5806 bruce 830 ECB :
831 : /* ------------------------------------------------
832 : * bt_metap()
833 : *
5705 tgl 834 : * Get a btree's meta-page information
5806 bruce 835 EUB : *
5806 bruce 836 ECB : * Usage: SELECT * FROM bt_metap('t1_pkey')
837 : * ------------------------------------------------
838 : */
839 : Datum
5806 bruce 840 CBC 2 : bt_metap(PG_FUNCTION_ARGS)
841 : {
2219 noah 842 2 : text *relname = PG_GETARG_TEXT_PP(0);
843 : Datum result;
844 : Relation rel;
5806 bruce 845 ECB : RangeVar *relrv;
5705 tgl 846 : BTMetaPageData *metad;
847 : TupleDesc tupleDesc;
848 : int j;
849 : char *values[9];
850 : Buffer buffer;
851 : Page page;
852 : HeapTuple tuple;
853 :
5705 tgl 854 GIC 2 : if (!superuser())
5705 tgl 855 LBC 0 : ereport(ERROR,
856 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
857 : errmsg("must be superuser to use pageinspect functions")));
858 :
5806 bruce 859 CBC 2 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
5806 bruce 860 GIC 2 : rel = relation_openrv(relrv, AccessShareLock);
5806 bruce 861 ECB :
5806 bruce 862 GIC 2 : if (!IS_INDEX(rel) || !IS_BTREE(rel))
389 michael 863 1 : ereport(ERROR,
864 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
865 : errmsg("\"%s\" is not a %s index",
389 michael 866 ECB : RelationGetRelationName(rel), "btree")));
867 :
5122 tgl 868 : /*
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.
872 : */
5122 tgl 873 GIC 1 : if (RELATION_IS_OTHER_TEMP(rel))
5122 tgl 874 UIC 0 : ereport(ERROR,
875 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
876 : errmsg("cannot access temporary tables of other sessions")));
877 :
5806 bruce 878 GIC 1 : buffer = ReadBuffer(rel, 0);
3782 tgl 879 1 : LockBuffer(buffer, BUFFER_LOCK_SHARE);
880 :
2545 kgrittn 881 CBC 1 : page = BufferGetPage(buffer);
5705 tgl 882 GIC 1 : metad = BTPageGetMeta(page);
5705 tgl 883 ECB :
884 : /* Build a tuple descriptor for our result type */
5705 tgl 885 GIC 1 : if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
5705 tgl 886 UIC 0 : elog(ERROR, "return type must be a row type");
887 :
1128 pg 888 ECB : /*
1128 pg 889 EUB : * We need a kluge here to detect API versions prior to 1.8. Earlier
890 : * versions incorrectly used int4 for certain columns.
891 : *
892 : * There is no way to reliably avoid the problems created by the old
1128 pg 893 ECB : * function definition at this point, so insist that the user update the
894 : * extension.
895 : */
1128 pg 896 GIC 1 : if (tupleDesc->natts < BT_METAP_COLS_V1_8)
1128 pg 897 UIC 0 : ereport(ERROR,
898 : (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
1128 pg 899 ECB : errmsg("function has wrong number of declared columns"),
900 : errhint("To resolve the problem, update the \"pageinspect\" extension to the latest version.")));
901 :
5705 tgl 902 CBC 1 : j = 0;
3380 peter_e 903 GIC 1 : values[j++] = psprintf("%d", metad->btm_magic);
3380 peter_e 904 CBC 1 : values[j++] = psprintf("%d", metad->btm_version);
1128 pg 905 GIC 1 : values[j++] = psprintf(INT64_FORMAT, (int64) metad->btm_root);
1128 pg 906 CBC 1 : values[j++] = psprintf(INT64_FORMAT, (int64) metad->btm_level);
1128 pg 907 GIC 1 : values[j++] = psprintf(INT64_FORMAT, (int64) metad->btm_fastroot);
1128 pg 908 CBC 1 : values[j++] = psprintf(INT64_FORMAT, (int64) metad->btm_fastlevel);
1830 teodor 909 ECB :
910 : /*
911 : * Get values of extended metadata if available, use default values
1135 pg 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()).
1830 teodor 915 : */
1481 pg 916 CBC 1 : if (metad->btm_version >= BTREE_NOVAC_VERSION)
917 : {
774 pg 918 GIC 2 : values[j++] = psprintf(INT64_FORMAT,
919 1 : (int64) metad->btm_last_cleanup_num_delpages);
1785 tgl 920 1 : values[j++] = psprintf("%f", metad->btm_last_cleanup_num_heap_tuples);
1135 pg 921 1 : values[j++] = metad->btm_allequalimage ? "t" : "f";
922 : }
1830 teodor 923 ECB : else
924 : {
1830 teodor 925 LBC 0 : values[j++] = "0";
926 0 : values[j++] = "-1";
1135 pg 927 UIC 0 : values[j++] = "f";
928 : }
929 :
5705 tgl 930 CBC 1 : tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
5705 tgl 931 ECB : values);
932 :
5705 tgl 933 GIC 1 : result = HeapTupleGetDatum(tuple);
934 :
3782 tgl 935 CBC 1 : UnlockReleaseBuffer(buffer);
5806 bruce 936 GBC 1 : relation_close(rel, AccessShareLock);
937 :
5806 bruce 938 CBC 1 : PG_RETURN_DATUM(result);
5806 bruce 939 ECB : }
|