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