LCOV - code coverage report
Current view: top level - contrib/pgstattuple - pgstatapprox.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 46.5 % 99 46
Test Date: 2026-02-17 17:20:33 Functions: 83.3 % 6 5
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * pgstatapprox.c
       4              :  *        Bloat estimation functions
       5              :  *
       6              :  * Copyright (c) 2014-2026, PostgreSQL Global Development Group
       7              :  *
       8              :  * IDENTIFICATION
       9              :  *        contrib/pgstattuple/pgstatapprox.c
      10              :  *
      11              :  *-------------------------------------------------------------------------
      12              :  */
      13              : #include "postgres.h"
      14              : 
      15              : #include "access/heapam.h"
      16              : #include "access/htup_details.h"
      17              : #include "access/relation.h"
      18              : #include "access/visibilitymap.h"
      19              : #include "catalog/pg_am_d.h"
      20              : #include "commands/vacuum.h"
      21              : #include "funcapi.h"
      22              : #include "miscadmin.h"
      23              : #include "storage/bufmgr.h"
      24              : #include "storage/freespace.h"
      25              : #include "storage/procarray.h"
      26              : 
      27            1 : PG_FUNCTION_INFO_V1(pgstattuple_approx);
      28            2 : PG_FUNCTION_INFO_V1(pgstattuple_approx_v1_5);
      29              : 
      30              : Datum       pgstattuple_approx_internal(Oid relid, FunctionCallInfo fcinfo);
      31              : 
      32              : typedef struct output_type
      33              : {
      34              :     uint64      table_len;
      35              :     double      scanned_percent;
      36              :     uint64      tuple_count;
      37              :     uint64      tuple_len;
      38              :     double      tuple_percent;
      39              :     uint64      dead_tuple_count;
      40              :     uint64      dead_tuple_len;
      41              :     double      dead_tuple_percent;
      42              :     uint64      free_space;
      43              :     double      free_percent;
      44              : } output_type;
      45              : 
      46              : #define NUM_OUTPUT_COLUMNS 10
      47              : 
      48              : /*
      49              :  * This function takes an already open relation and scans its pages,
      50              :  * skipping those that have the corresponding visibility map bit set.
      51              :  * For pages we skip, we find the free space from the free space map
      52              :  * and approximate tuple_len on that basis. For the others, we count
      53              :  * the exact number of dead tuples etc.
      54              :  *
      55              :  * This scan is loosely based on vacuumlazy.c:lazy_scan_heap(), but
      56              :  * we do not try to avoid skipping single pages.
      57              :  */
      58              : static void
      59            2 : statapprox_heap(Relation rel, output_type *stat)
      60              : {
      61              :     BlockNumber scanned,
      62              :                 nblocks,
      63              :                 blkno;
      64            2 :     Buffer      vmbuffer = InvalidBuffer;
      65              :     BufferAccessStrategy bstrategy;
      66              :     TransactionId OldestXmin;
      67              : 
      68            2 :     OldestXmin = GetOldestNonRemovableTransactionId(rel);
      69            2 :     bstrategy = GetAccessStrategy(BAS_BULKREAD);
      70              : 
      71            2 :     nblocks = RelationGetNumberOfBlocks(rel);
      72            2 :     scanned = 0;
      73              : 
      74            2 :     for (blkno = 0; blkno < nblocks; blkno++)
      75              :     {
      76              :         Buffer      buf;
      77              :         Page        page;
      78              :         OffsetNumber offnum,
      79              :                     maxoff;
      80              :         Size        freespace;
      81              : 
      82            0 :         CHECK_FOR_INTERRUPTS();
      83              : 
      84              :         /*
      85              :          * If the page has only visible tuples, then we can find out the free
      86              :          * space from the FSM and move on.
      87              :          */
      88            0 :         if (VM_ALL_VISIBLE(rel, blkno, &vmbuffer))
      89              :         {
      90            0 :             freespace = GetRecordedFreeSpace(rel, blkno);
      91            0 :             stat->tuple_len += BLCKSZ - freespace;
      92            0 :             stat->free_space += freespace;
      93            0 :             continue;
      94              :         }
      95              : 
      96            0 :         buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno,
      97              :                                  RBM_NORMAL, bstrategy);
      98              : 
      99            0 :         LockBuffer(buf, BUFFER_LOCK_SHARE);
     100              : 
     101            0 :         page = BufferGetPage(buf);
     102              : 
     103            0 :         stat->free_space += PageGetExactFreeSpace(page);
     104              : 
     105              :         /* We may count the page as scanned even if it's new/empty */
     106            0 :         scanned++;
     107              : 
     108            0 :         if (PageIsNew(page) || PageIsEmpty(page))
     109              :         {
     110            0 :             UnlockReleaseBuffer(buf);
     111            0 :             continue;
     112              :         }
     113              : 
     114              :         /*
     115              :          * Look at each tuple on the page and decide whether it's live or
     116              :          * dead, then count it and its size. Unlike lazy_scan_heap, we can
     117              :          * afford to ignore problems and special cases.
     118              :          */
     119            0 :         maxoff = PageGetMaxOffsetNumber(page);
     120              : 
     121            0 :         for (offnum = FirstOffsetNumber;
     122            0 :              offnum <= maxoff;
     123            0 :              offnum = OffsetNumberNext(offnum))
     124              :         {
     125              :             ItemId      itemid;
     126              :             HeapTupleData tuple;
     127              : 
     128            0 :             itemid = PageGetItemId(page, offnum);
     129              : 
     130            0 :             if (!ItemIdIsUsed(itemid) || ItemIdIsRedirected(itemid) ||
     131            0 :                 ItemIdIsDead(itemid))
     132              :             {
     133            0 :                 continue;
     134              :             }
     135              : 
     136              :             Assert(ItemIdIsNormal(itemid));
     137              : 
     138            0 :             ItemPointerSet(&(tuple.t_self), blkno, offnum);
     139              : 
     140            0 :             tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid);
     141            0 :             tuple.t_len = ItemIdGetLength(itemid);
     142            0 :             tuple.t_tableOid = RelationGetRelid(rel);
     143              : 
     144              :             /*
     145              :              * We follow VACUUM's lead in counting INSERT_IN_PROGRESS tuples
     146              :              * as "dead" while DELETE_IN_PROGRESS tuples are "live".  We don't
     147              :              * bother distinguishing tuples inserted/deleted by our own
     148              :              * transaction.
     149              :              */
     150            0 :             switch (HeapTupleSatisfiesVacuum(&tuple, OldestXmin, buf))
     151              :             {
     152            0 :                 case HEAPTUPLE_LIVE:
     153              :                 case HEAPTUPLE_DELETE_IN_PROGRESS:
     154            0 :                     stat->tuple_len += tuple.t_len;
     155            0 :                     stat->tuple_count++;
     156            0 :                     break;
     157            0 :                 case HEAPTUPLE_DEAD:
     158              :                 case HEAPTUPLE_RECENTLY_DEAD:
     159              :                 case HEAPTUPLE_INSERT_IN_PROGRESS:
     160            0 :                     stat->dead_tuple_len += tuple.t_len;
     161            0 :                     stat->dead_tuple_count++;
     162            0 :                     break;
     163            0 :                 default:
     164            0 :                     elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
     165              :                     break;
     166              :             }
     167              :         }
     168              : 
     169            0 :         UnlockReleaseBuffer(buf);
     170              :     }
     171              : 
     172            2 :     stat->table_len = (uint64) nblocks * BLCKSZ;
     173              : 
     174              :     /*
     175              :      * We don't know how many tuples are in the pages we didn't scan, so
     176              :      * extrapolate the live-tuple count to the whole table in the same way
     177              :      * that VACUUM does.  (Like VACUUM, we're not taking a random sample, so
     178              :      * just extrapolating linearly seems unsafe.)  There should be no dead
     179              :      * tuples in all-visible pages, so no correction is needed for that, and
     180              :      * we already accounted for the space in those pages, too.
     181              :      */
     182            4 :     stat->tuple_count = vac_estimate_reltuples(rel, nblocks, scanned,
     183            2 :                                                stat->tuple_count);
     184              : 
     185              :     /* It's not clear if we could get -1 here, but be safe. */
     186            2 :     stat->tuple_count = Max(stat->tuple_count, 0);
     187              : 
     188              :     /*
     189              :      * Calculate percentages if the relation has one or more pages.
     190              :      */
     191            2 :     if (nblocks != 0)
     192              :     {
     193            0 :         stat->scanned_percent = 100.0 * scanned / nblocks;
     194            0 :         stat->tuple_percent = 100.0 * stat->tuple_len / stat->table_len;
     195            0 :         stat->dead_tuple_percent = 100.0 * stat->dead_tuple_len / stat->table_len;
     196            0 :         stat->free_percent = 100.0 * stat->free_space / stat->table_len;
     197              :     }
     198              : 
     199            2 :     if (BufferIsValid(vmbuffer))
     200              :     {
     201            0 :         ReleaseBuffer(vmbuffer);
     202            0 :         vmbuffer = InvalidBuffer;
     203              :     }
     204            2 : }
     205              : 
     206              : /*
     207              :  * Returns estimated live/dead tuple statistics for the given relid.
     208              :  *
     209              :  * The superuser() check here must be kept as the library might be upgraded
     210              :  * without the extension being upgraded, meaning that in pre-1.5 installations
     211              :  * these functions could be called by any user.
     212              :  */
     213              : Datum
     214            0 : pgstattuple_approx(PG_FUNCTION_ARGS)
     215              : {
     216            0 :     Oid         relid = PG_GETARG_OID(0);
     217              : 
     218            0 :     if (!superuser())
     219            0 :         ereport(ERROR,
     220              :                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     221              :                  errmsg("must be superuser to use pgstattuple functions")));
     222              : 
     223            0 :     PG_RETURN_DATUM(pgstattuple_approx_internal(relid, fcinfo));
     224              : }
     225              : 
     226              : /*
     227              :  * As of pgstattuple version 1.5, we no longer need to check if the user
     228              :  * is a superuser because we REVOKE EXECUTE on the SQL function from PUBLIC.
     229              :  * Users can then grant access to it based on their policies.
     230              :  *
     231              :  * Otherwise identical to pgstattuple_approx (above).
     232              :  */
     233              : Datum
     234            6 : pgstattuple_approx_v1_5(PG_FUNCTION_ARGS)
     235              : {
     236            6 :     Oid         relid = PG_GETARG_OID(0);
     237              : 
     238            6 :     PG_RETURN_DATUM(pgstattuple_approx_internal(relid, fcinfo));
     239              : }
     240              : 
     241              : Datum
     242            6 : pgstattuple_approx_internal(Oid relid, FunctionCallInfo fcinfo)
     243              : {
     244              :     Relation    rel;
     245            6 :     output_type stat = {0};
     246              :     TupleDesc   tupdesc;
     247              :     bool        nulls[NUM_OUTPUT_COLUMNS];
     248              :     Datum       values[NUM_OUTPUT_COLUMNS];
     249              :     HeapTuple   ret;
     250            6 :     int         i = 0;
     251              : 
     252            6 :     if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
     253            0 :         elog(ERROR, "return type must be a row type");
     254              : 
     255            6 :     if (tupdesc->natts != NUM_OUTPUT_COLUMNS)
     256            0 :         elog(ERROR, "incorrect number of output arguments");
     257              : 
     258            6 :     rel = relation_open(relid, AccessShareLock);
     259              : 
     260              :     /*
     261              :      * Reject attempts to read non-local temporary relations; we would be
     262              :      * likely to get wrong data since we have no visibility into the owning
     263              :      * session's local buffers.
     264              :      */
     265            6 :     if (RELATION_IS_OTHER_TEMP(rel))
     266            0 :         ereport(ERROR,
     267              :                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     268              :                  errmsg("cannot access temporary tables of other sessions")));
     269              : 
     270              :     /*
     271              :      * We support only relation kinds with a visibility map and a free space
     272              :      * map.
     273              :      */
     274            6 :     if (!(rel->rd_rel->relkind == RELKIND_RELATION ||
     275            5 :           rel->rd_rel->relkind == RELKIND_MATVIEW ||
     276            5 :           rel->rd_rel->relkind == RELKIND_TOASTVALUE))
     277            4 :         ereport(ERROR,
     278              :                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     279              :                  errmsg("relation \"%s\" is of wrong relation kind",
     280              :                         RelationGetRelationName(rel)),
     281              :                  errdetail_relkind_not_supported(rel->rd_rel->relkind)));
     282              : 
     283            2 :     if (rel->rd_rel->relam != HEAP_TABLE_AM_OID)
     284            0 :         ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     285              :                         errmsg("only heap AM is supported")));
     286              : 
     287            2 :     statapprox_heap(rel, &stat);
     288              : 
     289            2 :     relation_close(rel, AccessShareLock);
     290              : 
     291            2 :     memset(nulls, 0, sizeof(nulls));
     292              : 
     293            2 :     values[i++] = Int64GetDatum(stat.table_len);
     294            2 :     values[i++] = Float8GetDatum(stat.scanned_percent);
     295            2 :     values[i++] = Int64GetDatum(stat.tuple_count);
     296            2 :     values[i++] = Int64GetDatum(stat.tuple_len);
     297            2 :     values[i++] = Float8GetDatum(stat.tuple_percent);
     298            2 :     values[i++] = Int64GetDatum(stat.dead_tuple_count);
     299            2 :     values[i++] = Int64GetDatum(stat.dead_tuple_len);
     300            2 :     values[i++] = Float8GetDatum(stat.dead_tuple_percent);
     301            2 :     values[i++] = Int64GetDatum(stat.free_space);
     302            2 :     values[i++] = Float8GetDatum(stat.free_percent);
     303              : 
     304            2 :     ret = heap_form_tuple(tupdesc, values, nulls);
     305            2 :     return HeapTupleGetDatum(ret);
     306              : }
        

Generated by: LCOV version 2.0-1