Line data Source code
1 : /*
2 : * hashfuncs.c
3 : * Functions to investigate the content of HASH indexes
4 : *
5 : * Copyright (c) 2017-2024, PostgreSQL Global Development Group
6 : *
7 : * IDENTIFICATION
8 : * contrib/pageinspect/hashfuncs.c
9 : */
10 :
11 : #include "postgres.h"
12 :
13 : #include "access/hash.h"
14 : #include "access/htup_details.h"
15 : #include "access/relation.h"
16 : #include "catalog/pg_am.h"
17 : #include "catalog/pg_type.h"
18 : #include "funcapi.h"
19 : #include "miscadmin.h"
20 : #include "pageinspect.h"
21 : #include "utils/array.h"
22 : #include "utils/builtins.h"
23 : #include "utils/rel.h"
24 :
25 14 : PG_FUNCTION_INFO_V1(hash_page_type);
26 14 : PG_FUNCTION_INFO_V1(hash_page_stats);
27 14 : PG_FUNCTION_INFO_V1(hash_page_items);
28 14 : PG_FUNCTION_INFO_V1(hash_bitmap_info);
29 14 : PG_FUNCTION_INFO_V1(hash_metapage_info);
30 :
31 : #define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX)
32 : #define IS_HASH(r) ((r)->rd_rel->relam == HASH_AM_OID)
33 :
34 : /* ------------------------------------------------
35 : * structure for single hash page statistics
36 : * ------------------------------------------------
37 : */
38 : typedef struct HashPageStat
39 : {
40 : int live_items;
41 : int dead_items;
42 : int page_size;
43 : int free_size;
44 :
45 : /* opaque data */
46 : BlockNumber hasho_prevblkno;
47 : BlockNumber hasho_nextblkno;
48 : Bucket hasho_bucket;
49 : uint16 hasho_flag;
50 : uint16 hasho_page_id;
51 : } HashPageStat;
52 :
53 :
54 : /*
55 : * Verify that the given bytea contains a HASH page, or die in the attempt.
56 : * A pointer to a palloc'd, properly aligned copy of the page is returned.
57 : */
58 : static Page
59 72 : verify_hash_page(bytea *raw_page, int flags)
60 : {
61 72 : Page page = get_page_from_raw(raw_page);
62 64 : int pagetype = LH_UNUSED_PAGE;
63 :
64 : /* Treat new pages as unused. */
65 64 : if (!PageIsNew(page))
66 : {
67 : HashPageOpaque pageopaque;
68 :
69 56 : if (PageGetSpecialSize(page) != MAXALIGN(sizeof(HashPageOpaqueData)))
70 8 : ereport(ERROR,
71 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
72 : errmsg("input page is not a valid %s page", "hash"),
73 : errdetail("Expected special size %d, got %d.",
74 : (int) MAXALIGN(sizeof(HashPageOpaqueData)),
75 : (int) PageGetSpecialSize(page))));
76 :
77 48 : pageopaque = HashPageGetOpaque(page);
78 48 : if (pageopaque->hasho_page_id != HASHO_PAGE_ID)
79 0 : ereport(ERROR,
80 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
81 : errmsg("input page is not a valid %s page", "hash"),
82 : errdetail("Expected %08x, got %08x.",
83 : HASHO_PAGE_ID, pageopaque->hasho_page_id)));
84 :
85 48 : pagetype = pageopaque->hasho_flag & LH_PAGE_TYPE;
86 : }
87 :
88 : /* Check that page type is sane. */
89 56 : if (pagetype != LH_OVERFLOW_PAGE && pagetype != LH_BUCKET_PAGE &&
90 16 : pagetype != LH_BITMAP_PAGE && pagetype != LH_META_PAGE &&
91 : pagetype != LH_UNUSED_PAGE)
92 0 : ereport(ERROR,
93 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
94 : errmsg("invalid hash page type %08x", pagetype)));
95 :
96 : /* If requested, verify page type. */
97 56 : if (flags != 0 && (pagetype & flags) == 0)
98 : {
99 24 : switch (flags)
100 : {
101 12 : case LH_META_PAGE:
102 12 : ereport(ERROR,
103 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
104 : errmsg("page is not a hash meta page")));
105 : break;
106 12 : case LH_BUCKET_PAGE | LH_OVERFLOW_PAGE:
107 12 : ereport(ERROR,
108 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
109 : errmsg("page is not a hash bucket or overflow page")));
110 : break;
111 0 : case LH_OVERFLOW_PAGE:
112 0 : ereport(ERROR,
113 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
114 : errmsg("page is not a hash overflow page")));
115 : break;
116 0 : default:
117 0 : elog(ERROR,
118 : "hash page of type %08x not in mask %08x",
119 : pagetype, flags);
120 : break;
121 : }
122 32 : }
123 :
124 : /*
125 : * If it is the metapage, also verify magic number and version.
126 : */
127 32 : if (pagetype == LH_META_PAGE)
128 : {
129 4 : HashMetaPage metap = HashPageGetMeta(page);
130 :
131 4 : if (metap->hashm_magic != HASH_MAGIC)
132 0 : ereport(ERROR,
133 : (errcode(ERRCODE_INDEX_CORRUPTED),
134 : errmsg("invalid magic number for metadata"),
135 : errdetail("Expected 0x%08x, got 0x%08x.",
136 : HASH_MAGIC, metap->hashm_magic)));
137 :
138 4 : if (metap->hashm_version != HASH_VERSION)
139 0 : ereport(ERROR,
140 : (errcode(ERRCODE_INDEX_CORRUPTED),
141 : errmsg("invalid version for metadata"),
142 : errdetail("Expected %d, got %d.",
143 : HASH_VERSION, metap->hashm_version)));
144 : }
145 :
146 32 : return page;
147 : }
148 :
149 : /* -------------------------------------------------
150 : * GetHashPageStatistics()
151 : *
152 : * Collect statistics of single hash page
153 : * -------------------------------------------------
154 : */
155 : static void
156 8 : GetHashPageStatistics(Page page, HashPageStat *stat)
157 : {
158 8 : OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
159 8 : HashPageOpaque opaque = HashPageGetOpaque(page);
160 : int off;
161 :
162 8 : stat->dead_items = stat->live_items = 0;
163 8 : stat->page_size = PageGetPageSize(page);
164 :
165 : /* hash page opaque data */
166 8 : stat->hasho_prevblkno = opaque->hasho_prevblkno;
167 8 : stat->hasho_nextblkno = opaque->hasho_nextblkno;
168 8 : stat->hasho_bucket = opaque->hasho_bucket;
169 8 : stat->hasho_flag = opaque->hasho_flag;
170 8 : stat->hasho_page_id = opaque->hasho_page_id;
171 :
172 : /* count live and dead tuples, and free space */
173 10 : for (off = FirstOffsetNumber; off <= maxoff; off++)
174 : {
175 2 : ItemId id = PageGetItemId(page, off);
176 :
177 2 : if (!ItemIdIsDead(id))
178 2 : stat->live_items++;
179 : else
180 0 : stat->dead_items++;
181 : }
182 8 : stat->free_size = PageGetFreeSpace(page);
183 8 : }
184 :
185 : /* ---------------------------------------------------
186 : * hash_page_type()
187 : *
188 : * Usage: SELECT hash_page_type(get_raw_page('con_hash_index', 1));
189 : * ---------------------------------------------------
190 : */
191 : Datum
192 18 : hash_page_type(PG_FUNCTION_ARGS)
193 : {
194 18 : bytea *raw_page = PG_GETARG_BYTEA_P(0);
195 : Page page;
196 : HashPageOpaque opaque;
197 : int pagetype;
198 : const char *type;
199 :
200 18 : if (!superuser())
201 0 : ereport(ERROR,
202 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
203 : errmsg("must be superuser to use raw page functions")));
204 :
205 18 : page = verify_hash_page(raw_page, 0);
206 :
207 14 : if (PageIsNew(page))
208 2 : type = "unused";
209 : else
210 : {
211 12 : opaque = HashPageGetOpaque(page);
212 :
213 : /* page type (flags) */
214 12 : pagetype = opaque->hasho_flag & LH_PAGE_TYPE;
215 12 : if (pagetype == LH_META_PAGE)
216 2 : type = "metapage";
217 10 : else if (pagetype == LH_OVERFLOW_PAGE)
218 0 : type = "overflow";
219 10 : else if (pagetype == LH_BUCKET_PAGE)
220 8 : type = "bucket";
221 2 : else if (pagetype == LH_BITMAP_PAGE)
222 2 : type = "bitmap";
223 : else
224 0 : type = "unused";
225 : }
226 :
227 14 : PG_RETURN_TEXT_P(cstring_to_text(type));
228 : }
229 :
230 : /* ---------------------------------------------------
231 : * hash_page_stats()
232 : *
233 : * Usage: SELECT * FROM hash_page_stats(get_raw_page('con_hash_index', 1));
234 : * ---------------------------------------------------
235 : */
236 : Datum
237 18 : hash_page_stats(PG_FUNCTION_ARGS)
238 : {
239 18 : bytea *raw_page = PG_GETARG_BYTEA_P(0);
240 : Page page;
241 : int j;
242 : Datum values[9];
243 18 : bool nulls[9] = {0};
244 : HashPageStat stat;
245 : HeapTuple tuple;
246 : TupleDesc tupleDesc;
247 :
248 18 : if (!superuser())
249 0 : ereport(ERROR,
250 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
251 : errmsg("must be superuser to use raw page functions")));
252 :
253 18 : page = verify_hash_page(raw_page, LH_BUCKET_PAGE | LH_OVERFLOW_PAGE);
254 :
255 : /* keep compiler quiet */
256 8 : stat.hasho_prevblkno = stat.hasho_nextblkno = InvalidBlockNumber;
257 8 : stat.hasho_flag = stat.hasho_page_id = stat.free_size = 0;
258 :
259 8 : GetHashPageStatistics(page, &stat);
260 :
261 : /* Build a tuple descriptor for our result type */
262 8 : if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
263 0 : elog(ERROR, "return type must be a row type");
264 8 : tupleDesc = BlessTupleDesc(tupleDesc);
265 :
266 8 : j = 0;
267 8 : values[j++] = Int32GetDatum(stat.live_items);
268 8 : values[j++] = Int32GetDatum(stat.dead_items);
269 8 : values[j++] = Int32GetDatum(stat.page_size);
270 8 : values[j++] = Int32GetDatum(stat.free_size);
271 8 : values[j++] = Int64GetDatum((int64) stat.hasho_prevblkno);
272 8 : values[j++] = Int64GetDatum((int64) stat.hasho_nextblkno);
273 8 : values[j++] = Int64GetDatum((int64) stat.hasho_bucket);
274 8 : values[j++] = Int32GetDatum((int32) stat.hasho_flag);
275 8 : values[j++] = Int32GetDatum((int32) stat.hasho_page_id);
276 :
277 8 : tuple = heap_form_tuple(tupleDesc, values, nulls);
278 :
279 8 : PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
280 : }
281 :
282 : /*
283 : * cross-call data structure for SRF
284 : */
285 : struct user_args
286 : {
287 : Page page;
288 : OffsetNumber offset;
289 : };
290 :
291 : /*-------------------------------------------------------
292 : * hash_page_items()
293 : *
294 : * Get IndexTupleData set in a hash page
295 : *
296 : * Usage: SELECT * FROM hash_page_items(get_raw_page('con_hash_index', 1));
297 : *-------------------------------------------------------
298 : */
299 : Datum
300 20 : hash_page_items(PG_FUNCTION_ARGS)
301 : {
302 20 : bytea *raw_page = PG_GETARG_BYTEA_P(0);
303 : Page page;
304 : Datum result;
305 : Datum values[3];
306 20 : bool nulls[3] = {0};
307 : uint32 hashkey;
308 : HeapTuple tuple;
309 : FuncCallContext *fctx;
310 : MemoryContext mctx;
311 : struct user_args *uargs;
312 :
313 20 : if (!superuser())
314 0 : ereport(ERROR,
315 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
316 : errmsg("must be superuser to use raw page functions")));
317 :
318 20 : if (SRF_IS_FIRSTCALL())
319 : {
320 : TupleDesc tupleDesc;
321 :
322 18 : fctx = SRF_FIRSTCALL_INIT();
323 :
324 18 : mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
325 :
326 18 : page = verify_hash_page(raw_page, LH_BUCKET_PAGE | LH_OVERFLOW_PAGE);
327 :
328 8 : uargs = palloc(sizeof(struct user_args));
329 :
330 8 : uargs->page = page;
331 :
332 8 : uargs->offset = FirstOffsetNumber;
333 :
334 8 : fctx->max_calls = PageGetMaxOffsetNumber(uargs->page);
335 :
336 : /* Build a tuple descriptor for our result type */
337 8 : if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
338 0 : elog(ERROR, "return type must be a row type");
339 8 : tupleDesc = BlessTupleDesc(tupleDesc);
340 :
341 8 : fctx->attinmeta = TupleDescGetAttInMetadata(tupleDesc);
342 :
343 8 : fctx->user_fctx = uargs;
344 :
345 8 : MemoryContextSwitchTo(mctx);
346 : }
347 :
348 10 : fctx = SRF_PERCALL_SETUP();
349 10 : uargs = fctx->user_fctx;
350 :
351 10 : if (fctx->call_cntr < fctx->max_calls)
352 : {
353 : ItemId id;
354 : IndexTuple itup;
355 : int j;
356 :
357 2 : id = PageGetItemId(uargs->page, uargs->offset);
358 :
359 2 : if (!ItemIdIsValid(id))
360 0 : elog(ERROR, "invalid ItemId");
361 :
362 2 : itup = (IndexTuple) PageGetItem(uargs->page, id);
363 :
364 2 : j = 0;
365 2 : values[j++] = Int32GetDatum((int32) uargs->offset);
366 2 : values[j++] = PointerGetDatum(&itup->t_tid);
367 :
368 2 : hashkey = _hash_get_indextuple_hashkey(itup);
369 2 : values[j] = Int64GetDatum((int64) hashkey);
370 :
371 2 : tuple = heap_form_tuple(fctx->attinmeta->tupdesc, values, nulls);
372 2 : result = HeapTupleGetDatum(tuple);
373 :
374 2 : uargs->offset = uargs->offset + 1;
375 :
376 2 : SRF_RETURN_NEXT(fctx, result);
377 : }
378 :
379 8 : SRF_RETURN_DONE(fctx);
380 : }
381 :
382 : /* ------------------------------------------------
383 : * hash_bitmap_info()
384 : *
385 : * Get bitmap information for a particular overflow page
386 : *
387 : * Usage: SELECT * FROM hash_bitmap_info('con_hash_index'::regclass, 5);
388 : * ------------------------------------------------
389 : */
390 : Datum
391 20 : hash_bitmap_info(PG_FUNCTION_ARGS)
392 : {
393 20 : Oid indexRelid = PG_GETARG_OID(0);
394 20 : int64 ovflblkno = PG_GETARG_INT64(1);
395 : HashMetaPage metap;
396 : Buffer metabuf,
397 : mapbuf;
398 : BlockNumber bitmapblkno;
399 : Page mappage;
400 20 : bool bit = false;
401 : TupleDesc tupleDesc;
402 : Relation indexRel;
403 : uint32 ovflbitno;
404 : int32 bitmappage,
405 : bitmapbit;
406 : HeapTuple tuple;
407 : int i,
408 : j;
409 : Datum values[3];
410 20 : bool nulls[3] = {0};
411 : uint32 *freep;
412 :
413 20 : if (!superuser())
414 0 : ereport(ERROR,
415 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
416 : errmsg("must be superuser to use raw page functions")));
417 :
418 20 : indexRel = relation_open(indexRelid, AccessShareLock);
419 :
420 20 : if (!IS_INDEX(indexRel) || !IS_HASH(indexRel))
421 4 : ereport(ERROR,
422 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
423 : errmsg("\"%s\" is not a %s index",
424 : RelationGetRelationName(indexRel), "hash")));
425 :
426 16 : if (RELATION_IS_OTHER_TEMP(indexRel))
427 0 : ereport(ERROR,
428 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
429 : errmsg("cannot access temporary tables of other sessions")));
430 :
431 16 : if (ovflblkno < 0 || ovflblkno > MaxBlockNumber)
432 2 : ereport(ERROR,
433 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
434 : errmsg("invalid block number")));
435 :
436 14 : if (ovflblkno >= RelationGetNumberOfBlocks(indexRel))
437 2 : ereport(ERROR,
438 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
439 : errmsg("block number %lld is out of range for relation \"%s\"",
440 : (long long int) ovflblkno, RelationGetRelationName(indexRel))));
441 :
442 : /* Read the metapage so we can determine which bitmap page to use */
443 12 : metabuf = _hash_getbuf(indexRel, HASH_METAPAGE, HASH_READ, LH_META_PAGE);
444 12 : metap = HashPageGetMeta(BufferGetPage(metabuf));
445 :
446 : /*
447 : * Reject attempt to read the bit for a metapage or bitmap page; this is
448 : * only meaningful for overflow pages.
449 : */
450 12 : if (ovflblkno == 0)
451 2 : ereport(ERROR,
452 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
453 : errmsg("invalid overflow block number %u",
454 : (BlockNumber) ovflblkno)));
455 18 : for (i = 0; i < metap->hashm_nmaps; i++)
456 10 : if (metap->hashm_mapp[i] == ovflblkno)
457 2 : ereport(ERROR,
458 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
459 : errmsg("invalid overflow block number %u",
460 : (BlockNumber) ovflblkno)));
461 :
462 : /*
463 : * Identify overflow bit number. This will error out for primary bucket
464 : * pages, and we've already rejected the metapage and bitmap pages above.
465 : */
466 8 : ovflbitno = _hash_ovflblkno_to_bitno(metap, (BlockNumber) ovflblkno);
467 :
468 0 : bitmappage = ovflbitno >> BMPG_SHIFT(metap);
469 0 : bitmapbit = ovflbitno & BMPG_MASK(metap);
470 :
471 0 : if (bitmappage >= metap->hashm_nmaps)
472 0 : ereport(ERROR,
473 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
474 : errmsg("invalid overflow block number %u",
475 : (BlockNumber) ovflblkno)));
476 :
477 0 : bitmapblkno = metap->hashm_mapp[bitmappage];
478 :
479 0 : _hash_relbuf(indexRel, metabuf);
480 :
481 : /* Check the status of bitmap bit for overflow page */
482 0 : mapbuf = _hash_getbuf(indexRel, bitmapblkno, HASH_READ, LH_BITMAP_PAGE);
483 0 : mappage = BufferGetPage(mapbuf);
484 0 : freep = HashPageGetBitmap(mappage);
485 :
486 0 : bit = ISSET(freep, bitmapbit) != 0;
487 :
488 0 : _hash_relbuf(indexRel, mapbuf);
489 0 : index_close(indexRel, AccessShareLock);
490 :
491 : /* Build a tuple descriptor for our result type */
492 0 : if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
493 0 : elog(ERROR, "return type must be a row type");
494 0 : tupleDesc = BlessTupleDesc(tupleDesc);
495 :
496 0 : j = 0;
497 0 : values[j++] = Int64GetDatum((int64) bitmapblkno);
498 0 : values[j++] = Int32GetDatum(bitmapbit);
499 0 : values[j++] = BoolGetDatum(bit);
500 :
501 0 : tuple = heap_form_tuple(tupleDesc, values, nulls);
502 :
503 0 : PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
504 : }
505 :
506 : /* ------------------------------------------------
507 : * hash_metapage_info()
508 : *
509 : * Get the meta-page information for a hash index
510 : *
511 : * Usage: SELECT * FROM hash_metapage_info(get_raw_page('con_hash_index', 0))
512 : * ------------------------------------------------
513 : */
514 : Datum
515 18 : hash_metapage_info(PG_FUNCTION_ARGS)
516 : {
517 18 : bytea *raw_page = PG_GETARG_BYTEA_P(0);
518 : Page page;
519 : HashMetaPageData *metad;
520 : TupleDesc tupleDesc;
521 : HeapTuple tuple;
522 : int i,
523 : j;
524 : Datum values[16];
525 18 : bool nulls[16] = {0};
526 : Datum spares[HASH_MAX_SPLITPOINTS];
527 : Datum mapp[HASH_MAX_BITMAPS];
528 :
529 18 : if (!superuser())
530 0 : ereport(ERROR,
531 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
532 : errmsg("must be superuser to use raw page functions")));
533 :
534 18 : page = verify_hash_page(raw_page, LH_META_PAGE);
535 :
536 : /* Build a tuple descriptor for our result type */
537 2 : if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
538 0 : elog(ERROR, "return type must be a row type");
539 2 : tupleDesc = BlessTupleDesc(tupleDesc);
540 :
541 2 : metad = HashPageGetMeta(page);
542 :
543 2 : j = 0;
544 2 : values[j++] = Int64GetDatum((int64) metad->hashm_magic);
545 2 : values[j++] = Int64GetDatum((int64) metad->hashm_version);
546 2 : values[j++] = Float8GetDatum(metad->hashm_ntuples);
547 2 : values[j++] = Int32GetDatum((int32) metad->hashm_ffactor);
548 2 : values[j++] = Int32GetDatum((int32) metad->hashm_bsize);
549 2 : values[j++] = Int32GetDatum((int32) metad->hashm_bmsize);
550 2 : values[j++] = Int32GetDatum((int32) metad->hashm_bmshift);
551 2 : values[j++] = Int64GetDatum((int64) metad->hashm_maxbucket);
552 2 : values[j++] = Int64GetDatum((int64) metad->hashm_highmask);
553 2 : values[j++] = Int64GetDatum((int64) metad->hashm_lowmask);
554 2 : values[j++] = Int64GetDatum((int64) metad->hashm_ovflpoint);
555 2 : values[j++] = Int64GetDatum((int64) metad->hashm_firstfree);
556 2 : values[j++] = Int64GetDatum((int64) metad->hashm_nmaps);
557 2 : values[j++] = ObjectIdGetDatum((Oid) metad->hashm_procid);
558 :
559 198 : for (i = 0; i < HASH_MAX_SPLITPOINTS; i++)
560 196 : spares[i] = Int64GetDatum((int64) metad->hashm_spares[i]);
561 2 : values[j++] = PointerGetDatum(construct_array_builtin(spares, HASH_MAX_SPLITPOINTS, INT8OID));
562 :
563 2050 : for (i = 0; i < HASH_MAX_BITMAPS; i++)
564 2048 : mapp[i] = Int64GetDatum((int64) metad->hashm_mapp[i]);
565 2 : values[j++] = PointerGetDatum(construct_array_builtin(mapp, HASH_MAX_BITMAPS, INT8OID));
566 :
567 2 : tuple = heap_form_tuple(tupleDesc, values, nulls);
568 :
569 2 : PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
570 : }
|