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 "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 : : */
6586 tgl@sss.pgh.pa.us 52 :CBC 1 : PG_FUNCTION_INFO_V1(pgstatindex);
4432 fujii@postgresql.org 53 : 1 : PG_FUNCTION_INFO_V1(pgstatindexbyid);
6586 tgl@sss.pgh.pa.us 54 : 1 : PG_FUNCTION_INFO_V1(pg_relpages);
4432 fujii@postgresql.org 55 : 1 : PG_FUNCTION_INFO_V1(pg_relpagesbyid);
4658 heikki.linnakangas@i 56 : 1 : PG_FUNCTION_INFO_V1(pgstatginindex);
3137 rhaas@postgresql.org 57 : 2 : PG_FUNCTION_INFO_V1(pgstathashindex);
58 : :
3264 sfrost@snowman.net 59 : 2 : PG_FUNCTION_INFO_V1(pgstatindex_v1_5);
60 : 2 : PG_FUNCTION_INFO_V1(pgstatindexbyid_v1_5);
61 : 2 : PG_FUNCTION_INFO_V1(pg_relpages_v1_5);
62 : 2 : PG_FUNCTION_INFO_V1(pg_relpagesbyid_v1_5);
63 : 2 : 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
6944 bruce@momjian.us 142 :UBC 0 : pgstatindex(PG_FUNCTION_ARGS)
143 : : {
3100 noah@leadboat.com 144 : 0 : text *relname = PG_GETARG_TEXT_PP(0);
145 : : Relation rel;
146 : : RangeVar *relrv;
147 : :
6586 tgl@sss.pgh.pa.us 148 [ # # ]: 0 : if (!superuser())
149 [ # # ]: 0 : ereport(ERROR,
150 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
151 : : errmsg("must be superuser to use pgstattuple functions")));
152 : :
6944 bruce@momjian.us 153 : 0 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
154 : 0 : rel = relation_openrv(relrv, AccessShareLock);
155 : :
4432 fujii@postgresql.org 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
3264 sfrost@snowman.net 167 :CBC 11 : pgstatindex_v1_5(PG_FUNCTION_ARGS)
168 : : {
3100 noah@leadboat.com 169 : 11 : text *relname = PG_GETARG_TEXT_PP(0);
170 : : Relation rel;
171 : : RangeVar *relrv;
172 : :
3264 sfrost@snowman.net 173 : 11 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
174 : 11 : rel = relation_openrv(relrv, AccessShareLock);
175 : :
176 : 11 : 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
4432 fujii@postgresql.org 185 :UBC 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
3264 sfrost@snowman.net 202 :CBC 1 : pgstatindexbyid_v1_5(PG_FUNCTION_ARGS)
203 : : {
204 : 1 : Oid relid = PG_GETARG_OID(0);
205 : : Relation rel;
206 : :
207 : 1 : rel = relation_open(relid, AccessShareLock);
208 : :
209 : 1 : PG_RETURN_DATUM(pgstatindex_impl(rel, fcinfo));
210 : : }
211 : :
212 : : static Datum
4432 fujii@postgresql.org 213 : 12 : pgstatindex_impl(Relation rel, FunctionCallInfo fcinfo)
214 : : {
215 : : Datum result;
216 : : BlockNumber nblocks;
217 : : BlockNumber blkno;
218 : : BTIndexStat indexStat;
219 : 12 : BufferAccessStrategy bstrategy = GetAccessStrategy(BAS_BULKREAD);
220 : :
6944 bruce@momjian.us 221 [ + + + + ]: 12 : if (!IS_INDEX(rel) || !IS_BTREE(rel))
3103 sfrost@snowman.net 222 [ + - ]: 7 : 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 : : */
6003 tgl@sss.pgh.pa.us 232 [ - + - - ]: 5 : if (RELATION_IS_OTHER_TEMP(rel))
6003 tgl@sss.pgh.pa.us 233 [ # # ]:UBC 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 : : */
677 noah@leadboat.com 243 [ - + ]:CBC 5 : if (!rel->rd_index->indisvalid)
677 noah@leadboat.com 244 [ # # ]:UBC 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 : : {
4925 rhaas@postgresql.org 253 :CBC 5 : Buffer buffer = ReadBufferExtended(rel, MAIN_FORKNUM, 0, RBM_NORMAL, bstrategy);
3426 kgrittn@postgresql.o 254 : 5 : Page page = BufferGetPage(buffer);
6944 bruce@momjian.us 255 : 5 : BTMetaPageData *metad = BTPageGetMeta(page);
256 : :
257 : 5 : indexStat.version = metad->btm_version;
258 : 5 : indexStat.level = metad->btm_level;
6378 tgl@sss.pgh.pa.us 259 : 5 : indexStat.root_blkno = metad->btm_root;
260 : :
6944 bruce@momjian.us 261 : 5 : ReleaseBuffer(buffer);
262 : : }
263 : :
264 : : /* -- init counters -- */
265 : 5 : indexStat.internal_pages = 0;
6378 tgl@sss.pgh.pa.us 266 : 5 : indexStat.leaf_pages = 0;
6944 bruce@momjian.us 267 : 5 : indexStat.empty_pages = 0;
268 : 5 : indexStat.deleted_pages = 0;
269 : :
270 : 5 : indexStat.max_avail = 0;
271 : 5 : indexStat.free_space = 0;
272 : :
6378 tgl@sss.pgh.pa.us 273 : 5 : indexStat.fragments = 0;
274 : :
275 : : /*
276 : : * Scan all blocks except the metapage
277 : : */
278 : 5 : nblocks = RelationGetNumberOfBlocks(rel);
279 : :
6944 bruce@momjian.us 280 [ - + ]: 5 : for (blkno = 1; blkno < nblocks; blkno++)
281 : : {
282 : : Buffer buffer;
283 : : Page page;
284 : : BTPageOpaque opaque;
285 : :
5084 rhaas@postgresql.org 286 [ # # ]:UBC 0 : CHECK_FOR_INTERRUPTS();
287 : :
288 : : /* Read and lock buffer */
4836 bruce@momjian.us 289 : 0 : buffer = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy);
6687 290 : 0 : LockBuffer(buffer, BUFFER_LOCK_SHARE);
291 : :
3426 kgrittn@postgresql.o 292 : 0 : page = BufferGetPage(buffer);
1254 michael@paquier.xyz 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 : : */
3488 tgl@sss.pgh.pa.us 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 : :
6505 bruce@momjian.us 309 : 0 : max_avail = BLCKSZ - (BLCKSZ - ((PageHeader) page)->pd_special + SizeOfPageHeaderData);
6687 310 : 0 : indexStat.max_avail += max_avail;
362 tgl@sss.pgh.pa.us 311 : 0 : indexStat.free_space += PageGetExactFreeSpace(page);
312 : :
6687 bruce@momjian.us 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);
6944 327 : 0 : ReleaseBuffer(buffer);
328 : : }
329 : :
6944 bruce@momjian.us 330 :CBC 5 : 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 */
6586 tgl@sss.pgh.pa.us 343 [ - + ]: 5 : if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
6586 tgl@sss.pgh.pa.us 344 [ # # ]:UBC 0 : elog(ERROR, "return type must be a row type");
345 : :
6944 bruce@momjian.us 346 :CBC 5 : j = 0;
4261 peter_e@gmx.net 347 : 5 : values[j++] = psprintf("%d", indexStat.version);
348 : 5 : values[j++] = psprintf("%d", indexStat.level);
349 : 10 : values[j++] = psprintf(INT64_FORMAT,
350 : : (1 + /* include the metapage in index_size */
4141 bruce@momjian.us 351 : 5 : indexStat.leaf_pages +
352 : 5 : indexStat.internal_pages +
353 : 5 : indexStat.deleted_pages +
354 : 5 : indexStat.empty_pages) * BLCKSZ);
4261 peter_e@gmx.net 355 : 5 : values[j++] = psprintf("%u", indexStat.root_blkno);
356 : 5 : values[j++] = psprintf(INT64_FORMAT, indexStat.internal_pages);
357 : 5 : values[j++] = psprintf(INT64_FORMAT, indexStat.leaf_pages);
358 : 5 : values[j++] = psprintf(INT64_FORMAT, indexStat.empty_pages);
359 : 5 : values[j++] = psprintf(INT64_FORMAT, indexStat.deleted_pages);
5127 tgl@sss.pgh.pa.us 360 [ - + ]: 5 : if (indexStat.max_avail > 0)
4261 peter_e@gmx.net 361 :UBC 0 : values[j++] = psprintf("%.2f",
4141 bruce@momjian.us 362 : 0 : 100.0 - (double) indexStat.free_space / (double) indexStat.max_avail * 100.0);
363 : : else
4261 peter_e@gmx.net 364 :CBC 5 : values[j++] = pstrdup("NaN");
5127 tgl@sss.pgh.pa.us 365 [ - + ]: 5 : if (indexStat.leaf_pages > 0)
4261 peter_e@gmx.net 366 :UBC 0 : values[j++] = psprintf("%.2f",
4141 bruce@momjian.us 367 : 0 : (double) indexStat.fragments / (double) indexStat.leaf_pages * 100.0);
368 : : else
4261 peter_e@gmx.net 369 :CBC 5 : values[j++] = pstrdup("NaN");
370 : :
6944 bruce@momjian.us 371 : 5 : tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
372 : : values);
373 : :
6586 tgl@sss.pgh.pa.us 374 : 5 : result = HeapTupleGetDatum(tuple);
375 : : }
376 : :
4432 fujii@postgresql.org 377 : 5 : 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
6944 bruce@momjian.us 392 :UBC 0 : pg_relpages(PG_FUNCTION_ARGS)
393 : : {
3100 noah@leadboat.com 394 : 0 : text *relname = PG_GETARG_TEXT_PP(0);
395 : : Relation rel;
396 : : RangeVar *relrv;
397 : :
6586 tgl@sss.pgh.pa.us 398 [ # # ]: 0 : if (!superuser())
399 [ # # ]: 0 : ereport(ERROR,
400 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
401 : : errmsg("must be superuser to use pgstattuple functions")));
402 : :
6944 bruce@momjian.us 403 : 0 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
404 : 0 : rel = relation_openrv(relrv, AccessShareLock);
405 : :
1521 peter@eisentraut.org 406 : 0 : PG_RETURN_INT64(pg_relpages_impl(rel));
407 : : }
408 : :
409 : : /* No need for superuser checks in v1.5, see above */
410 : : Datum
3264 sfrost@snowman.net 411 :CBC 10 : pg_relpages_v1_5(PG_FUNCTION_ARGS)
412 : : {
3100 noah@leadboat.com 413 : 10 : text *relname = PG_GETARG_TEXT_PP(0);
414 : : Relation rel;
415 : : RangeVar *relrv;
416 : :
3264 sfrost@snowman.net 417 : 10 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
418 : 10 : rel = relation_openrv(relrv, AccessShareLock);
419 : :
1521 peter@eisentraut.org 420 : 10 : PG_RETURN_INT64(pg_relpages_impl(rel));
421 : : }
422 : :
423 : : /* Must keep superuser() check, see above. */
424 : : Datum
4432 fujii@postgresql.org 425 :UBC 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 : :
1521 peter@eisentraut.org 437 : 0 : PG_RETURN_INT64(pg_relpages_impl(rel));
438 : : }
439 : :
440 : : /* No need for superuser checks in v1.5, see above */
441 : : Datum
3264 sfrost@snowman.net 442 :CBC 3 : pg_relpagesbyid_v1_5(PG_FUNCTION_ARGS)
443 : : {
444 : 3 : Oid relid = PG_GETARG_OID(0);
445 : : Relation rel;
446 : :
447 : 3 : rel = relation_open(relid, AccessShareLock);
448 : :
1521 peter@eisentraut.org 449 : 3 : PG_RETURN_INT64(pg_relpages_impl(rel));
450 : : }
451 : :
452 : : static int64
453 : 13 : pg_relpages_impl(Relation rel)
454 : : {
455 : : int64 relpages;
456 : :
457 [ + + + + : 13 : if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
+ + + + +
- ]
458 [ + - ]: 3 : 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 : :
3264 sfrost@snowman.net 466 : 10 : relpages = RelationGetNumberOfBlocks(rel);
467 : :
468 : 10 : relation_close(rel, AccessShareLock);
469 : :
1521 peter@eisentraut.org 470 : 10 : 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
4658 heikki.linnakangas@i 482 :UBC 0 : pgstatginindex(PG_FUNCTION_ARGS)
483 : : {
484 : 0 : Oid relid = PG_GETARG_OID(0);
485 : :
3264 sfrost@snowman.net 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
3264 sfrost@snowman.net 496 :CBC 8 : pgstatginindex_v1_5(PG_FUNCTION_ARGS)
497 : : {
498 : 8 : Oid relid = PG_GETARG_OID(0);
499 : :
500 : 8 : PG_RETURN_DATUM(pgstatginindex_internal(relid, fcinfo));
501 : : }
502 : :
503 : : Datum
504 : 8 : 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];
4658 heikki.linnakangas@i 514 : 8 : bool nulls[3] = {false, false, false};
515 : : Datum result;
516 : :
517 : 8 : rel = relation_open(relid, AccessShareLock);
518 : :
519 [ + + + + ]: 8 : if (!IS_INDEX(rel) || !IS_GIN(rel))
3103 sfrost@snowman.net 520 [ + - ]: 7 : 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 : : */
4658 heikki.linnakangas@i 530 [ - + - - ]: 1 : if (RELATION_IS_OTHER_TEMP(rel))
4658 heikki.linnakangas@i 531 [ # # ]:UBC 0 : ereport(ERROR,
532 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
533 : : errmsg("cannot access temporary indexes of other sessions")));
534 : :
535 : : /* see pgstatindex_impl */
677 noah@leadboat.com 536 [ - + ]:CBC 1 : if (!rel->rd_index->indisvalid)
677 noah@leadboat.com 537 [ # # ]:UBC 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 : : */
4658 heikki.linnakangas@i 545 :CBC 1 : buffer = ReadBuffer(rel, GIN_METAPAGE_BLKNO);
546 : 1 : LockBuffer(buffer, GIN_SHARE);
3426 kgrittn@postgresql.o 547 : 1 : page = BufferGetPage(buffer);
4658 heikki.linnakangas@i 548 : 1 : metadata = GinPageGetMeta(page);
549 : :
550 : 1 : stats.version = metadata->ginVersion;
551 : 1 : stats.pending_pages = metadata->nPendingPages;
552 : 1 : stats.pending_tuples = metadata->nPendingHeapTuples;
553 : :
554 : 1 : UnlockReleaseBuffer(buffer);
555 : 1 : relation_close(rel, AccessShareLock);
556 : :
557 : : /*
558 : : * Build a tuple descriptor for our result type
559 : : */
560 [ - + ]: 1 : if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
4658 heikki.linnakangas@i 561 [ # # ]:UBC 0 : elog(ERROR, "return type must be a row type");
562 : :
4658 heikki.linnakangas@i 563 :CBC 1 : values[0] = Int32GetDatum(stats.version);
564 : 1 : values[1] = UInt32GetDatum(stats.pending_pages);
565 : 1 : values[2] = Int64GetDatum(stats.pending_tuples);
566 : :
567 : : /*
568 : : * Build and return the tuple
569 : : */
570 : 1 : tuple = heap_form_tuple(tupleDesc, values, nulls);
571 : 1 : result = HeapTupleGetDatum(tuple);
572 : :
2942 peter_e@gmx.net 573 : 1 : return result;
574 : : }
575 : :
576 : : /* ------------------------------------------------------
577 : : * pgstathashindex()
578 : : *
579 : : * Usage: SELECT * FROM pgstathashindex('hashindex');
580 : : * ------------------------------------------------------
581 : : */
582 : : Datum
3137 rhaas@postgresql.org 583 : 10 : pgstathashindex(PG_FUNCTION_ARGS)
584 : : {
585 : 10 : 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];
1148 peter@eisentraut.org 594 : 10 : bool nulls[8] = {0};
595 : : Buffer metabuf;
596 : : HashMetaPage metap;
597 : : float8 free_percent;
598 : : uint64 total_space;
599 : :
627 michael@paquier.xyz 600 : 10 : rel = relation_open(relid, AccessShareLock);
601 : :
602 [ + + + + ]: 10 : if (!IS_INDEX(rel) || !IS_HASH(rel))
3103 sfrost@snowman.net 603 [ + - ]: 8 : 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 : : */
3137 rhaas@postgresql.org 613 [ - + - - ]: 2 : if (RELATION_IS_OTHER_TEMP(rel))
3137 rhaas@postgresql.org 614 [ # # ]:UBC 0 : ereport(ERROR,
615 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
616 : : errmsg("cannot access temporary indexes of other sessions")));
617 : :
618 : : /* see pgstatindex_impl */
677 noah@leadboat.com 619 [ - + ]:CBC 2 : if (!rel->rd_index->indisvalid)
677 noah@leadboat.com 620 [ # # ]:UBC 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. */
3137 rhaas@postgresql.org 626 :CBC 2 : memset(&stats, 0, sizeof(stats));
627 : 2 : metabuf = _hash_getbuf(rel, HASH_METAPAGE, HASH_READ, LH_META_PAGE);
628 : 2 : metap = HashPageGetMeta(BufferGetPage(metabuf));
629 : 2 : stats.version = metap->hashm_version;
630 : 2 : stats.space_per_page = metap->hashm_bsize;
631 : 2 : _hash_relbuf(rel, metabuf);
632 : :
633 : : /* Get the current relation length */
634 : 2 : nblocks = RelationGetNumberOfBlocks(rel);
635 : :
636 : : /* prepare access strategy for this index */
637 : 2 : bstrategy = GetAccessStrategy(BAS_BULKREAD);
638 : :
639 : : /* Start from blkno 1 as 0th block is metapage */
640 [ + + ]: 16 : for (blkno = 1; blkno < nblocks; blkno++)
641 : : {
642 : : Buffer buf;
643 : : Page page;
644 : :
645 [ - + ]: 14 : CHECK_FOR_INTERRUPTS();
646 : :
647 : 14 : buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
648 : : bstrategy);
649 : 14 : LockBuffer(buf, BUFFER_LOCK_SHARE);
8 peter@eisentraut.org 650 :GNC 14 : page = BufferGetPage(buf);
651 : :
3137 rhaas@postgresql.org 652 [ - + ]:CBC 14 : if (PageIsNew(page))
3069 rhaas@postgresql.org 653 :UBC 0 : stats.unused_pages++;
3137 rhaas@postgresql.org 654 [ - + ]:CBC 14 : else if (PageGetSpecialSize(page) !=
655 : : MAXALIGN(sizeof(HashPageOpaqueData)))
3137 rhaas@postgresql.org 656 [ # # ]:UBC 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 : :
1254 michael@paquier.xyz 666 :CBC 14 : opaque = HashPageGetOpaque(page);
3069 rhaas@postgresql.org 667 : 14 : pagetype = opaque->hasho_flag & LH_PAGE_TYPE;
668 : :
669 [ + + ]: 14 : if (pagetype == LH_BUCKET_PAGE)
670 : : {
3137 671 : 12 : stats.bucket_pages++;
672 : 12 : GetHashPageStats(page, &stats);
673 : : }
3069 674 [ - + ]: 2 : else if (pagetype == LH_OVERFLOW_PAGE)
675 : : {
3137 rhaas@postgresql.org 676 :UBC 0 : stats.overflow_pages++;
677 : 0 : GetHashPageStats(page, &stats);
678 : : }
3069 rhaas@postgresql.org 679 [ + - ]:CBC 2 : else if (pagetype == LH_BITMAP_PAGE)
3137 680 : 2 : stats.bitmap_pages++;
3069 rhaas@postgresql.org 681 [ # # ]:UBC 0 : else if (pagetype == LH_UNUSED_PAGE)
682 : 0 : stats.unused_pages++;
683 : : else
3137 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 : : }
3137 rhaas@postgresql.org 690 :CBC 14 : UnlockReleaseBuffer(buf);
691 : : }
692 : :
693 : : /* Done accessing the index */
694 : 2 : index_close(rel, AccessShareLock);
695 : :
696 : : /* Count unused pages as free space. */
2949 697 : 2 : 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 : 2 : total_space = (uint64) (nblocks - (stats.bitmap_pages + 1)) *
704 : 2 : stats.space_per_page;
705 : :
3137 706 [ - + ]: 2 : if (total_space == 0)
3137 rhaas@postgresql.org 707 :UBC 0 : free_percent = 0.0;
708 : : else
3137 rhaas@postgresql.org 709 :CBC 2 : free_percent = 100.0 * stats.free_space / total_space;
710 : :
711 : : /*
712 : : * Build a tuple descriptor for our result type
713 : : */
714 [ - + ]: 2 : if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
3137 rhaas@postgresql.org 715 [ # # ]:UBC 0 : elog(ERROR, "return type must be a row type");
716 : :
3137 rhaas@postgresql.org 717 :CBC 2 : tupleDesc = BlessTupleDesc(tupleDesc);
718 : :
719 : : /*
720 : : * Build and return the tuple
721 : : */
722 : 2 : values[0] = Int32GetDatum(stats.version);
723 : 2 : values[1] = Int64GetDatum((int64) stats.bucket_pages);
724 : 2 : values[2] = Int64GetDatum((int64) stats.overflow_pages);
725 : 2 : values[3] = Int64GetDatum((int64) stats.bitmap_pages);
3069 726 : 2 : values[4] = Int64GetDatum((int64) stats.unused_pages);
3137 727 : 2 : values[5] = Int64GetDatum(stats.live_items);
728 : 2 : values[6] = Int64GetDatum(stats.dead_items);
729 : 2 : values[7] = Float8GetDatum(free_percent);
730 : 2 : tuple = heap_form_tuple(tupleDesc, values, nulls);
731 : :
732 : 2 : PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
733 : : }
734 : :
735 : : /* -------------------------------------------------
736 : : * GetHashPageStats()
737 : : *
738 : : * Collect statistics of single hash page
739 : : * -------------------------------------------------
740 : : */
741 : : static void
742 : 12 : GetHashPageStats(Page page, HashIndexStat *stats)
743 : : {
744 : 12 : OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
745 : : int off;
746 : :
747 : : /* count live and dead tuples, and free space */
748 [ - + ]: 12 : for (off = FirstOffsetNumber; off <= maxoff; off++)
749 : : {
3034 bruce@momjian.us 750 :UBC 0 : ItemId id = PageGetItemId(page, off);
751 : :
3137 rhaas@postgresql.org 752 [ # # ]: 0 : if (!ItemIdIsDead(id))
753 : 0 : stats->live_items++;
754 : : else
755 : 0 : stats->dead_items++;
756 : : }
3137 rhaas@postgresql.org 757 :CBC 12 : stats->free_space += PageGetExactFreeSpace(page);
758 : 12 : }
|