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