Line data Source code
1 : /*
2 : * contrib/pgstattuple/pgstatindex.c
3 : *
4 : *
5 : * pgstatindex
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/gin_private.h"
31 : #include "access/hash.h"
32 : #include "access/htup_details.h"
33 : #include "access/nbtree.h"
34 : #include "access/relation.h"
35 : #include "catalog/namespace.h"
36 : #include "catalog/pg_am.h"
37 : #include "funcapi.h"
38 : #include "miscadmin.h"
39 : #include "storage/bufmgr.h"
40 : #include "utils/rel.h"
41 : #include "utils/varlena.h"
42 :
43 :
44 : /*
45 : * Because of backward-compatibility issue, we have decided to have
46 : * two types of interfaces, with regclass-type input arg and text-type
47 : * input arg, for each function.
48 : *
49 : * Those functions which have text-type input arg will be deprecated
50 : * in the future release.
51 : */
52 2 : PG_FUNCTION_INFO_V1(pgstatindex);
53 2 : PG_FUNCTION_INFO_V1(pgstatindexbyid);
54 2 : PG_FUNCTION_INFO_V1(pg_relpages);
55 2 : PG_FUNCTION_INFO_V1(pg_relpagesbyid);
56 2 : PG_FUNCTION_INFO_V1(pgstatginindex);
57 4 : PG_FUNCTION_INFO_V1(pgstathashindex);
58 :
59 4 : PG_FUNCTION_INFO_V1(pgstatindex_v1_5);
60 4 : PG_FUNCTION_INFO_V1(pgstatindexbyid_v1_5);
61 4 : PG_FUNCTION_INFO_V1(pg_relpages_v1_5);
62 4 : PG_FUNCTION_INFO_V1(pg_relpagesbyid_v1_5);
63 4 : PG_FUNCTION_INFO_V1(pgstatginindex_v1_5);
64 :
65 : Datum pgstatginindex_internal(Oid relid, FunctionCallInfo fcinfo);
66 :
67 : #define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX)
68 : #define IS_BTREE(r) ((r)->rd_rel->relam == BTREE_AM_OID)
69 : #define IS_GIN(r) ((r)->rd_rel->relam == GIN_AM_OID)
70 : #define IS_HASH(r) ((r)->rd_rel->relam == HASH_AM_OID)
71 :
72 : /* ------------------------------------------------
73 : * A structure for a whole btree index statistics
74 : * used by pgstatindex().
75 : * ------------------------------------------------
76 : */
77 : typedef struct BTIndexStat
78 : {
79 : uint32 version;
80 : uint32 level;
81 : BlockNumber root_blkno;
82 :
83 : uint64 internal_pages;
84 : uint64 leaf_pages;
85 : uint64 empty_pages;
86 : uint64 deleted_pages;
87 :
88 : uint64 max_avail;
89 : uint64 free_space;
90 :
91 : uint64 fragments;
92 : } BTIndexStat;
93 :
94 : /* ------------------------------------------------
95 : * A structure for a whole GIN index statistics
96 : * used by pgstatginindex().
97 : * ------------------------------------------------
98 : */
99 : typedef struct GinIndexStat
100 : {
101 : int32 version;
102 :
103 : BlockNumber pending_pages;
104 : int64 pending_tuples;
105 : } GinIndexStat;
106 :
107 : /* ------------------------------------------------
108 : * A structure for a whole HASH index statistics
109 : * used by pgstathashindex().
110 : * ------------------------------------------------
111 : */
112 : typedef struct HashIndexStat
113 : {
114 : int32 version;
115 : int32 space_per_page;
116 :
117 : BlockNumber bucket_pages;
118 : BlockNumber overflow_pages;
119 : BlockNumber bitmap_pages;
120 : BlockNumber unused_pages;
121 :
122 : int64 live_items;
123 : int64 dead_items;
124 : uint64 free_space;
125 : } HashIndexStat;
126 :
127 : static Datum pgstatindex_impl(Relation rel, FunctionCallInfo fcinfo);
128 : static int64 pg_relpages_impl(Relation rel);
129 : static void GetHashPageStats(Page page, HashIndexStat *stats);
130 :
131 : /* ------------------------------------------------------
132 : * pgstatindex()
133 : *
134 : * Usage: SELECT * FROM pgstatindex('t1_pkey');
135 : *
136 : * The superuser() check here must be kept as the library might be upgraded
137 : * without the extension being upgraded, meaning that in pre-1.5 installations
138 : * these functions could be called by any user.
139 : * ------------------------------------------------------
140 : */
141 : Datum
142 0 : pgstatindex(PG_FUNCTION_ARGS)
143 : {
144 0 : text *relname = PG_GETARG_TEXT_PP(0);
145 : Relation rel;
146 : RangeVar *relrv;
147 :
148 0 : if (!superuser())
149 0 : ereport(ERROR,
150 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
151 : errmsg("must be superuser to use pgstattuple functions")));
152 :
153 0 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
154 0 : rel = relation_openrv(relrv, AccessShareLock);
155 :
156 0 : PG_RETURN_DATUM(pgstatindex_impl(rel, fcinfo));
157 : }
158 :
159 : /*
160 : * As of pgstattuple version 1.5, we no longer need to check if the user
161 : * is a superuser because we REVOKE EXECUTE on the function from PUBLIC.
162 : * Users can then grant access to it based on their policies.
163 : *
164 : * Otherwise identical to pgstatindex (above).
165 : */
166 : Datum
167 22 : pgstatindex_v1_5(PG_FUNCTION_ARGS)
168 : {
169 22 : text *relname = PG_GETARG_TEXT_PP(0);
170 : Relation rel;
171 : RangeVar *relrv;
172 :
173 22 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
174 22 : rel = relation_openrv(relrv, AccessShareLock);
175 :
176 22 : PG_RETURN_DATUM(pgstatindex_impl(rel, fcinfo));
177 : }
178 :
179 : /*
180 : * The superuser() check here must be kept as the library might be upgraded
181 : * without the extension being upgraded, meaning that in pre-1.5 installations
182 : * these functions could be called by any user.
183 : */
184 : Datum
185 0 : pgstatindexbyid(PG_FUNCTION_ARGS)
186 : {
187 0 : Oid relid = PG_GETARG_OID(0);
188 : Relation rel;
189 :
190 0 : if (!superuser())
191 0 : ereport(ERROR,
192 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
193 : errmsg("must be superuser to use pgstattuple functions")));
194 :
195 0 : rel = relation_open(relid, AccessShareLock);
196 :
197 0 : PG_RETURN_DATUM(pgstatindex_impl(rel, fcinfo));
198 : }
199 :
200 : /* No need for superuser checks in v1.5, see above */
201 : Datum
202 2 : pgstatindexbyid_v1_5(PG_FUNCTION_ARGS)
203 : {
204 2 : Oid relid = PG_GETARG_OID(0);
205 : Relation rel;
206 :
207 2 : rel = relation_open(relid, AccessShareLock);
208 :
209 2 : PG_RETURN_DATUM(pgstatindex_impl(rel, fcinfo));
210 : }
211 :
212 : static Datum
213 24 : pgstatindex_impl(Relation rel, FunctionCallInfo fcinfo)
214 : {
215 : Datum result;
216 : BlockNumber nblocks;
217 : BlockNumber blkno;
218 : BTIndexStat indexStat;
219 24 : BufferAccessStrategy bstrategy = GetAccessStrategy(BAS_BULKREAD);
220 :
221 24 : if (!IS_INDEX(rel) || !IS_BTREE(rel))
222 14 : ereport(ERROR,
223 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
224 : errmsg("relation \"%s\" is not a btree index",
225 : RelationGetRelationName(rel))));
226 :
227 : /*
228 : * Reject attempts to read non-local temporary relations; we would be
229 : * likely to get wrong data since we have no visibility into the owning
230 : * session's local buffers.
231 : */
232 10 : if (RELATION_IS_OTHER_TEMP(rel))
233 0 : ereport(ERROR,
234 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
235 : errmsg("cannot access temporary tables of other sessions")));
236 :
237 : /*
238 : * A !indisready index could lead to ERRCODE_DATA_CORRUPTED later, so exit
239 : * early. We're capable of assessing an indisready&&!indisvalid index,
240 : * but the results could be confusing. For example, the index's size
241 : * could be too low for a valid index of the table.
242 : */
243 10 : if (!rel->rd_index->indisvalid)
244 0 : ereport(ERROR,
245 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
246 : errmsg("index \"%s\" is not valid",
247 : RelationGetRelationName(rel))));
248 :
249 : /*
250 : * Read metapage
251 : */
252 : {
253 10 : Buffer buffer = ReadBufferExtended(rel, MAIN_FORKNUM, 0, RBM_NORMAL, bstrategy);
254 10 : Page page = BufferGetPage(buffer);
255 10 : BTMetaPageData *metad = BTPageGetMeta(page);
256 :
257 10 : indexStat.version = metad->btm_version;
258 10 : indexStat.level = metad->btm_level;
259 10 : indexStat.root_blkno = metad->btm_root;
260 :
261 10 : ReleaseBuffer(buffer);
262 : }
263 :
264 : /* -- init counters -- */
265 10 : indexStat.internal_pages = 0;
266 10 : indexStat.leaf_pages = 0;
267 10 : indexStat.empty_pages = 0;
268 10 : indexStat.deleted_pages = 0;
269 :
270 10 : indexStat.max_avail = 0;
271 10 : indexStat.free_space = 0;
272 :
273 10 : indexStat.fragments = 0;
274 :
275 : /*
276 : * Scan all blocks except the metapage
277 : */
278 10 : nblocks = RelationGetNumberOfBlocks(rel);
279 :
280 10 : for (blkno = 1; blkno < nblocks; blkno++)
281 : {
282 : Buffer buffer;
283 : Page page;
284 : BTPageOpaque opaque;
285 :
286 0 : CHECK_FOR_INTERRUPTS();
287 :
288 : /* Read and lock buffer */
289 0 : buffer = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy);
290 0 : LockBuffer(buffer, BUFFER_LOCK_SHARE);
291 :
292 0 : page = BufferGetPage(buffer);
293 0 : opaque = BTPageGetOpaque(page);
294 :
295 : /*
296 : * Determine page type, and update totals.
297 : *
298 : * Note that we arbitrarily bucket deleted pages together without
299 : * considering if they're leaf pages or internal pages.
300 : */
301 0 : if (P_ISDELETED(opaque))
302 0 : indexStat.deleted_pages++;
303 0 : else if (P_IGNORE(opaque))
304 0 : indexStat.empty_pages++; /* this is the "half dead" state */
305 0 : else if (P_ISLEAF(opaque))
306 : {
307 : int max_avail;
308 :
309 0 : max_avail = BLCKSZ - (BLCKSZ - ((PageHeader) page)->pd_special + SizeOfPageHeaderData);
310 0 : indexStat.max_avail += max_avail;
311 0 : indexStat.free_space += PageGetExactFreeSpace(page);
312 :
313 0 : indexStat.leaf_pages++;
314 :
315 : /*
316 : * If the next leaf is on an earlier block, it means a
317 : * fragmentation.
318 : */
319 0 : if (opaque->btpo_next != P_NONE && opaque->btpo_next < blkno)
320 0 : indexStat.fragments++;
321 : }
322 : else
323 0 : indexStat.internal_pages++;
324 :
325 : /* Unlock and release buffer */
326 0 : LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
327 0 : ReleaseBuffer(buffer);
328 : }
329 :
330 10 : relation_close(rel, AccessShareLock);
331 :
332 : /*----------------------------
333 : * Build a result tuple
334 : *----------------------------
335 : */
336 : {
337 : TupleDesc tupleDesc;
338 : int j;
339 : char *values[10];
340 : HeapTuple tuple;
341 :
342 : /* Build a tuple descriptor for our result type */
343 10 : if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
344 0 : elog(ERROR, "return type must be a row type");
345 :
346 10 : j = 0;
347 10 : values[j++] = psprintf("%d", indexStat.version);
348 10 : values[j++] = psprintf("%d", indexStat.level);
349 20 : values[j++] = psprintf(INT64_FORMAT,
350 : (1 + /* include the metapage in index_size */
351 10 : indexStat.leaf_pages +
352 10 : indexStat.internal_pages +
353 10 : indexStat.deleted_pages +
354 10 : indexStat.empty_pages) * BLCKSZ);
355 10 : values[j++] = psprintf("%u", indexStat.root_blkno);
356 10 : values[j++] = psprintf(INT64_FORMAT, indexStat.internal_pages);
357 10 : values[j++] = psprintf(INT64_FORMAT, indexStat.leaf_pages);
358 10 : values[j++] = psprintf(INT64_FORMAT, indexStat.empty_pages);
359 10 : values[j++] = psprintf(INT64_FORMAT, indexStat.deleted_pages);
360 10 : if (indexStat.max_avail > 0)
361 0 : values[j++] = psprintf("%.2f",
362 0 : 100.0 - (double) indexStat.free_space / (double) indexStat.max_avail * 100.0);
363 : else
364 10 : values[j++] = pstrdup("NaN");
365 10 : if (indexStat.leaf_pages > 0)
366 0 : values[j++] = psprintf("%.2f",
367 0 : (double) indexStat.fragments / (double) indexStat.leaf_pages * 100.0);
368 : else
369 10 : values[j++] = pstrdup("NaN");
370 :
371 10 : tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
372 : values);
373 :
374 10 : result = HeapTupleGetDatum(tuple);
375 : }
376 :
377 10 : return result;
378 : }
379 :
380 : /* --------------------------------------------------------
381 : * pg_relpages()
382 : *
383 : * Get the number of pages of the table/index.
384 : *
385 : * Usage: SELECT pg_relpages('t1');
386 : * SELECT pg_relpages('t1_pkey');
387 : *
388 : * Must keep superuser() check, see above.
389 : * --------------------------------------------------------
390 : */
391 : Datum
392 0 : pg_relpages(PG_FUNCTION_ARGS)
393 : {
394 0 : text *relname = PG_GETARG_TEXT_PP(0);
395 : Relation rel;
396 : RangeVar *relrv;
397 :
398 0 : if (!superuser())
399 0 : ereport(ERROR,
400 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
401 : errmsg("must be superuser to use pgstattuple functions")));
402 :
403 0 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
404 0 : rel = relation_openrv(relrv, AccessShareLock);
405 :
406 0 : PG_RETURN_INT64(pg_relpages_impl(rel));
407 : }
408 :
409 : /* No need for superuser checks in v1.5, see above */
410 : Datum
411 20 : pg_relpages_v1_5(PG_FUNCTION_ARGS)
412 : {
413 20 : text *relname = PG_GETARG_TEXT_PP(0);
414 : Relation rel;
415 : RangeVar *relrv;
416 :
417 20 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
418 20 : rel = relation_openrv(relrv, AccessShareLock);
419 :
420 20 : PG_RETURN_INT64(pg_relpages_impl(rel));
421 : }
422 :
423 : /* Must keep superuser() check, see above. */
424 : Datum
425 0 : pg_relpagesbyid(PG_FUNCTION_ARGS)
426 : {
427 0 : Oid relid = PG_GETARG_OID(0);
428 : Relation rel;
429 :
430 0 : if (!superuser())
431 0 : ereport(ERROR,
432 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
433 : errmsg("must be superuser to use pgstattuple functions")));
434 :
435 0 : rel = relation_open(relid, AccessShareLock);
436 :
437 0 : PG_RETURN_INT64(pg_relpages_impl(rel));
438 : }
439 :
440 : /* No need for superuser checks in v1.5, see above */
441 : Datum
442 6 : pg_relpagesbyid_v1_5(PG_FUNCTION_ARGS)
443 : {
444 6 : Oid relid = PG_GETARG_OID(0);
445 : Relation rel;
446 :
447 6 : rel = relation_open(relid, AccessShareLock);
448 :
449 6 : PG_RETURN_INT64(pg_relpages_impl(rel));
450 : }
451 :
452 : static int64
453 26 : pg_relpages_impl(Relation rel)
454 : {
455 : int64 relpages;
456 :
457 26 : if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
458 6 : ereport(ERROR,
459 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
460 : errmsg("cannot get page count of relation \"%s\"",
461 : RelationGetRelationName(rel)),
462 : errdetail_relkind_not_supported(rel->rd_rel->relkind)));
463 :
464 : /* note: this will work OK on non-local temp tables */
465 :
466 20 : relpages = RelationGetNumberOfBlocks(rel);
467 :
468 20 : relation_close(rel, AccessShareLock);
469 :
470 20 : return relpages;
471 : }
472 :
473 : /* ------------------------------------------------------
474 : * pgstatginindex()
475 : *
476 : * Usage: SELECT * FROM pgstatginindex('ginindex');
477 : *
478 : * Must keep superuser() check, see above.
479 : * ------------------------------------------------------
480 : */
481 : Datum
482 0 : pgstatginindex(PG_FUNCTION_ARGS)
483 : {
484 0 : Oid relid = PG_GETARG_OID(0);
485 :
486 0 : if (!superuser())
487 0 : ereport(ERROR,
488 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
489 : errmsg("must be superuser to use pgstattuple functions")));
490 :
491 0 : PG_RETURN_DATUM(pgstatginindex_internal(relid, fcinfo));
492 : }
493 :
494 : /* No need for superuser checks in v1.5, see above */
495 : Datum
496 16 : pgstatginindex_v1_5(PG_FUNCTION_ARGS)
497 : {
498 16 : Oid relid = PG_GETARG_OID(0);
499 :
500 16 : PG_RETURN_DATUM(pgstatginindex_internal(relid, fcinfo));
501 : }
502 :
503 : Datum
504 16 : pgstatginindex_internal(Oid relid, FunctionCallInfo fcinfo)
505 : {
506 : Relation rel;
507 : Buffer buffer;
508 : Page page;
509 : GinMetaPageData *metadata;
510 : GinIndexStat stats;
511 : HeapTuple tuple;
512 : TupleDesc tupleDesc;
513 : Datum values[3];
514 16 : bool nulls[3] = {false, false, false};
515 : Datum result;
516 :
517 16 : rel = relation_open(relid, AccessShareLock);
518 :
519 16 : if (!IS_INDEX(rel) || !IS_GIN(rel))
520 14 : ereport(ERROR,
521 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
522 : errmsg("relation \"%s\" is not a GIN index",
523 : RelationGetRelationName(rel))));
524 :
525 : /*
526 : * Reject attempts to read non-local temporary relations; we would be
527 : * likely to get wrong data since we have no visibility into the owning
528 : * session's local buffers.
529 : */
530 2 : if (RELATION_IS_OTHER_TEMP(rel))
531 0 : ereport(ERROR,
532 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
533 : errmsg("cannot access temporary indexes of other sessions")));
534 :
535 : /* see pgstatindex_impl */
536 2 : if (!rel->rd_index->indisvalid)
537 0 : ereport(ERROR,
538 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
539 : errmsg("index \"%s\" is not valid",
540 : RelationGetRelationName(rel))));
541 :
542 : /*
543 : * Read metapage
544 : */
545 2 : buffer = ReadBuffer(rel, GIN_METAPAGE_BLKNO);
546 2 : LockBuffer(buffer, GIN_SHARE);
547 2 : page = BufferGetPage(buffer);
548 2 : metadata = GinPageGetMeta(page);
549 :
550 2 : stats.version = metadata->ginVersion;
551 2 : stats.pending_pages = metadata->nPendingPages;
552 2 : stats.pending_tuples = metadata->nPendingHeapTuples;
553 :
554 2 : UnlockReleaseBuffer(buffer);
555 2 : relation_close(rel, AccessShareLock);
556 :
557 : /*
558 : * Build a tuple descriptor for our result type
559 : */
560 2 : if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
561 0 : elog(ERROR, "return type must be a row type");
562 :
563 2 : values[0] = Int32GetDatum(stats.version);
564 2 : values[1] = UInt32GetDatum(stats.pending_pages);
565 2 : values[2] = Int64GetDatum(stats.pending_tuples);
566 :
567 : /*
568 : * Build and return the tuple
569 : */
570 2 : tuple = heap_form_tuple(tupleDesc, values, nulls);
571 2 : result = HeapTupleGetDatum(tuple);
572 :
573 2 : return result;
574 : }
575 :
576 : /* ------------------------------------------------------
577 : * pgstathashindex()
578 : *
579 : * Usage: SELECT * FROM pgstathashindex('hashindex');
580 : * ------------------------------------------------------
581 : */
582 : Datum
583 20 : pgstathashindex(PG_FUNCTION_ARGS)
584 : {
585 20 : Oid relid = PG_GETARG_OID(0);
586 : BlockNumber nblocks;
587 : BlockNumber blkno;
588 : Relation rel;
589 : HashIndexStat stats;
590 : BufferAccessStrategy bstrategy;
591 : HeapTuple tuple;
592 : TupleDesc tupleDesc;
593 : Datum values[8];
594 20 : bool nulls[8] = {0};
595 : Buffer metabuf;
596 : HashMetaPage metap;
597 : float8 free_percent;
598 : uint64 total_space;
599 :
600 20 : rel = relation_open(relid, AccessShareLock);
601 :
602 20 : if (!IS_INDEX(rel) || !IS_HASH(rel))
603 16 : ereport(ERROR,
604 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
605 : errmsg("relation \"%s\" is not a hash index",
606 : RelationGetRelationName(rel))));
607 :
608 : /*
609 : * Reject attempts to read non-local temporary relations; we would be
610 : * likely to get wrong data since we have no visibility into the owning
611 : * session's local buffers.
612 : */
613 4 : if (RELATION_IS_OTHER_TEMP(rel))
614 0 : ereport(ERROR,
615 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
616 : errmsg("cannot access temporary indexes of other sessions")));
617 :
618 : /* see pgstatindex_impl */
619 4 : if (!rel->rd_index->indisvalid)
620 0 : ereport(ERROR,
621 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
622 : errmsg("index \"%s\" is not valid",
623 : RelationGetRelationName(rel))));
624 :
625 : /* Get the information we need from the metapage. */
626 4 : memset(&stats, 0, sizeof(stats));
627 4 : metabuf = _hash_getbuf(rel, HASH_METAPAGE, HASH_READ, LH_META_PAGE);
628 4 : metap = HashPageGetMeta(BufferGetPage(metabuf));
629 4 : stats.version = metap->hashm_version;
630 4 : stats.space_per_page = metap->hashm_bsize;
631 4 : _hash_relbuf(rel, metabuf);
632 :
633 : /* Get the current relation length */
634 4 : nblocks = RelationGetNumberOfBlocks(rel);
635 :
636 : /* prepare access strategy for this index */
637 4 : bstrategy = GetAccessStrategy(BAS_BULKREAD);
638 :
639 : /* Start from blkno 1 as 0th block is metapage */
640 32 : for (blkno = 1; blkno < nblocks; blkno++)
641 : {
642 : Buffer buf;
643 : Page page;
644 :
645 28 : CHECK_FOR_INTERRUPTS();
646 :
647 28 : buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
648 : bstrategy);
649 28 : LockBuffer(buf, BUFFER_LOCK_SHARE);
650 28 : page = (Page) BufferGetPage(buf);
651 :
652 28 : if (PageIsNew(page))
653 0 : stats.unused_pages++;
654 28 : else if (PageGetSpecialSize(page) !=
655 : MAXALIGN(sizeof(HashPageOpaqueData)))
656 0 : ereport(ERROR,
657 : (errcode(ERRCODE_INDEX_CORRUPTED),
658 : errmsg("index \"%s\" contains corrupted page at block %u",
659 : RelationGetRelationName(rel),
660 : BufferGetBlockNumber(buf))));
661 : else
662 : {
663 : HashPageOpaque opaque;
664 : int pagetype;
665 :
666 28 : opaque = HashPageGetOpaque(page);
667 28 : pagetype = opaque->hasho_flag & LH_PAGE_TYPE;
668 :
669 28 : if (pagetype == LH_BUCKET_PAGE)
670 : {
671 24 : stats.bucket_pages++;
672 24 : GetHashPageStats(page, &stats);
673 : }
674 4 : else if (pagetype == LH_OVERFLOW_PAGE)
675 : {
676 0 : stats.overflow_pages++;
677 0 : GetHashPageStats(page, &stats);
678 : }
679 4 : else if (pagetype == LH_BITMAP_PAGE)
680 4 : stats.bitmap_pages++;
681 0 : else if (pagetype == LH_UNUSED_PAGE)
682 0 : stats.unused_pages++;
683 : else
684 0 : ereport(ERROR,
685 : (errcode(ERRCODE_INDEX_CORRUPTED),
686 : errmsg("unexpected page type 0x%04X in HASH index \"%s\" block %u",
687 : opaque->hasho_flag, RelationGetRelationName(rel),
688 : BufferGetBlockNumber(buf))));
689 : }
690 28 : UnlockReleaseBuffer(buf);
691 : }
692 :
693 : /* Done accessing the index */
694 4 : index_close(rel, AccessShareLock);
695 :
696 : /* Count unused pages as free space. */
697 4 : stats.free_space += (uint64) stats.unused_pages * stats.space_per_page;
698 :
699 : /*
700 : * Total space available for tuples excludes the metapage and the bitmap
701 : * pages.
702 : */
703 4 : total_space = (uint64) (nblocks - (stats.bitmap_pages + 1)) *
704 4 : stats.space_per_page;
705 :
706 4 : if (total_space == 0)
707 0 : free_percent = 0.0;
708 : else
709 4 : free_percent = 100.0 * stats.free_space / total_space;
710 :
711 : /*
712 : * Build a tuple descriptor for our result type
713 : */
714 4 : if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
715 0 : elog(ERROR, "return type must be a row type");
716 :
717 4 : tupleDesc = BlessTupleDesc(tupleDesc);
718 :
719 : /*
720 : * Build and return the tuple
721 : */
722 4 : values[0] = Int32GetDatum(stats.version);
723 4 : values[1] = Int64GetDatum((int64) stats.bucket_pages);
724 4 : values[2] = Int64GetDatum((int64) stats.overflow_pages);
725 4 : values[3] = Int64GetDatum((int64) stats.bitmap_pages);
726 4 : values[4] = Int64GetDatum((int64) stats.unused_pages);
727 4 : values[5] = Int64GetDatum(stats.live_items);
728 4 : values[6] = Int64GetDatum(stats.dead_items);
729 4 : values[7] = Float8GetDatum(free_percent);
730 4 : tuple = heap_form_tuple(tupleDesc, values, nulls);
731 :
732 4 : PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
733 : }
734 :
735 : /* -------------------------------------------------
736 : * GetHashPageStats()
737 : *
738 : * Collect statistics of single hash page
739 : * -------------------------------------------------
740 : */
741 : static void
742 24 : GetHashPageStats(Page page, HashIndexStat *stats)
743 : {
744 24 : OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
745 : int off;
746 :
747 : /* count live and dead tuples, and free space */
748 24 : for (off = FirstOffsetNumber; off <= maxoff; off++)
749 : {
750 0 : ItemId id = PageGetItemId(page, off);
751 :
752 0 : if (!ItemIdIsDead(id))
753 0 : stats->live_items++;
754 : else
755 0 : stats->dead_items++;
756 : }
757 24 : stats->free_space += PageGetExactFreeSpace(page);
758 24 : }
|