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