LCOV - differential code coverage report
Current view: top level - contrib/pg_surgery - heap_surgery.c (source / functions) Coverage Total Hit LBC UIC UBC GIC GNC CBC EUB ECB DCB
Current: Differential Code Coverage HEAD vs 15 Lines: 92.7 % 124 115 4 2 3 85 2 28 6 82 1
Current Date: 2023-04-08 15:15:32 Functions: 100.0 % 9 9 9 9
Baseline: 15
Baseline Date: 2023-04-08 15:09:40
Legend: Lines: hit not hit

           TLA  Line data    Source code
       1                 : /*-------------------------------------------------------------------------
       2                 :  *
       3                 :  * heap_surgery.c
       4                 :  *    Functions to perform surgery on the damaged heap table.
       5                 :  *
       6                 :  * Copyright (c) 2020-2023, PostgreSQL Global Development Group
       7                 :  *
       8                 :  * IDENTIFICATION
       9                 :  *    contrib/pg_surgery/heap_surgery.c
      10                 :  *
      11                 :  *-------------------------------------------------------------------------
      12                 :  */
      13                 : #include "postgres.h"
      14                 : 
      15                 : #include "access/heapam.h"
      16                 : #include "access/visibilitymap.h"
      17                 : #include "access/xloginsert.h"
      18                 : #include "catalog/pg_am_d.h"
      19                 : #include "catalog/pg_proc_d.h"
      20                 : #include "miscadmin.h"
      21                 : #include "storage/bufmgr.h"
      22                 : #include "utils/acl.h"
      23                 : #include "utils/array.h"
      24                 : #include "utils/rel.h"
      25                 : 
      26 GIC           1 : PG_MODULE_MAGIC;
      27 ECB             : 
      28                 : /* Options to forcefully change the state of a heap tuple. */
      29                 : typedef enum HeapTupleForceOption
      30                 : {
      31                 :     HEAP_FORCE_KILL,
      32                 :     HEAP_FORCE_FREEZE
      33                 : } HeapTupleForceOption;
      34                 : 
      35 GIC           2 : PG_FUNCTION_INFO_V1(heap_force_kill);
      36 CBC           2 : PG_FUNCTION_INFO_V1(heap_force_freeze);
      37 ECB             : 
      38                 : static int32 tidcmp(const void *a, const void *b);
      39                 : static Datum heap_force_common(FunctionCallInfo fcinfo,
      40                 :                                HeapTupleForceOption heap_force_opt);
      41                 : static void sanity_check_tid_array(ArrayType *ta, int *ntids);
      42                 : static BlockNumber find_tids_one_page(ItemPointer tids, int ntids,
      43                 :                                       OffsetNumber *next_start_ptr);
      44                 : 
      45                 : /*-------------------------------------------------------------------------
      46                 :  * heap_force_kill()
      47                 :  *
      48                 :  * Force kill the tuple(s) pointed to by the item pointer(s) stored in the
      49                 :  * given TID array.
      50                 :  *
      51                 :  * Usage: SELECT heap_force_kill(regclass, tid[]);
      52                 :  *-------------------------------------------------------------------------
      53                 :  */
      54                 : Datum
      55 GIC           9 : heap_force_kill(PG_FUNCTION_ARGS)
      56 ECB             : {
      57 GIC           9 :     PG_RETURN_DATUM(heap_force_common(fcinfo, HEAP_FORCE_KILL));
      58 ECB             : }
      59                 : 
      60                 : /*-------------------------------------------------------------------------
      61                 :  * heap_force_freeze()
      62                 :  *
      63                 :  * Force freeze the tuple(s) pointed to by the item pointer(s) stored in the
      64                 :  * given TID array.
      65                 :  *
      66                 :  * Usage: SELECT heap_force_freeze(regclass, tid[]);
      67                 :  *-------------------------------------------------------------------------
      68                 :  */
      69                 : Datum
      70 GIC           7 : heap_force_freeze(PG_FUNCTION_ARGS)
      71 ECB             : {
      72 GIC           7 :     PG_RETURN_DATUM(heap_force_common(fcinfo, HEAP_FORCE_FREEZE));
      73 ECB             : }
      74                 : 
      75                 : /*-------------------------------------------------------------------------
      76                 :  * heap_force_common()
      77                 :  *
      78                 :  * Common code for heap_force_kill and heap_force_freeze
      79                 :  *-------------------------------------------------------------------------
      80                 :  */
      81                 : static Datum
      82 GIC          16 : heap_force_common(FunctionCallInfo fcinfo, HeapTupleForceOption heap_force_opt)
      83 ECB             : {
      84 GIC          16 :     Oid         relid = PG_GETARG_OID(0);
      85 CBC          16 :     ArrayType  *ta = PG_GETARG_ARRAYTYPE_P_COPY(1);
      86 ECB             :     ItemPointer tids;
      87                 :     int         ntids,
      88                 :                 nblocks;
      89                 :     Relation    rel;
      90                 :     OffsetNumber curr_start_ptr,
      91                 :                 next_start_ptr;
      92                 :     bool        include_this_tid[MaxHeapTuplesPerPage];
      93                 : 
      94 GIC          16 :     if (RecoveryInProgress())
      95 LBC           0 :         ereport(ERROR,
      96 EUB             :                 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
      97                 :                  errmsg("recovery is in progress"),
      98                 :                  errhint("heap surgery functions cannot be executed during recovery.")));
      99                 : 
     100                 :     /* Check inputs. */
     101 GIC          16 :     sanity_check_tid_array(ta, &ntids);
     102 ECB             : 
     103 GIC          14 :     rel = relation_open(relid, RowExclusiveLock);
     104 ECB             : 
     105                 :     /*
     106                 :      * Check target relation.
     107                 :      */
     108 GIC          14 :     if (!RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind))
     109 CBC           2 :         ereport(ERROR,
     110 ECB             :                 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     111                 :                  errmsg("cannot operate on relation \"%s\"",
     112                 :                         RelationGetRelationName(rel)),
     113                 :                  errdetail_relkind_not_supported(rel->rd_rel->relkind)));
     114                 : 
     115 GIC          12 :     if (rel->rd_rel->relam != HEAP_TABLE_AM_OID)
     116 LBC           0 :         ereport(ERROR,
     117 EUB             :                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     118                 :                  errmsg("only heap AM is supported")));
     119                 : 
     120                 :     /* Must be owner of the table or superuser. */
     121 GNC          12 :     if (!object_ownercheck(RelationRelationId, RelationGetRelid(rel), GetUserId()))
     122 LBC           0 :         aclcheck_error(ACLCHECK_NOT_OWNER,
     123 UBC           0 :                        get_relkind_objtype(rel->rd_rel->relkind),
     124               0 :                        RelationGetRelationName(rel));
     125 EUB             : 
     126 GIC          12 :     tids = ((ItemPointer) ARR_DATA_PTR(ta));
     127 ECB             : 
     128                 :     /*
     129                 :      * If there is more than one TID in the array, sort them so that we can
     130                 :      * easily fetch all the TIDs belonging to one particular page from the
     131                 :      * array.
     132                 :      */
     133 GIC          12 :     if (ntids > 1)
     134 GNC           2 :         qsort(tids, ntids, sizeof(ItemPointerData), tidcmp);
     135 ECB             : 
     136 GIC          12 :     curr_start_ptr = next_start_ptr = 0;
     137 CBC          12 :     nblocks = RelationGetNumberOfBlocks(rel);
     138 ECB             : 
     139                 :     /*
     140                 :      * Loop, performing the necessary actions for each block.
     141                 :      */
     142 GIC          24 :     while (next_start_ptr != ntids)
     143 ECB             :     {
     144                 :         Buffer      buf;
     145 GIC          12 :         Buffer      vmbuf = InvalidBuffer;
     146 ECB             :         Page        page;
     147                 :         BlockNumber blkno;
     148                 :         OffsetNumber curoff;
     149                 :         OffsetNumber maxoffset;
     150                 :         int         i;
     151 GIC          12 :         bool        did_modify_page = false;
     152 CBC          12 :         bool        did_modify_vm = false;
     153 ECB             : 
     154 GIC          12 :         CHECK_FOR_INTERRUPTS();
     155 ECB             : 
     156                 :         /*
     157                 :          * Find all the TIDs belonging to one particular page starting from
     158                 :          * next_start_ptr and process them one by one.
     159                 :          */
     160 GIC          12 :         blkno = find_tids_one_page(tids, ntids, &next_start_ptr);
     161 ECB             : 
     162                 :         /* Check whether the block number is valid. */
     163 GIC          12 :         if (blkno >= nblocks)
     164 ECB             :         {
     165                 :             /* Update the current_start_ptr before moving to the next page. */
     166 GIC           1 :             curr_start_ptr = next_start_ptr;
     167 ECB             : 
     168 GIC           1 :             ereport(NOTICE,
     169 ECB             :                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     170                 :                      errmsg("skipping block %u for relation \"%s\" because the block number is out of range",
     171                 :                             blkno, RelationGetRelationName(rel))));
     172 GIC           1 :             continue;
     173 ECB             :         }
     174                 : 
     175 GIC          11 :         buf = ReadBuffer(rel, blkno);
     176 CBC          11 :         LockBufferForCleanup(buf);
     177 ECB             : 
     178 GIC          11 :         page = BufferGetPage(buf);
     179 ECB             : 
     180 GIC          11 :         maxoffset = PageGetMaxOffsetNumber(page);
     181 ECB             : 
     182                 :         /*
     183                 :          * Figure out which TIDs we are going to process and which ones we are
     184                 :          * going to skip.
     185                 :          */
     186 GIC          11 :         memset(include_this_tid, 0, sizeof(include_this_tid));
     187 CBC          24 :         for (i = curr_start_ptr; i < next_start_ptr; i++)
     188 ECB             :         {
     189 GIC          13 :             OffsetNumber offno = ItemPointerGetOffsetNumberNoCheck(&tids[i]);
     190 ECB             :             ItemId      itemid;
     191                 : 
     192                 :             /* Check whether the offset number is valid. */
     193 GIC          13 :             if (offno == InvalidOffsetNumber || offno > maxoffset)
     194 ECB             :             {
     195 GIC           2 :                 ereport(NOTICE,
     196 ECB             :                         errmsg("skipping tid (%u, %u) for relation \"%s\" because the item number is out of range",
     197                 :                                blkno, offno, RelationGetRelationName(rel)));
     198 GIC           2 :                 continue;
     199 ECB             :             }
     200                 : 
     201 GIC          11 :             itemid = PageGetItemId(page, offno);
     202 ECB             : 
     203                 :             /* Only accept an item ID that is used. */
     204 GIC          11 :             if (ItemIdIsRedirected(itemid))
     205 ECB             :             {
     206 GIC           1 :                 ereport(NOTICE,
     207 ECB             :                         errmsg("skipping tid (%u, %u) for relation \"%s\" because it redirects to item %u",
     208                 :                                blkno, offno, RelationGetRelationName(rel),
     209                 :                                ItemIdGetRedirect(itemid)));
     210 GIC           1 :                 continue;
     211 ECB             :             }
     212 GIC          10 :             else if (ItemIdIsDead(itemid))
     213 ECB             :             {
     214 GIC           2 :                 ereport(NOTICE,
     215 ECB             :                         (errmsg("skipping tid (%u, %u) for relation \"%s\" because it is marked dead",
     216                 :                                 blkno, offno, RelationGetRelationName(rel))));
     217 GIC           2 :                 continue;
     218 ECB             :             }
     219 GIC           8 :             else if (!ItemIdIsUsed(itemid))
     220 ECB             :             {
     221 GIC           1 :                 ereport(NOTICE,
     222 ECB             :                         (errmsg("skipping tid (%u, %u) for relation \"%s\" because it is marked unused",
     223                 :                                 blkno, offno, RelationGetRelationName(rel))));
     224 GIC           1 :                 continue;
     225 ECB             :             }
     226                 : 
     227                 :             /* Mark it for processing. */
     228 GIC           7 :             Assert(offno < MaxHeapTuplesPerPage);
     229 CBC           7 :             include_this_tid[offno] = true;
     230 ECB             :         }
     231                 : 
     232                 :         /*
     233                 :          * Before entering the critical section, pin the visibility map page
     234                 :          * if it appears to be necessary.
     235                 :          */
     236 GIC          11 :         if (heap_force_opt == HEAP_FORCE_KILL && PageIsAllVisible(page))
     237 CBC           3 :             visibilitymap_pin(rel, blkno, &vmbuf);
     238 ECB             : 
     239                 :         /* No ereport(ERROR) from here until all the changes are logged. */
     240 GIC          11 :         START_CRIT_SECTION();
     241 ECB             : 
     242 GIC          55 :         for (curoff = FirstOffsetNumber; curoff <= maxoffset;
     243 CBC          44 :              curoff = OffsetNumberNext(curoff))
     244 ECB             :         {
     245                 :             ItemId      itemid;
     246                 : 
     247 GIC          44 :             if (!include_this_tid[curoff])
     248 CBC          37 :                 continue;
     249 ECB             : 
     250 GIC           7 :             itemid = PageGetItemId(page, curoff);
     251 CBC           7 :             Assert(ItemIdIsNormal(itemid));
     252 ECB             : 
     253 GIC           7 :             did_modify_page = true;
     254 ECB             : 
     255 GIC           7 :             if (heap_force_opt == HEAP_FORCE_KILL)
     256 ECB             :             {
     257 GIC           3 :                 ItemIdSetDead(itemid);
     258 ECB             : 
     259                 :                 /*
     260                 :                  * If the page is marked all-visible, we must clear
     261                 :                  * PD_ALL_VISIBLE flag on the page header and an all-visible
     262                 :                  * bit on the visibility map corresponding to the page.
     263                 :                  */
     264 GIC           3 :                 if (PageIsAllVisible(page))
     265 ECB             :                 {
     266 GIC           1 :                     PageClearAllVisible(page);
     267 CBC           1 :                     visibilitymap_clear(rel, blkno, vmbuf,
     268 ECB             :                                         VISIBILITYMAP_VALID_BITS);
     269 GIC           1 :                     did_modify_vm = true;
     270 ECB             :                 }
     271                 :             }
     272                 :             else
     273                 :             {
     274                 :                 HeapTupleHeader htup;
     275                 : 
     276 GIC           4 :                 Assert(heap_force_opt == HEAP_FORCE_FREEZE);
     277 ECB             : 
     278 GIC           4 :                 htup = (HeapTupleHeader) PageGetItem(page, itemid);
     279 ECB             : 
     280                 :                 /*
     281                 :                  * Reset all visibility-related fields of the tuple. This
     282                 :                  * logic should mimic heap_execute_freeze_tuple(), but we
     283                 :                  * choose to reset xmin and ctid just to be sure that no
     284                 :                  * potentially-garbled data is left behind.
     285                 :                  */
     286 GIC           4 :                 ItemPointerSet(&htup->t_ctid, blkno, curoff);
     287 CBC           4 :                 HeapTupleHeaderSetXmin(htup, FrozenTransactionId);
     288               4 :                 HeapTupleHeaderSetXmax(htup, InvalidTransactionId);
     289               4 :                 if (htup->t_infomask & HEAP_MOVED)
     290 ECB             :                 {
     291 UIC           0 :                     if (htup->t_infomask & HEAP_MOVED_OFF)
     292 UBC           0 :                         HeapTupleHeaderSetXvac(htup, InvalidTransactionId);
     293 EUB             :                     else
     294 UIC           0 :                         HeapTupleHeaderSetXvac(htup, FrozenTransactionId);
     295 EUB             :                 }
     296                 : 
     297                 :                 /*
     298                 :                  * Clear all the visibility-related bits of this tuple and
     299                 :                  * mark it as frozen. Also, get rid of HOT_UPDATED and
     300                 :                  * KEYS_UPDATES bits.
     301                 :                  */
     302 GIC           4 :                 htup->t_infomask &= ~HEAP_XACT_MASK;
     303 CBC           4 :                 htup->t_infomask |= (HEAP_XMIN_FROZEN | HEAP_XMAX_INVALID);
     304               4 :                 htup->t_infomask2 &= ~HEAP_HOT_UPDATED;
     305               4 :                 htup->t_infomask2 &= ~HEAP_KEYS_UPDATED;
     306 ECB             :             }
     307                 :         }
     308                 : 
     309                 :         /*
     310                 :          * If the page was modified, only then, we mark the buffer dirty or do
     311                 :          * the WAL logging.
     312                 :          */
     313 GIC          11 :         if (did_modify_page)
     314 ECB             :         {
     315                 :             /* Mark buffer dirty before we write WAL. */
     316 GIC           6 :             MarkBufferDirty(buf);
     317 ECB             : 
     318                 :             /* XLOG stuff */
     319 GIC           6 :             if (RelationNeedsWAL(rel))
     320 CBC           2 :                 log_newpage_buffer(buf, true);
     321 ECB             :         }
     322                 : 
     323                 :         /* WAL log the VM page if it was modified. */
     324 GIC          11 :         if (did_modify_vm && RelationNeedsWAL(rel))
     325 LBC           0 :             log_newpage_buffer(vmbuf, false);
     326 EUB             : 
     327 GIC          11 :         END_CRIT_SECTION();
     328 ECB             : 
     329 GIC          11 :         UnlockReleaseBuffer(buf);
     330 ECB             : 
     331 GIC          11 :         if (vmbuf != InvalidBuffer)
     332 CBC           3 :             ReleaseBuffer(vmbuf);
     333 ECB             : 
     334                 :         /* Update the current_start_ptr before moving to the next page. */
     335 GIC          11 :         curr_start_ptr = next_start_ptr;
     336 ECB             :     }
     337                 : 
     338 GIC          12 :     relation_close(rel, RowExclusiveLock);
     339 ECB             : 
     340 GIC          12 :     pfree(ta);
     341 ECB             : 
     342 GIC          12 :     PG_RETURN_VOID();
     343 ECB             : }
     344                 : 
     345                 : /*-------------------------------------------------------------------------
     346                 :  * tidcmp()
     347                 :  *
     348                 :  * Compare two item pointers, return -1, 0, or +1.
     349                 :  *
     350                 :  * See ItemPointerCompare for details.
     351                 :  * ------------------------------------------------------------------------
     352                 :  */
     353                 : static int32
     354 GIC           3 : tidcmp(const void *a, const void *b)
     355 ECB             : {
     356 GIC           3 :     ItemPointer iptr1 = ((const ItemPointer) a);
     357 CBC           3 :     ItemPointer iptr2 = ((const ItemPointer) b);
     358 ECB             : 
     359 GIC           3 :     return ItemPointerCompare(iptr1, iptr2);
     360 ECB             : }
     361                 : 
     362                 : /*-------------------------------------------------------------------------
     363                 :  * sanity_check_tid_array()
     364                 :  *
     365                 :  * Perform sanity checks on the given tid array, and set *ntids to the
     366                 :  * number of items in the array.
     367                 :  * ------------------------------------------------------------------------
     368                 :  */
     369                 : static void
     370 GIC          16 : sanity_check_tid_array(ArrayType *ta, int *ntids)
     371 ECB             : {
     372 GIC          16 :     if (ARR_HASNULL(ta) && array_contains_nulls(ta))
     373 CBC           1 :         ereport(ERROR,
     374 ECB             :                 (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
     375                 :                  errmsg("array must not contain nulls")));
     376                 : 
     377 GIC          15 :     if (ARR_NDIM(ta) > 1)
     378 CBC           1 :         ereport(ERROR,
     379 ECB             :                 (errcode(ERRCODE_DATA_EXCEPTION),
     380                 :                  errmsg("argument must be empty or one-dimensional array")));
     381                 : 
     382 GIC          14 :     *ntids = ArrayGetNItems(ARR_NDIM(ta), ARR_DIMS(ta));
     383 CBC          14 : }
     384 ECB             : 
     385                 : /*-------------------------------------------------------------------------
     386                 :  * find_tids_one_page()
     387                 :  *
     388                 :  * Find all the tids residing in the same page as tids[next_start_ptr], and
     389                 :  * update next_start_ptr so that it points to the first tid in the next page.
     390                 :  *
     391                 :  * NOTE: The input tids[] array must be sorted.
     392                 :  * ------------------------------------------------------------------------
     393                 :  */
     394                 : static BlockNumber
     395 GIC          12 : find_tids_one_page(ItemPointer tids, int ntids, OffsetNumber *next_start_ptr)
     396 ECB             : {
     397                 :     int         i;
     398                 :     BlockNumber prev_blkno,
     399                 :                 blkno;
     400                 : 
     401 GIC          12 :     prev_blkno = blkno = InvalidBlockNumber;
     402 ECB             : 
     403 GIC          26 :     for (i = *next_start_ptr; i < ntids; i++)
     404 ECB             :     {
     405 GIC          15 :         ItemPointerData tid = tids[i];
     406 ECB             : 
     407 GIC          15 :         blkno = ItemPointerGetBlockNumberNoCheck(&tid);
     408 ECB             : 
     409 GIC          15 :         if (i == *next_start_ptr)
     410 CBC          12 :             prev_blkno = blkno;
     411 ECB             : 
     412 GIC          15 :         if (prev_blkno != blkno)
     413 CBC           1 :             break;
     414 ECB             :     }
     415                 : 
     416 GIC          12 :     *next_start_ptr = i;
     417 CBC          12 :     return prev_blkno;
     418 ECB             : }
        

Generated by: LCOV version v1.16-55-g56c0a2a