LCOV - code coverage report
Current view: top level - contrib/pg_visibility - pg_visibility.c (source / functions) Hit Total Coverage
Test: PostgreSQL 13devel Lines: 198 286 69.2 %
Date: 2019-11-21 12:06:29 Functions: 19 23 82.6 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * pg_visibility.c
       4             :  *    display visibility map information and page-level visibility bits
       5             :  *
       6             :  * Copyright (c) 2016-2019, PostgreSQL Global Development Group
       7             :  *
       8             :  *    contrib/pg_visibility/pg_visibility.c
       9             :  *-------------------------------------------------------------------------
      10             :  */
      11             : #include "postgres.h"
      12             : 
      13             : #include "access/heapam.h"
      14             : #include "access/htup_details.h"
      15             : #include "access/visibilitymap.h"
      16             : #include "catalog/pg_type.h"
      17             : #include "catalog/storage_xlog.h"
      18             : #include "funcapi.h"
      19             : #include "miscadmin.h"
      20             : #include "storage/bufmgr.h"
      21             : #include "storage/procarray.h"
      22             : #include "storage/smgr.h"
      23             : #include "utils/rel.h"
      24             : #include "utils/snapmgr.h"
      25             : 
      26           2 : PG_MODULE_MAGIC;
      27             : 
      28             : typedef struct vbits
      29             : {
      30             :     BlockNumber next;
      31             :     BlockNumber count;
      32             :     uint8       bits[FLEXIBLE_ARRAY_MEMBER];
      33             : } vbits;
      34             : 
      35             : typedef struct corrupt_items
      36             : {
      37             :     BlockNumber next;
      38             :     BlockNumber count;
      39             :     ItemPointer tids;
      40             : } corrupt_items;
      41             : 
      42           2 : PG_FUNCTION_INFO_V1(pg_visibility_map);
      43           4 : PG_FUNCTION_INFO_V1(pg_visibility_map_rel);
      44           4 : PG_FUNCTION_INFO_V1(pg_visibility);
      45           4 : PG_FUNCTION_INFO_V1(pg_visibility_rel);
      46           4 : PG_FUNCTION_INFO_V1(pg_visibility_map_summary);
      47           4 : PG_FUNCTION_INFO_V1(pg_check_frozen);
      48           2 : PG_FUNCTION_INFO_V1(pg_check_visible);
      49           4 : PG_FUNCTION_INFO_V1(pg_truncate_visibility_map);
      50             : 
      51             : static TupleDesc pg_visibility_tupdesc(bool include_blkno, bool include_pd);
      52             : static vbits *collect_visibility_data(Oid relid, bool include_pd);
      53             : static corrupt_items *collect_corrupt_items(Oid relid, bool all_visible,
      54             :                                             bool all_frozen);
      55             : static void record_corrupt_item(corrupt_items *items, ItemPointer tid);
      56             : static bool tuple_all_visible(HeapTuple tup, TransactionId OldestXmin,
      57             :                               Buffer buffer);
      58             : static void check_relation_relkind(Relation rel);
      59             : 
      60             : /*
      61             :  * Visibility map information for a single block of a relation.
      62             :  *
      63             :  * Note: the VM code will silently return zeroes for pages past the end
      64             :  * of the map, so we allow probes up to MaxBlockNumber regardless of the
      65             :  * actual relation size.
      66             :  */
      67             : Datum
      68           0 : pg_visibility_map(PG_FUNCTION_ARGS)
      69             : {
      70           0 :     Oid         relid = PG_GETARG_OID(0);
      71           0 :     int64       blkno = PG_GETARG_INT64(1);
      72             :     int32       mapbits;
      73             :     Relation    rel;
      74           0 :     Buffer      vmbuffer = InvalidBuffer;
      75             :     TupleDesc   tupdesc;
      76             :     Datum       values[2];
      77             :     bool        nulls[2];
      78             : 
      79           0 :     rel = relation_open(relid, AccessShareLock);
      80             : 
      81             :     /* Only some relkinds have a visibility map */
      82           0 :     check_relation_relkind(rel);
      83             : 
      84           0 :     if (blkno < 0 || blkno > MaxBlockNumber)
      85           0 :         ereport(ERROR,
      86             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
      87             :                  errmsg("invalid block number")));
      88             : 
      89           0 :     tupdesc = pg_visibility_tupdesc(false, false);
      90           0 :     MemSet(nulls, 0, sizeof(nulls));
      91             : 
      92           0 :     mapbits = (int32) visibilitymap_get_status(rel, blkno, &vmbuffer);
      93           0 :     if (vmbuffer != InvalidBuffer)
      94           0 :         ReleaseBuffer(vmbuffer);
      95           0 :     values[0] = BoolGetDatum((mapbits & VISIBILITYMAP_ALL_VISIBLE) != 0);
      96           0 :     values[1] = BoolGetDatum((mapbits & VISIBILITYMAP_ALL_FROZEN) != 0);
      97             : 
      98           0 :     relation_close(rel, AccessShareLock);
      99             : 
     100           0 :     PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
     101             : }
     102             : 
     103             : /*
     104             :  * Visibility map information for a single block of a relation, plus the
     105             :  * page-level information for the same block.
     106             :  */
     107             : Datum
     108          12 : pg_visibility(PG_FUNCTION_ARGS)
     109             : {
     110          12 :     Oid         relid = PG_GETARG_OID(0);
     111          12 :     int64       blkno = PG_GETARG_INT64(1);
     112             :     int32       mapbits;
     113             :     Relation    rel;
     114          12 :     Buffer      vmbuffer = InvalidBuffer;
     115             :     Buffer      buffer;
     116             :     Page        page;
     117             :     TupleDesc   tupdesc;
     118             :     Datum       values[3];
     119             :     bool        nulls[3];
     120             : 
     121          12 :     rel = relation_open(relid, AccessShareLock);
     122             : 
     123             :     /* Only some relkinds have a visibility map */
     124          12 :     check_relation_relkind(rel);
     125             : 
     126           2 :     if (blkno < 0 || blkno > MaxBlockNumber)
     127           0 :         ereport(ERROR,
     128             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     129             :                  errmsg("invalid block number")));
     130             : 
     131           2 :     tupdesc = pg_visibility_tupdesc(false, true);
     132           2 :     MemSet(nulls, 0, sizeof(nulls));
     133             : 
     134           2 :     mapbits = (int32) visibilitymap_get_status(rel, blkno, &vmbuffer);
     135           2 :     if (vmbuffer != InvalidBuffer)
     136           2 :         ReleaseBuffer(vmbuffer);
     137           2 :     values[0] = BoolGetDatum((mapbits & VISIBILITYMAP_ALL_VISIBLE) != 0);
     138           2 :     values[1] = BoolGetDatum((mapbits & VISIBILITYMAP_ALL_FROZEN) != 0);
     139             : 
     140             :     /* Here we have to explicitly check rel size ... */
     141           2 :     if (blkno < RelationGetNumberOfBlocks(rel))
     142             :     {
     143           2 :         buffer = ReadBuffer(rel, blkno);
     144           2 :         LockBuffer(buffer, BUFFER_LOCK_SHARE);
     145             : 
     146           2 :         page = BufferGetPage(buffer);
     147           2 :         values[2] = BoolGetDatum(PageIsAllVisible(page));
     148             : 
     149           2 :         UnlockReleaseBuffer(buffer);
     150             :     }
     151             :     else
     152             :     {
     153             :         /* As with the vismap, silently return 0 for pages past EOF */
     154           0 :         values[2] = BoolGetDatum(false);
     155             :     }
     156             : 
     157           2 :     relation_close(rel, AccessShareLock);
     158             : 
     159           2 :     PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
     160             : }
     161             : 
     162             : /*
     163             :  * Visibility map information for every block in a relation.
     164             :  */
     165             : Datum
     166          14 : pg_visibility_map_rel(PG_FUNCTION_ARGS)
     167             : {
     168             :     FuncCallContext *funcctx;
     169             :     vbits      *info;
     170             : 
     171          14 :     if (SRF_IS_FIRSTCALL())
     172             :     {
     173          12 :         Oid         relid = PG_GETARG_OID(0);
     174             :         MemoryContext oldcontext;
     175             : 
     176          12 :         funcctx = SRF_FIRSTCALL_INIT();
     177          12 :         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
     178          12 :         funcctx->tuple_desc = pg_visibility_tupdesc(true, false);
     179             :         /* collect_visibility_data will verify the relkind */
     180          12 :         funcctx->user_fctx = collect_visibility_data(relid, false);
     181           2 :         MemoryContextSwitchTo(oldcontext);
     182             :     }
     183             : 
     184           4 :     funcctx = SRF_PERCALL_SETUP();
     185           4 :     info = (vbits *) funcctx->user_fctx;
     186             : 
     187           4 :     if (info->next < info->count)
     188             :     {
     189             :         Datum       values[3];
     190             :         bool        nulls[3];
     191             :         HeapTuple   tuple;
     192             : 
     193           2 :         MemSet(nulls, 0, sizeof(nulls));
     194           2 :         values[0] = Int64GetDatum(info->next);
     195           2 :         values[1] = BoolGetDatum((info->bits[info->next] & (1 << 0)) != 0);
     196           2 :         values[2] = BoolGetDatum((info->bits[info->next] & (1 << 1)) != 0);
     197           2 :         info->next++;
     198             : 
     199           2 :         tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
     200           2 :         SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
     201             :     }
     202             : 
     203           2 :     SRF_RETURN_DONE(funcctx);
     204             : }
     205             : 
     206             : /*
     207             :  * Visibility map information for every block in a relation, plus the page
     208             :  * level information for each block.
     209             :  */
     210             : Datum
     211          12 : pg_visibility_rel(PG_FUNCTION_ARGS)
     212             : {
     213             :     FuncCallContext *funcctx;
     214             :     vbits      *info;
     215             : 
     216          12 :     if (SRF_IS_FIRSTCALL())
     217             :     {
     218           8 :         Oid         relid = PG_GETARG_OID(0);
     219             :         MemoryContext oldcontext;
     220             : 
     221           8 :         funcctx = SRF_FIRSTCALL_INIT();
     222           8 :         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
     223           8 :         funcctx->tuple_desc = pg_visibility_tupdesc(true, true);
     224             :         /* collect_visibility_data will verify the relkind */
     225           8 :         funcctx->user_fctx = collect_visibility_data(relid, true);
     226           8 :         MemoryContextSwitchTo(oldcontext);
     227             :     }
     228             : 
     229          12 :     funcctx = SRF_PERCALL_SETUP();
     230          12 :     info = (vbits *) funcctx->user_fctx;
     231             : 
     232          12 :     if (info->next < info->count)
     233             :     {
     234             :         Datum       values[4];
     235             :         bool        nulls[4];
     236             :         HeapTuple   tuple;
     237             : 
     238           4 :         MemSet(nulls, 0, sizeof(nulls));
     239           4 :         values[0] = Int64GetDatum(info->next);
     240           4 :         values[1] = BoolGetDatum((info->bits[info->next] & (1 << 0)) != 0);
     241           4 :         values[2] = BoolGetDatum((info->bits[info->next] & (1 << 1)) != 0);
     242           4 :         values[3] = BoolGetDatum((info->bits[info->next] & (1 << 2)) != 0);
     243           4 :         info->next++;
     244             : 
     245           4 :         tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
     246           4 :         SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
     247             :     }
     248             : 
     249           8 :     SRF_RETURN_DONE(funcctx);
     250             : }
     251             : 
     252             : /*
     253             :  * Count the number of all-visible and all-frozen pages in the visibility
     254             :  * map for a particular relation.
     255             :  */
     256             : Datum
     257          12 : pg_visibility_map_summary(PG_FUNCTION_ARGS)
     258             : {
     259          12 :     Oid         relid = PG_GETARG_OID(0);
     260             :     Relation    rel;
     261             :     BlockNumber nblocks;
     262             :     BlockNumber blkno;
     263          12 :     Buffer      vmbuffer = InvalidBuffer;
     264          12 :     int64       all_visible = 0;
     265          12 :     int64       all_frozen = 0;
     266             :     TupleDesc   tupdesc;
     267             :     Datum       values[2];
     268             :     bool        nulls[2];
     269             : 
     270          12 :     rel = relation_open(relid, AccessShareLock);
     271             : 
     272             :     /* Only some relkinds have a visibility map */
     273          12 :     check_relation_relkind(rel);
     274             : 
     275           2 :     nblocks = RelationGetNumberOfBlocks(rel);
     276             : 
     277           4 :     for (blkno = 0; blkno < nblocks; ++blkno)
     278             :     {
     279             :         int32       mapbits;
     280             : 
     281             :         /* Make sure we are interruptible. */
     282           2 :         CHECK_FOR_INTERRUPTS();
     283             : 
     284             :         /* Get map info. */
     285           2 :         mapbits = (int32) visibilitymap_get_status(rel, blkno, &vmbuffer);
     286           2 :         if ((mapbits & VISIBILITYMAP_ALL_VISIBLE) != 0)
     287           2 :             ++all_visible;
     288           2 :         if ((mapbits & VISIBILITYMAP_ALL_FROZEN) != 0)
     289           0 :             ++all_frozen;
     290             :     }
     291             : 
     292             :     /* Clean up. */
     293           2 :     if (vmbuffer != InvalidBuffer)
     294           2 :         ReleaseBuffer(vmbuffer);
     295           2 :     relation_close(rel, AccessShareLock);
     296             : 
     297           2 :     tupdesc = CreateTemplateTupleDesc(2);
     298           2 :     TupleDescInitEntry(tupdesc, (AttrNumber) 1, "all_visible", INT8OID, -1, 0);
     299           2 :     TupleDescInitEntry(tupdesc, (AttrNumber) 2, "all_frozen", INT8OID, -1, 0);
     300           2 :     tupdesc = BlessTupleDesc(tupdesc);
     301             : 
     302           2 :     MemSet(nulls, 0, sizeof(nulls));
     303           2 :     values[0] = Int64GetDatum(all_visible);
     304           2 :     values[1] = Int64GetDatum(all_frozen);
     305             : 
     306           2 :     PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
     307             : }
     308             : 
     309             : /*
     310             :  * Return the TIDs of non-frozen tuples present in pages marked all-frozen
     311             :  * in the visibility map.  We hope no one will ever find any, but there could
     312             :  * be bugs, database corruption, etc.
     313             :  */
     314             : Datum
     315          12 : pg_check_frozen(PG_FUNCTION_ARGS)
     316             : {
     317             :     FuncCallContext *funcctx;
     318             :     corrupt_items *items;
     319             : 
     320          12 :     if (SRF_IS_FIRSTCALL())
     321             :     {
     322          12 :         Oid         relid = PG_GETARG_OID(0);
     323             :         MemoryContext oldcontext;
     324             : 
     325          12 :         funcctx = SRF_FIRSTCALL_INIT();
     326          12 :         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
     327             :         /* collect_corrupt_items will verify the relkind */
     328          12 :         funcctx->user_fctx = collect_corrupt_items(relid, false, true);
     329           2 :         MemoryContextSwitchTo(oldcontext);
     330             :     }
     331             : 
     332           2 :     funcctx = SRF_PERCALL_SETUP();
     333           2 :     items = (corrupt_items *) funcctx->user_fctx;
     334             : 
     335           2 :     if (items->next < items->count)
     336           0 :         SRF_RETURN_NEXT(funcctx, PointerGetDatum(&items->tids[items->next++]));
     337             : 
     338           2 :     SRF_RETURN_DONE(funcctx);
     339             : }
     340             : 
     341             : /*
     342             :  * Return the TIDs of not-all-visible tuples in pages marked all-visible
     343             :  * in the visibility map.  We hope no one will ever find any, but there could
     344             :  * be bugs, database corruption, etc.
     345             :  */
     346             : Datum
     347           0 : pg_check_visible(PG_FUNCTION_ARGS)
     348             : {
     349             :     FuncCallContext *funcctx;
     350             :     corrupt_items *items;
     351             : 
     352           0 :     if (SRF_IS_FIRSTCALL())
     353             :     {
     354           0 :         Oid         relid = PG_GETARG_OID(0);
     355             :         MemoryContext oldcontext;
     356             : 
     357           0 :         funcctx = SRF_FIRSTCALL_INIT();
     358           0 :         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
     359             :         /* collect_corrupt_items will verify the relkind */
     360           0 :         funcctx->user_fctx = collect_corrupt_items(relid, true, false);
     361           0 :         MemoryContextSwitchTo(oldcontext);
     362             :     }
     363             : 
     364           0 :     funcctx = SRF_PERCALL_SETUP();
     365           0 :     items = (corrupt_items *) funcctx->user_fctx;
     366             : 
     367           0 :     if (items->next < items->count)
     368           0 :         SRF_RETURN_NEXT(funcctx, PointerGetDatum(&items->tids[items->next++]));
     369             : 
     370           0 :     SRF_RETURN_DONE(funcctx);
     371             : }
     372             : 
     373             : /*
     374             :  * Remove the visibility map fork for a relation.  If there turn out to be
     375             :  * any bugs in the visibility map code that require rebuilding the VM, this
     376             :  * provides users with a way to do it that is cleaner than shutting down the
     377             :  * server and removing files by hand.
     378             :  *
     379             :  * This is a cut-down version of RelationTruncate.
     380             :  */
     381             : Datum
     382          12 : pg_truncate_visibility_map(PG_FUNCTION_ARGS)
     383             : {
     384          12 :     Oid         relid = PG_GETARG_OID(0);
     385             :     Relation    rel;
     386             :     ForkNumber  fork;
     387             :     BlockNumber block;
     388             : 
     389          12 :     rel = relation_open(relid, AccessExclusiveLock);
     390             : 
     391             :     /* Only some relkinds have a visibility map */
     392          12 :     check_relation_relkind(rel);
     393             : 
     394           2 :     RelationOpenSmgr(rel);
     395           2 :     rel->rd_smgr->smgr_vm_nblocks = InvalidBlockNumber;
     396             : 
     397           2 :     block = visibilitymap_prepare_truncate(rel, 0);
     398           2 :     if (BlockNumberIsValid(block))
     399             :     {
     400           2 :         fork = VISIBILITYMAP_FORKNUM;
     401           2 :         smgrtruncate(rel->rd_smgr, &fork, 1, &block);
     402             :     }
     403             : 
     404           2 :     if (RelationNeedsWAL(rel))
     405             :     {
     406             :         xl_smgr_truncate xlrec;
     407             : 
     408           2 :         xlrec.blkno = 0;
     409           2 :         xlrec.rnode = rel->rd_node;
     410           2 :         xlrec.flags = SMGR_TRUNCATE_VM;
     411             : 
     412           2 :         XLogBeginInsert();
     413           2 :         XLogRegisterData((char *) &xlrec, sizeof(xlrec));
     414             : 
     415           2 :         XLogInsert(RM_SMGR_ID, XLOG_SMGR_TRUNCATE | XLR_SPECIAL_REL_UPDATE);
     416             :     }
     417             : 
     418             :     /*
     419             :      * Release the lock right away, not at commit time.
     420             :      *
     421             :      * It would be a problem to release the lock prior to commit if this
     422             :      * truncate operation sends any transactional invalidation messages. Other
     423             :      * backends would potentially be able to lock the relation without
     424             :      * processing them in the window of time between when we release the lock
     425             :      * here and when we sent the messages at our eventual commit.  However,
     426             :      * we're currently only sending a non-transactional smgr invalidation,
     427             :      * which will have been posted to shared memory immediately from within
     428             :      * smgr_truncate.  Therefore, there should be no race here.
     429             :      *
     430             :      * The reason why it's desirable to release the lock early here is because
     431             :      * of the possibility that someone will need to use this to blow away many
     432             :      * visibility map forks at once.  If we can't release the lock until
     433             :      * commit time, the transaction doing this will accumulate
     434             :      * AccessExclusiveLocks on all of those relations at the same time, which
     435             :      * is undesirable. However, if this turns out to be unsafe we may have no
     436             :      * choice...
     437             :      */
     438           2 :     relation_close(rel, AccessExclusiveLock);
     439             : 
     440             :     /* Nothing to return. */
     441           2 :     PG_RETURN_VOID();
     442             : }
     443             : 
     444             : /*
     445             :  * Helper function to construct whichever TupleDesc we need for a particular
     446             :  * call.
     447             :  */
     448             : static TupleDesc
     449          22 : pg_visibility_tupdesc(bool include_blkno, bool include_pd)
     450             : {
     451             :     TupleDesc   tupdesc;
     452          22 :     AttrNumber  maxattr = 2;
     453          22 :     AttrNumber  a = 0;
     454             : 
     455          22 :     if (include_blkno)
     456          20 :         ++maxattr;
     457          22 :     if (include_pd)
     458          10 :         ++maxattr;
     459          22 :     tupdesc = CreateTemplateTupleDesc(maxattr);
     460          22 :     if (include_blkno)
     461          20 :         TupleDescInitEntry(tupdesc, ++a, "blkno", INT8OID, -1, 0);
     462          22 :     TupleDescInitEntry(tupdesc, ++a, "all_visible", BOOLOID, -1, 0);
     463          22 :     TupleDescInitEntry(tupdesc, ++a, "all_frozen", BOOLOID, -1, 0);
     464          22 :     if (include_pd)
     465          10 :         TupleDescInitEntry(tupdesc, ++a, "pd_all_visible", BOOLOID, -1, 0);
     466             :     Assert(a == maxattr);
     467             : 
     468          22 :     return BlessTupleDesc(tupdesc);
     469             : }
     470             : 
     471             : /*
     472             :  * Collect visibility data about a relation.
     473             :  *
     474             :  * Checks relkind of relid and will throw an error if the relation does not
     475             :  * have a VM.
     476             :  */
     477             : static vbits *
     478          20 : collect_visibility_data(Oid relid, bool include_pd)
     479             : {
     480             :     Relation    rel;
     481             :     BlockNumber nblocks;
     482             :     vbits      *info;
     483             :     BlockNumber blkno;
     484          20 :     Buffer      vmbuffer = InvalidBuffer;
     485          20 :     BufferAccessStrategy bstrategy = GetAccessStrategy(BAS_BULKREAD);
     486             : 
     487          20 :     rel = relation_open(relid, AccessShareLock);
     488             : 
     489             :     /* Only some relkinds have a visibility map */
     490          20 :     check_relation_relkind(rel);
     491             : 
     492          10 :     nblocks = RelationGetNumberOfBlocks(rel);
     493          10 :     info = palloc0(offsetof(vbits, bits) + nblocks);
     494          10 :     info->next = 0;
     495          10 :     info->count = nblocks;
     496             : 
     497          16 :     for (blkno = 0; blkno < nblocks; ++blkno)
     498             :     {
     499             :         int32       mapbits;
     500             : 
     501             :         /* Make sure we are interruptible. */
     502           6 :         CHECK_FOR_INTERRUPTS();
     503             : 
     504             :         /* Get map info. */
     505           6 :         mapbits = (int32) visibilitymap_get_status(rel, blkno, &vmbuffer);
     506           6 :         if ((mapbits & VISIBILITYMAP_ALL_VISIBLE) != 0)
     507           4 :             info->bits[blkno] |= (1 << 0);
     508           6 :         if ((mapbits & VISIBILITYMAP_ALL_FROZEN) != 0)
     509           0 :             info->bits[blkno] |= (1 << 1);
     510             : 
     511             :         /*
     512             :          * Page-level data requires reading every block, so only get it if the
     513             :          * caller needs it.  Use a buffer access strategy, too, to prevent
     514             :          * cache-trashing.
     515             :          */
     516           6 :         if (include_pd)
     517             :         {
     518             :             Buffer      buffer;
     519             :             Page        page;
     520             : 
     521           4 :             buffer = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
     522             :                                         bstrategy);
     523           4 :             LockBuffer(buffer, BUFFER_LOCK_SHARE);
     524             : 
     525           4 :             page = BufferGetPage(buffer);
     526           4 :             if (PageIsAllVisible(page))
     527           2 :                 info->bits[blkno] |= (1 << 2);
     528             : 
     529           4 :             UnlockReleaseBuffer(buffer);
     530             :         }
     531             :     }
     532             : 
     533             :     /* Clean up. */
     534          10 :     if (vmbuffer != InvalidBuffer)
     535           4 :         ReleaseBuffer(vmbuffer);
     536          10 :     relation_close(rel, AccessShareLock);
     537             : 
     538          10 :     return info;
     539             : }
     540             : 
     541             : /*
     542             :  * Returns a list of items whose visibility map information does not match
     543             :  * the status of the tuples on the page.
     544             :  *
     545             :  * If all_visible is passed as true, this will include all items which are
     546             :  * on pages marked as all-visible in the visibility map but which do not
     547             :  * seem to in fact be all-visible.
     548             :  *
     549             :  * If all_frozen is passed as true, this will include all items which are
     550             :  * on pages marked as all-frozen but which do not seem to in fact be frozen.
     551             :  *
     552             :  * Checks relkind of relid and will throw an error if the relation does not
     553             :  * have a VM.
     554             :  */
     555             : static corrupt_items *
     556          12 : collect_corrupt_items(Oid relid, bool all_visible, bool all_frozen)
     557             : {
     558             :     Relation    rel;
     559             :     BlockNumber nblocks;
     560             :     corrupt_items *items;
     561             :     BlockNumber blkno;
     562          12 :     Buffer      vmbuffer = InvalidBuffer;
     563          12 :     BufferAccessStrategy bstrategy = GetAccessStrategy(BAS_BULKREAD);
     564          12 :     TransactionId OldestXmin = InvalidTransactionId;
     565             : 
     566          12 :     if (all_visible)
     567             :     {
     568             :         /* Don't pass rel; that will fail in recovery. */
     569           0 :         OldestXmin = GetOldestXmin(NULL, PROCARRAY_FLAGS_VACUUM);
     570             :     }
     571             : 
     572          12 :     rel = relation_open(relid, AccessShareLock);
     573             : 
     574             :     /* Only some relkinds have a visibility map */
     575          12 :     check_relation_relkind(rel);
     576             : 
     577           2 :     nblocks = RelationGetNumberOfBlocks(rel);
     578             : 
     579             :     /*
     580             :      * Guess an initial array size. We don't expect many corrupted tuples, so
     581             :      * start with a small array.  This function uses the "next" field to track
     582             :      * the next offset where we can store an item (which is the same thing as
     583             :      * the number of items found so far) and the "count" field to track the
     584             :      * number of entries allocated.  We'll repurpose these fields before
     585             :      * returning.
     586             :      */
     587           2 :     items = palloc0(sizeof(corrupt_items));
     588           2 :     items->next = 0;
     589           2 :     items->count = 64;
     590           2 :     items->tids = palloc(items->count * sizeof(ItemPointerData));
     591             : 
     592             :     /* Loop over every block in the relation. */
     593           4 :     for (blkno = 0; blkno < nblocks; ++blkno)
     594             :     {
     595           2 :         bool        check_frozen = false;
     596           2 :         bool        check_visible = false;
     597             :         Buffer      buffer;
     598             :         Page        page;
     599             :         OffsetNumber offnum,
     600             :                     maxoff;
     601             : 
     602             :         /* Make sure we are interruptible. */
     603           2 :         CHECK_FOR_INTERRUPTS();
     604             : 
     605             :         /* Use the visibility map to decide whether to check this page. */
     606           2 :         if (all_frozen && VM_ALL_FROZEN(rel, blkno, &vmbuffer))
     607           0 :             check_frozen = true;
     608           2 :         if (all_visible && VM_ALL_VISIBLE(rel, blkno, &vmbuffer))
     609           0 :             check_visible = true;
     610           2 :         if (!check_visible && !check_frozen)
     611           2 :             continue;
     612             : 
     613             :         /* Read and lock the page. */
     614           0 :         buffer = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
     615             :                                     bstrategy);
     616           0 :         LockBuffer(buffer, BUFFER_LOCK_SHARE);
     617             : 
     618           0 :         page = BufferGetPage(buffer);
     619           0 :         maxoff = PageGetMaxOffsetNumber(page);
     620             : 
     621             :         /*
     622             :          * The visibility map bits might have changed while we were acquiring
     623             :          * the page lock.  Recheck to avoid returning spurious results.
     624             :          */
     625           0 :         if (check_frozen && !VM_ALL_FROZEN(rel, blkno, &vmbuffer))
     626           0 :             check_frozen = false;
     627           0 :         if (check_visible && !VM_ALL_VISIBLE(rel, blkno, &vmbuffer))
     628           0 :             check_visible = false;
     629           0 :         if (!check_visible && !check_frozen)
     630             :         {
     631           0 :             UnlockReleaseBuffer(buffer);
     632           0 :             continue;
     633             :         }
     634             : 
     635             :         /* Iterate over each tuple on the page. */
     636           0 :         for (offnum = FirstOffsetNumber;
     637             :              offnum <= maxoff;
     638           0 :              offnum = OffsetNumberNext(offnum))
     639             :         {
     640             :             HeapTupleData tuple;
     641             :             ItemId      itemid;
     642             : 
     643           0 :             itemid = PageGetItemId(page, offnum);
     644             : 
     645             :             /* Unused or redirect line pointers are of no interest. */
     646           0 :             if (!ItemIdIsUsed(itemid) || ItemIdIsRedirected(itemid))
     647           0 :                 continue;
     648             : 
     649             :             /* Dead line pointers are neither all-visible nor frozen. */
     650           0 :             if (ItemIdIsDead(itemid))
     651             :             {
     652           0 :                 ItemPointerSet(&(tuple.t_self), blkno, offnum);
     653           0 :                 record_corrupt_item(items, &tuple.t_self);
     654           0 :                 continue;
     655             :             }
     656             : 
     657             :             /* Initialize a HeapTupleData structure for checks below. */
     658           0 :             ItemPointerSet(&(tuple.t_self), blkno, offnum);
     659           0 :             tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid);
     660           0 :             tuple.t_len = ItemIdGetLength(itemid);
     661           0 :             tuple.t_tableOid = relid;
     662             : 
     663             :             /*
     664             :              * If we're checking whether the page is all-visible, we expect
     665             :              * the tuple to be all-visible.
     666             :              */
     667           0 :             if (check_visible &&
     668           0 :                 !tuple_all_visible(&tuple, OldestXmin, buffer))
     669             :             {
     670             :                 TransactionId RecomputedOldestXmin;
     671             : 
     672             :                 /*
     673             :                  * Time has passed since we computed OldestXmin, so it's
     674             :                  * possible that this tuple is all-visible in reality even
     675             :                  * though it doesn't appear so based on our
     676             :                  * previously-computed value.  Let's compute a new value so we
     677             :                  * can be certain whether there is a problem.
     678             :                  *
     679             :                  * From a concurrency point of view, it sort of sucks to
     680             :                  * retake ProcArrayLock here while we're holding the buffer
     681             :                  * exclusively locked, but it should be safe against
     682             :                  * deadlocks, because surely GetOldestXmin() should never take
     683             :                  * a buffer lock. And this shouldn't happen often, so it's
     684             :                  * worth being careful so as to avoid false positives.
     685             :                  */
     686           0 :                 RecomputedOldestXmin = GetOldestXmin(NULL, PROCARRAY_FLAGS_VACUUM);
     687             : 
     688           0 :                 if (!TransactionIdPrecedes(OldestXmin, RecomputedOldestXmin))
     689           0 :                     record_corrupt_item(items, &tuple.t_self);
     690             :                 else
     691             :                 {
     692           0 :                     OldestXmin = RecomputedOldestXmin;
     693           0 :                     if (!tuple_all_visible(&tuple, OldestXmin, buffer))
     694           0 :                         record_corrupt_item(items, &tuple.t_self);
     695             :                 }
     696             :             }
     697             : 
     698             :             /*
     699             :              * If we're checking whether the page is all-frozen, we expect the
     700             :              * tuple to be in a state where it will never need freezing.
     701             :              */
     702           0 :             if (check_frozen)
     703             :             {
     704           0 :                 if (heap_tuple_needs_eventual_freeze(tuple.t_data))
     705           0 :                     record_corrupt_item(items, &tuple.t_self);
     706             :             }
     707             :         }
     708             : 
     709           0 :         UnlockReleaseBuffer(buffer);
     710             :     }
     711             : 
     712             :     /* Clean up. */
     713           2 :     if (vmbuffer != InvalidBuffer)
     714           2 :         ReleaseBuffer(vmbuffer);
     715           2 :     relation_close(rel, AccessShareLock);
     716             : 
     717             :     /*
     718             :      * Before returning, repurpose the fields to match caller's expectations.
     719             :      * next is now the next item that should be read (rather than written) and
     720             :      * count is now the number of items we wrote (rather than the number we
     721             :      * allocated).
     722             :      */
     723           2 :     items->count = items->next;
     724           2 :     items->next = 0;
     725             : 
     726           2 :     return items;
     727             : }
     728             : 
     729             : /*
     730             :  * Remember one corrupt item.
     731             :  */
     732             : static void
     733           0 : record_corrupt_item(corrupt_items *items, ItemPointer tid)
     734             : {
     735             :     /* enlarge output array if needed. */
     736           0 :     if (items->next >= items->count)
     737             :     {
     738           0 :         items->count *= 2;
     739           0 :         items->tids = repalloc(items->tids,
     740           0 :                                items->count * sizeof(ItemPointerData));
     741             :     }
     742             :     /* and add the new item */
     743           0 :     items->tids[items->next++] = *tid;
     744           0 : }
     745             : 
     746             : /*
     747             :  * Check whether a tuple is all-visible relative to a given OldestXmin value.
     748             :  * The buffer should contain the tuple and should be locked and pinned.
     749             :  */
     750             : static bool
     751           0 : tuple_all_visible(HeapTuple tup, TransactionId OldestXmin, Buffer buffer)
     752             : {
     753             :     HTSV_Result state;
     754             :     TransactionId xmin;
     755             : 
     756           0 :     state = HeapTupleSatisfiesVacuum(tup, OldestXmin, buffer);
     757           0 :     if (state != HEAPTUPLE_LIVE)
     758           0 :         return false;           /* all-visible implies live */
     759             : 
     760             :     /*
     761             :      * Neither lazy_scan_heap nor heap_page_is_all_visible will mark a page
     762             :      * all-visible unless every tuple is hinted committed. However, those hint
     763             :      * bits could be lost after a crash, so we can't be certain that they'll
     764             :      * be set here.  So just check the xmin.
     765             :      */
     766             : 
     767           0 :     xmin = HeapTupleHeaderGetXmin(tup->t_data);
     768           0 :     if (!TransactionIdPrecedes(xmin, OldestXmin))
     769           0 :         return false;           /* xmin not old enough for all to see */
     770             : 
     771           0 :     return true;
     772             : }
     773             : 
     774             : /*
     775             :  * check_relation_relkind - convenience routine to check that relation
     776             :  * is of the relkind supported by the callers
     777             :  */
     778             : static void
     779          68 : check_relation_relkind(Relation rel)
     780             : {
     781         122 :     if (rel->rd_rel->relkind != RELKIND_RELATION &&
     782         104 :         rel->rd_rel->relkind != RELKIND_MATVIEW &&
     783          50 :         rel->rd_rel->relkind != RELKIND_TOASTVALUE)
     784          50 :         ereport(ERROR,
     785             :                 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     786             :                  errmsg("\"%s\" is not a table, materialized view, or TOAST table",
     787             :                         RelationGetRelationName(rel))));
     788          18 : }

Generated by: LCOV version 1.13