LCOV - code coverage report
Current view: top level - contrib/pgstattuple - pgstatapprox.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 0.0 % 120 0
Test Date: 2026-01-26 10:56:24 Functions: 0.0 % 6 0
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            0 : PG_FUNCTION_INFO_V1(pgstattuple_approx);
      28            0 : 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            0 : statapprox_heap(Relation rel, output_type *stat)
      60              : {
      61            0 :         BlockNumber scanned,
      62              :                                 nblocks,
      63              :                                 blkno;
      64            0 :         Buffer          vmbuffer = InvalidBuffer;
      65            0 :         BufferAccessStrategy bstrategy;
      66            0 :         TransactionId OldestXmin;
      67              : 
      68            0 :         OldestXmin = GetOldestNonRemovableTransactionId(rel);
      69            0 :         bstrategy = GetAccessStrategy(BAS_BULKREAD);
      70              : 
      71            0 :         nblocks = RelationGetNumberOfBlocks(rel);
      72            0 :         scanned = 0;
      73              : 
      74            0 :         for (blkno = 0; blkno < nblocks; blkno++)
      75              :         {
      76            0 :                 Buffer          buf;
      77            0 :                 Page            page;
      78            0 :                 OffsetNumber offnum,
      79              :                                         maxoff;
      80            0 :                 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            0 :                                                                  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            0 :                         ItemId          itemid;
     126            0 :                         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            0 :                         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              :                                 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              :                                 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              :                                 default:
     164            0 :                                         elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
     165            0 :                                         break;
     166              :                         }
     167            0 :                 }
     168              : 
     169            0 :                 UnlockReleaseBuffer(buf);
     170            0 :         }
     171              : 
     172            0 :         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            0 :         stat->tuple_count = vac_estimate_reltuples(rel, nblocks, scanned,
     183            0 :                                                                                            stat->tuple_count);
     184              : 
     185              :         /* It's not clear if we could get -1 here, but be safe. */
     186            0 :         stat->tuple_count = Max(stat->tuple_count, 0);
     187              : 
     188              :         /*
     189              :          * Calculate percentages if the relation has one or more pages.
     190              :          */
     191            0 :         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            0 :         }
     198              : 
     199            0 :         if (BufferIsValid(vmbuffer))
     200              :         {
     201            0 :                 ReleaseBuffer(vmbuffer);
     202            0 :                 vmbuffer = InvalidBuffer;
     203            0 :         }
     204            0 : }
     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            0 : }
     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            0 : pgstattuple_approx_v1_5(PG_FUNCTION_ARGS)
     235              : {
     236            0 :         Oid                     relid = PG_GETARG_OID(0);
     237              : 
     238            0 :         PG_RETURN_DATUM(pgstattuple_approx_internal(relid, fcinfo));
     239            0 : }
     240              : 
     241              : Datum
     242            0 : pgstattuple_approx_internal(Oid relid, FunctionCallInfo fcinfo)
     243              : {
     244            0 :         Relation        rel;
     245            0 :         output_type stat = {0};
     246            0 :         TupleDesc       tupdesc;
     247            0 :         bool            nulls[NUM_OUTPUT_COLUMNS];
     248            0 :         Datum           values[NUM_OUTPUT_COLUMNS];
     249            0 :         HeapTuple       ret;
     250            0 :         int                     i = 0;
     251              : 
     252            0 :         if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
     253            0 :                 elog(ERROR, "return type must be a row type");
     254              : 
     255            0 :         if (tupdesc->natts != NUM_OUTPUT_COLUMNS)
     256            0 :                 elog(ERROR, "incorrect number of output arguments");
     257              : 
     258            0 :         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            0 :         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            0 :         if (!(rel->rd_rel->relkind == RELKIND_RELATION ||
     275            0 :                   rel->rd_rel->relkind == RELKIND_MATVIEW ||
     276            0 :                   rel->rd_rel->relkind == RELKIND_TOASTVALUE))
     277            0 :                 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            0 :         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            0 :         statapprox_heap(rel, &stat);
     288              : 
     289            0 :         relation_close(rel, AccessShareLock);
     290              : 
     291            0 :         memset(nulls, 0, sizeof(nulls));
     292              : 
     293            0 :         values[i++] = Int64GetDatum(stat.table_len);
     294            0 :         values[i++] = Float8GetDatum(stat.scanned_percent);
     295            0 :         values[i++] = Int64GetDatum(stat.tuple_count);
     296            0 :         values[i++] = Int64GetDatum(stat.tuple_len);
     297            0 :         values[i++] = Float8GetDatum(stat.tuple_percent);
     298            0 :         values[i++] = Int64GetDatum(stat.dead_tuple_count);
     299            0 :         values[i++] = Int64GetDatum(stat.dead_tuple_len);
     300            0 :         values[i++] = Float8GetDatum(stat.dead_tuple_percent);
     301            0 :         values[i++] = Int64GetDatum(stat.free_space);
     302            0 :         values[i++] = Float8GetDatum(stat.free_percent);
     303              : 
     304            0 :         ret = heap_form_tuple(tupdesc, values, nulls);
     305            0 :         return HeapTupleGetDatum(ret);
     306            0 : }
        

Generated by: LCOV version 2.3.2-1