LCOV - code coverage report
Current view: top level - src/backend/access/gin - ginxlog.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 82.1 % 346 284
Test Date: 2026-03-01 19:14:57 Functions: 88.2 % 17 15
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * ginxlog.c
       4              :  *    WAL replay logic for inverted index.
       5              :  *
       6              :  *
       7              :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       8              :  * Portions Copyright (c) 1994, Regents of the University of California
       9              :  *
      10              :  * IDENTIFICATION
      11              :  *           src/backend/access/gin/ginxlog.c
      12              :  *-------------------------------------------------------------------------
      13              :  */
      14              : #include "postgres.h"
      15              : 
      16              : #include "access/bufmask.h"
      17              : #include "access/gin_private.h"
      18              : #include "access/ginxlog.h"
      19              : #include "access/xlogutils.h"
      20              : #include "utils/memutils.h"
      21              : 
      22              : static MemoryContext opCtx;     /* working memory for operations */
      23              : 
      24              : static void
      25          139 : ginRedoClearIncompleteSplit(XLogReaderState *record, uint8 block_id)
      26              : {
      27          139 :     XLogRecPtr  lsn = record->EndRecPtr;
      28              :     Buffer      buffer;
      29              :     Page        page;
      30              : 
      31          139 :     if (XLogReadBufferForRedo(record, block_id, &buffer) == BLK_NEEDS_REDO)
      32              :     {
      33          139 :         page = BufferGetPage(buffer);
      34          139 :         GinPageGetOpaque(page)->flags &= ~GIN_INCOMPLETE_SPLIT;
      35              : 
      36          139 :         PageSetLSN(page, lsn);
      37          139 :         MarkBufferDirty(buffer);
      38              :     }
      39          139 :     if (BufferIsValid(buffer))
      40          139 :         UnlockReleaseBuffer(buffer);
      41          139 : }
      42              : 
      43              : static void
      44            6 : ginRedoCreatePTree(XLogReaderState *record)
      45              : {
      46            6 :     XLogRecPtr  lsn = record->EndRecPtr;
      47            6 :     ginxlogCreatePostingTree *data = (ginxlogCreatePostingTree *) XLogRecGetData(record);
      48              :     char       *ptr;
      49              :     Buffer      buffer;
      50              :     Page        page;
      51              : 
      52            6 :     buffer = XLogInitBufferForRedo(record, 0);
      53            6 :     page = BufferGetPage(buffer);
      54              : 
      55            6 :     GinInitBuffer(buffer, GIN_DATA | GIN_LEAF | GIN_COMPRESSED);
      56              : 
      57            6 :     ptr = XLogRecGetData(record) + sizeof(ginxlogCreatePostingTree);
      58              : 
      59              :     /* Place page data */
      60            6 :     memcpy(GinDataLeafPageGetPostingList(page), ptr, data->size);
      61              : 
      62            6 :     GinDataPageSetDataSize(page, data->size);
      63              : 
      64            6 :     PageSetLSN(page, lsn);
      65              : 
      66            6 :     MarkBufferDirty(buffer);
      67            6 :     UnlockReleaseBuffer(buffer);
      68            6 : }
      69              : 
      70              : static void
      71        23615 : ginRedoInsertEntry(Buffer buffer, bool isLeaf, BlockNumber rightblkno, void *rdata)
      72              : {
      73        23615 :     Page        page = BufferGetPage(buffer);
      74        23615 :     ginxlogInsertEntry *data = (ginxlogInsertEntry *) rdata;
      75        23615 :     OffsetNumber offset = data->offset;
      76              :     IndexTuple  itup;
      77              : 
      78        23615 :     if (rightblkno != InvalidBlockNumber)
      79              :     {
      80              :         /* update link to right page after split */
      81              :         Assert(!GinPageIsLeaf(page));
      82              :         Assert(offset >= FirstOffsetNumber && offset <= PageGetMaxOffsetNumber(page));
      83          130 :         itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offset));
      84          130 :         GinSetDownlink(itup, rightblkno);
      85              :     }
      86              : 
      87        23615 :     if (data->isDelete)
      88              :     {
      89              :         Assert(GinPageIsLeaf(page));
      90              :         Assert(offset >= FirstOffsetNumber && offset <= PageGetMaxOffsetNumber(page));
      91         3628 :         PageIndexTupleDelete(page, offset);
      92              :     }
      93              : 
      94        23615 :     itup = &data->tuple;
      95              : 
      96        23615 :     if (PageAddItem(page, itup, IndexTupleSize(itup), offset, false, false) == InvalidOffsetNumber)
      97              :     {
      98              :         RelFileLocator locator;
      99              :         ForkNumber  forknum;
     100              :         BlockNumber blknum;
     101              : 
     102            0 :         BufferGetTag(buffer, &locator, &forknum, &blknum);
     103            0 :         elog(ERROR, "failed to add item to index page in %u/%u/%u",
     104              :              locator.spcOid, locator.dbOid, locator.relNumber);
     105              :     }
     106        23615 : }
     107              : 
     108              : /*
     109              :  * Redo recompression of posting list.  Doing all the changes in-place is not
     110              :  * always possible, because it might require more space than we've on the page.
     111              :  * Instead, once modification is required we copy unprocessed tail of the page
     112              :  * into separately allocated chunk of memory for further reading original
     113              :  * versions of segments.  Thanks to that we don't bother about moving page data
     114              :  * in-place.
     115              :  */
     116              : static void
     117         3343 : ginRedoRecompress(Page page, ginxlogRecompressDataLeaf *data)
     118              : {
     119              :     int         actionno;
     120              :     int         segno;
     121              :     GinPostingList *oldseg;
     122              :     char       *segmentend;
     123              :     char       *walbuf;
     124              :     int         totalsize;
     125         3343 :     void       *tailCopy = NULL;
     126              :     char       *writePtr;
     127              :     char       *segptr;
     128              : 
     129              :     /*
     130              :      * If the page is in pre-9.4 format, convert to new format first.
     131              :      */
     132         3343 :     if (!GinPageIsCompressed(page))
     133              :     {
     134            0 :         ItemPointer uncompressed = (ItemPointer) GinDataPageGetData(page);
     135            0 :         int         nuncompressed = GinPageGetOpaque(page)->maxoff;
     136              :         int         npacked;
     137              : 
     138              :         /*
     139              :          * Empty leaf pages are deleted as part of vacuum, but leftmost and
     140              :          * rightmost pages are never deleted.  So, pg_upgrade'd from pre-9.4
     141              :          * instances might contain empty leaf pages, and we need to handle
     142              :          * them correctly.
     143              :          */
     144            0 :         if (nuncompressed > 0)
     145              :         {
     146              :             GinPostingList *plist;
     147              : 
     148            0 :             plist = ginCompressPostingList(uncompressed, nuncompressed,
     149              :                                            BLCKSZ, &npacked);
     150            0 :             totalsize = SizeOfGinPostingList(plist);
     151              : 
     152              :             Assert(npacked == nuncompressed);
     153              : 
     154            0 :             memcpy(GinDataLeafPageGetPostingList(page), plist, totalsize);
     155              :         }
     156              :         else
     157              :         {
     158            0 :             totalsize = 0;
     159              :         }
     160              : 
     161            0 :         GinDataPageSetDataSize(page, totalsize);
     162            0 :         GinPageSetCompressed(page);
     163            0 :         GinPageGetOpaque(page)->maxoff = InvalidOffsetNumber;
     164              :     }
     165              : 
     166         3343 :     oldseg = GinDataLeafPageGetPostingList(page);
     167         3343 :     writePtr = (char *) oldseg;
     168         3343 :     segmentend = (char *) oldseg + GinDataLeafPageGetPostingListSize(page);
     169         3343 :     segno = 0;
     170              : 
     171         3343 :     walbuf = ((char *) data) + sizeof(ginxlogRecompressDataLeaf);
     172         6701 :     for (actionno = 0; actionno < data->nactions; actionno++)
     173              :     {
     174         3358 :         uint8       a_segno = *((uint8 *) (walbuf++));
     175         3358 :         uint8       a_action = *((uint8 *) (walbuf++));
     176         3358 :         GinPostingList *newseg = NULL;
     177         3358 :         int         newsegsize = 0;
     178         3358 :         ItemPointerData *items = NULL;
     179         3358 :         uint16      nitems = 0;
     180              :         ItemPointerData *olditems;
     181              :         int         nolditems;
     182              :         ItemPointerData *newitems;
     183              :         int         nnewitems;
     184              :         int         segsize;
     185              : 
     186              :         /* Extract all the information we need from the WAL record */
     187         3358 :         if (a_action == GIN_SEGMENT_INSERT ||
     188              :             a_action == GIN_SEGMENT_REPLACE)
     189              :         {
     190           28 :             newseg = (GinPostingList *) walbuf;
     191           28 :             newsegsize = SizeOfGinPostingList(newseg);
     192           28 :             walbuf += SHORTALIGN(newsegsize);
     193              :         }
     194              : 
     195         3358 :         if (a_action == GIN_SEGMENT_ADDITEMS)
     196              :         {
     197         3324 :             memcpy(&nitems, walbuf, sizeof(uint16));
     198         3324 :             walbuf += sizeof(uint16);
     199         3324 :             items = (ItemPointerData *) walbuf;
     200         3324 :             walbuf += nitems * sizeof(ItemPointerData);
     201              :         }
     202              : 
     203              :         /* Skip to the segment that this action concerns */
     204              :         Assert(segno <= a_segno);
     205        59717 :         while (segno < a_segno)
     206              :         {
     207              :             /*
     208              :              * Once modification is started and page tail is copied, we've to
     209              :              * copy unmodified segments.
     210              :              */
     211        56359 :             segsize = SizeOfGinPostingList(oldseg);
     212        56359 :             if (tailCopy)
     213              :             {
     214              :                 Assert(writePtr + segsize < PageGetSpecialPointer(page));
     215            0 :                 memcpy(writePtr, oldseg, segsize);
     216              :             }
     217        56359 :             writePtr += segsize;
     218        56359 :             oldseg = GinNextPostingListSegment(oldseg);
     219        56359 :             segno++;
     220              :         }
     221              : 
     222              :         /*
     223              :          * ADDITEMS action is handled like REPLACE, but the new segment to
     224              :          * replace the old one is reconstructed using the old segment from
     225              :          * disk and the new items from the WAL record.
     226              :          */
     227         3358 :         if (a_action == GIN_SEGMENT_ADDITEMS)
     228              :         {
     229              :             int         npacked;
     230              : 
     231         3324 :             olditems = ginPostingListDecode(oldseg, &nolditems);
     232              : 
     233         3324 :             newitems = ginMergeItemPointers(items, nitems,
     234              :                                             olditems, nolditems,
     235              :                                             &nnewitems);
     236              :             Assert(nnewitems == nolditems + nitems);
     237              : 
     238         3324 :             newseg = ginCompressPostingList(newitems, nnewitems,
     239              :                                             BLCKSZ, &npacked);
     240              :             Assert(npacked == nnewitems);
     241              : 
     242         3324 :             newsegsize = SizeOfGinPostingList(newseg);
     243         3324 :             a_action = GIN_SEGMENT_REPLACE;
     244              :         }
     245              : 
     246         3358 :         segptr = (char *) oldseg;
     247         3358 :         if (segptr != segmentend)
     248         3336 :             segsize = SizeOfGinPostingList(oldseg);
     249              :         else
     250              :         {
     251              :             /*
     252              :              * Positioned after the last existing segment. Only INSERTs
     253              :              * expected here.
     254              :              */
     255              :             Assert(a_action == GIN_SEGMENT_INSERT);
     256           22 :             segsize = 0;
     257              :         }
     258              : 
     259              :         /*
     260              :          * We're about to start modification of the page.  So, copy tail of
     261              :          * the page if it's not done already.
     262              :          */
     263         3358 :         if (!tailCopy && segptr != segmentend)
     264              :         {
     265         3330 :             int         tailSize = segmentend - segptr;
     266              : 
     267         3330 :             tailCopy = palloc(tailSize);
     268         3330 :             memcpy(tailCopy, segptr, tailSize);
     269         3330 :             segptr = tailCopy;
     270         3330 :             oldseg = (GinPostingList *) segptr;
     271         3330 :             segmentend = segptr + tailSize;
     272              :         }
     273              : 
     274         3358 :         switch (a_action)
     275              :         {
     276            6 :             case GIN_SEGMENT_DELETE:
     277            6 :                 segptr += segsize;
     278            6 :                 segno++;
     279            6 :                 break;
     280              : 
     281           22 :             case GIN_SEGMENT_INSERT:
     282              :                 /* copy the new segment in place */
     283              :                 Assert(writePtr + newsegsize <= PageGetSpecialPointer(page));
     284           22 :                 memcpy(writePtr, newseg, newsegsize);
     285           22 :                 writePtr += newsegsize;
     286           22 :                 break;
     287              : 
     288         3330 :             case GIN_SEGMENT_REPLACE:
     289              :                 /* copy the new version of segment in place */
     290              :                 Assert(writePtr + newsegsize <= PageGetSpecialPointer(page));
     291         3330 :                 memcpy(writePtr, newseg, newsegsize);
     292         3330 :                 writePtr += newsegsize;
     293         3330 :                 segptr += segsize;
     294         3330 :                 segno++;
     295         3330 :                 break;
     296              : 
     297            0 :             default:
     298            0 :                 elog(ERROR, "unexpected GIN leaf action: %u", a_action);
     299              :         }
     300         3358 :         oldseg = (GinPostingList *) segptr;
     301              :     }
     302              : 
     303              :     /* Copy the rest of unmodified segments if any. */
     304         3343 :     segptr = (char *) oldseg;
     305         3343 :     if (segptr != segmentend && tailCopy)
     306              :     {
     307            3 :         int         restSize = segmentend - segptr;
     308              : 
     309              :         Assert(writePtr + restSize <= PageGetSpecialPointer(page));
     310            3 :         memcpy(writePtr, segptr, restSize);
     311            3 :         writePtr += restSize;
     312              :     }
     313              : 
     314         3343 :     totalsize = writePtr - (char *) GinDataLeafPageGetPostingList(page);
     315         3343 :     GinDataPageSetDataSize(page, totalsize);
     316         3343 : }
     317              : 
     318              : static void
     319         3342 : ginRedoInsertData(Buffer buffer, bool isLeaf, BlockNumber rightblkno, void *rdata)
     320              : {
     321         3342 :     Page        page = BufferGetPage(buffer);
     322              : 
     323         3342 :     if (isLeaf)
     324              :     {
     325         3340 :         ginxlogRecompressDataLeaf *data = (ginxlogRecompressDataLeaf *) rdata;
     326              : 
     327              :         Assert(GinPageIsLeaf(page));
     328              : 
     329         3340 :         ginRedoRecompress(page, data);
     330              :     }
     331              :     else
     332              :     {
     333            2 :         ginxlogInsertDataInternal *data = (ginxlogInsertDataInternal *) rdata;
     334              :         PostingItem *oldpitem;
     335              : 
     336              :         Assert(!GinPageIsLeaf(page));
     337              : 
     338              :         /* update link to right page after split */
     339            2 :         oldpitem = GinDataPageGetPostingItem(page, data->offset);
     340            2 :         PostingItemSetBlockNumber(oldpitem, rightblkno);
     341              : 
     342            2 :         GinDataPageAddPostingItem(page, &data->newitem, data->offset);
     343              :     }
     344         3342 : }
     345              : 
     346              : static void
     347        27012 : ginRedoInsert(XLogReaderState *record)
     348              : {
     349        27012 :     XLogRecPtr  lsn = record->EndRecPtr;
     350        27012 :     ginxlogInsert *data = (ginxlogInsert *) XLogRecGetData(record);
     351              :     Buffer      buffer;
     352              : #ifdef NOT_USED
     353              :     BlockNumber leftChildBlkno = InvalidBlockNumber;
     354              : #endif
     355        27012 :     BlockNumber rightChildBlkno = InvalidBlockNumber;
     356        27012 :     bool        isLeaf = (data->flags & GIN_INSERT_ISLEAF) != 0;
     357              : 
     358              :     /*
     359              :      * First clear incomplete-split flag on child page if this finishes a
     360              :      * split.
     361              :      */
     362        27012 :     if (!isLeaf)
     363              :     {
     364          139 :         char       *payload = XLogRecGetData(record) + sizeof(ginxlogInsert);
     365              : 
     366              : #ifdef NOT_USED
     367              :         leftChildBlkno = BlockIdGetBlockNumber((BlockId) payload);
     368              : #endif
     369          139 :         payload += sizeof(BlockIdData);
     370          139 :         rightChildBlkno = BlockIdGetBlockNumber((BlockId) payload);
     371              : 
     372          139 :         ginRedoClearIncompleteSplit(record, 1);
     373              :     }
     374              : 
     375        27012 :     if (XLogReadBufferForRedo(record, 0, &buffer) == BLK_NEEDS_REDO)
     376              :     {
     377        26957 :         Page        page = BufferGetPage(buffer);
     378              :         Size        len;
     379        26957 :         char       *payload = XLogRecGetBlockData(record, 0, &len);
     380              : 
     381              :         /* How to insert the payload is tree-type specific */
     382        26957 :         if (data->flags & GIN_INSERT_ISDATA)
     383              :         {
     384              :             Assert(GinPageIsData(page));
     385         3342 :             ginRedoInsertData(buffer, isLeaf, rightChildBlkno, payload);
     386              :         }
     387              :         else
     388              :         {
     389              :             Assert(!GinPageIsData(page));
     390        23615 :             ginRedoInsertEntry(buffer, isLeaf, rightChildBlkno, payload);
     391              :         }
     392              : 
     393        26957 :         PageSetLSN(page, lsn);
     394        26957 :         MarkBufferDirty(buffer);
     395              :     }
     396        27012 :     if (BufferIsValid(buffer))
     397        27012 :         UnlockReleaseBuffer(buffer);
     398        27012 : }
     399              : 
     400              : static void
     401          142 : ginRedoSplit(XLogReaderState *record)
     402              : {
     403          142 :     ginxlogSplit *data = (ginxlogSplit *) XLogRecGetData(record);
     404              :     Buffer      lbuffer,
     405              :                 rbuffer,
     406              :                 rootbuf;
     407          142 :     bool        isLeaf = (data->flags & GIN_INSERT_ISLEAF) != 0;
     408          142 :     bool        isRoot = (data->flags & GIN_SPLIT_ROOT) != 0;
     409              : 
     410              :     /*
     411              :      * First clear incomplete-split flag on child page if this finishes a
     412              :      * split
     413              :      */
     414          142 :     if (!isLeaf)
     415            0 :         ginRedoClearIncompleteSplit(record, 3);
     416              : 
     417          142 :     if (XLogReadBufferForRedo(record, 0, &lbuffer) != BLK_RESTORED)
     418            0 :         elog(ERROR, "GIN split record did not contain a full-page image of left page");
     419              : 
     420          142 :     if (XLogReadBufferForRedo(record, 1, &rbuffer) != BLK_RESTORED)
     421            0 :         elog(ERROR, "GIN split record did not contain a full-page image of right page");
     422              : 
     423          142 :     if (isRoot)
     424              :     {
     425            3 :         if (XLogReadBufferForRedo(record, 2, &rootbuf) != BLK_RESTORED)
     426            0 :             elog(ERROR, "GIN split record did not contain a full-page image of root page");
     427            3 :         UnlockReleaseBuffer(rootbuf);
     428              :     }
     429              : 
     430          142 :     UnlockReleaseBuffer(rbuffer);
     431          142 :     UnlockReleaseBuffer(lbuffer);
     432          142 : }
     433              : 
     434              : /*
     435              :  * VACUUM_PAGE record contains simply a full image of the page, similar to
     436              :  * an XLOG_FPI record.
     437              :  */
     438              : static void
     439            0 : ginRedoVacuumPage(XLogReaderState *record)
     440              : {
     441              :     Buffer      buffer;
     442              : 
     443            0 :     if (XLogReadBufferForRedo(record, 0, &buffer) != BLK_RESTORED)
     444              :     {
     445            0 :         elog(ERROR, "replay of gin entry tree page vacuum did not restore the page");
     446              :     }
     447            0 :     UnlockReleaseBuffer(buffer);
     448            0 : }
     449              : 
     450              : static void
     451            3 : ginRedoVacuumDataLeafPage(XLogReaderState *record)
     452              : {
     453            3 :     XLogRecPtr  lsn = record->EndRecPtr;
     454              :     Buffer      buffer;
     455              : 
     456            3 :     if (XLogReadBufferForRedo(record, 0, &buffer) == BLK_NEEDS_REDO)
     457              :     {
     458            3 :         Page        page = BufferGetPage(buffer);
     459              :         Size        len;
     460              :         ginxlogVacuumDataLeafPage *xlrec;
     461              : 
     462            3 :         xlrec = (ginxlogVacuumDataLeafPage *) XLogRecGetBlockData(record, 0, &len);
     463              : 
     464              :         Assert(GinPageIsLeaf(page));
     465              :         Assert(GinPageIsData(page));
     466              : 
     467            3 :         ginRedoRecompress(page, &xlrec->data);
     468            3 :         PageSetLSN(page, lsn);
     469            3 :         MarkBufferDirty(buffer);
     470              :     }
     471            3 :     if (BufferIsValid(buffer))
     472            3 :         UnlockReleaseBuffer(buffer);
     473            3 : }
     474              : 
     475              : static void
     476            0 : ginRedoDeletePage(XLogReaderState *record)
     477              : {
     478            0 :     XLogRecPtr  lsn = record->EndRecPtr;
     479            0 :     ginxlogDeletePage *data = (ginxlogDeletePage *) XLogRecGetData(record);
     480              :     Buffer      dbuffer;
     481              :     Buffer      pbuffer;
     482              :     Buffer      lbuffer;
     483              :     Page        page;
     484              : 
     485              :     /*
     486              :      * Lock left page first in order to prevent possible deadlock with
     487              :      * ginStepRight().
     488              :      */
     489            0 :     if (XLogReadBufferForRedo(record, 2, &lbuffer) == BLK_NEEDS_REDO)
     490              :     {
     491            0 :         page = BufferGetPage(lbuffer);
     492              :         Assert(GinPageIsData(page));
     493            0 :         GinPageGetOpaque(page)->rightlink = data->rightLink;
     494            0 :         PageSetLSN(page, lsn);
     495            0 :         MarkBufferDirty(lbuffer);
     496              :     }
     497              : 
     498            0 :     if (XLogReadBufferForRedo(record, 0, &dbuffer) == BLK_NEEDS_REDO)
     499              :     {
     500            0 :         page = BufferGetPage(dbuffer);
     501              :         Assert(GinPageIsData(page));
     502            0 :         GinPageSetDeleted(page);
     503            0 :         GinPageSetDeleteXid(page, data->deleteXid);
     504            0 :         PageSetLSN(page, lsn);
     505            0 :         MarkBufferDirty(dbuffer);
     506              :     }
     507              : 
     508            0 :     if (XLogReadBufferForRedo(record, 1, &pbuffer) == BLK_NEEDS_REDO)
     509              :     {
     510            0 :         page = BufferGetPage(pbuffer);
     511              :         Assert(GinPageIsData(page));
     512              :         Assert(!GinPageIsLeaf(page));
     513            0 :         GinPageDeletePostingItem(page, data->parentOffset);
     514            0 :         PageSetLSN(page, lsn);
     515            0 :         MarkBufferDirty(pbuffer);
     516              :     }
     517              : 
     518            0 :     if (BufferIsValid(lbuffer))
     519            0 :         UnlockReleaseBuffer(lbuffer);
     520            0 :     if (BufferIsValid(pbuffer))
     521            0 :         UnlockReleaseBuffer(pbuffer);
     522            0 :     if (BufferIsValid(dbuffer))
     523            0 :         UnlockReleaseBuffer(dbuffer);
     524            0 : }
     525              : 
     526              : static void
     527        23983 : ginRedoUpdateMetapage(XLogReaderState *record)
     528              : {
     529        23983 :     XLogRecPtr  lsn = record->EndRecPtr;
     530        23983 :     ginxlogUpdateMeta *data = (ginxlogUpdateMeta *) XLogRecGetData(record);
     531              :     Buffer      metabuffer;
     532              :     Page        metapage;
     533              :     Buffer      buffer;
     534              : 
     535              :     /*
     536              :      * Restore the metapage. This is essentially the same as a full-page
     537              :      * image, so restore the metapage unconditionally without looking at the
     538              :      * LSN, to avoid torn page hazards.
     539              :      */
     540        23983 :     metabuffer = XLogInitBufferForRedo(record, 0);
     541              :     Assert(BufferGetBlockNumber(metabuffer) == GIN_METAPAGE_BLKNO);
     542        23983 :     metapage = BufferGetPage(metabuffer);
     543              : 
     544        23983 :     GinInitMetabuffer(metabuffer);
     545        23983 :     memcpy(GinPageGetMeta(metapage), &data->metadata, sizeof(GinMetaPageData));
     546        23983 :     PageSetLSN(metapage, lsn);
     547        23983 :     MarkBufferDirty(metabuffer);
     548              : 
     549        23983 :     if (data->ntuples > 0)
     550              :     {
     551              :         /*
     552              :          * insert into tail page
     553              :          */
     554        23795 :         if (XLogReadBufferForRedo(record, 1, &buffer) == BLK_NEEDS_REDO)
     555              :         {
     556        23777 :             Page        page = BufferGetPage(buffer);
     557              :             OffsetNumber off;
     558              :             int         i;
     559              :             Size        tupsize;
     560              :             char       *payload;
     561              :             IndexTuple  tuples;
     562              :             Size        totaltupsize;
     563              : 
     564        23777 :             payload = XLogRecGetBlockData(record, 1, &totaltupsize);
     565        23777 :             tuples = (IndexTuple) payload;
     566              : 
     567        23777 :             if (PageIsEmpty(page))
     568            0 :                 off = FirstOffsetNumber;
     569              :             else
     570        23777 :                 off = OffsetNumberNext(PageGetMaxOffsetNumber(page));
     571              : 
     572        95101 :             for (i = 0; i < data->ntuples; i++)
     573              :             {
     574        71324 :                 tupsize = IndexTupleSize(tuples);
     575              : 
     576        71324 :                 if (PageAddItem(page, tuples, tupsize, off, false, false) == InvalidOffsetNumber)
     577            0 :                     elog(ERROR, "failed to add item to index page");
     578              : 
     579        71324 :                 tuples = (IndexTuple) (((char *) tuples) + tupsize);
     580              : 
     581        71324 :                 off++;
     582              :             }
     583              :             Assert(payload + totaltupsize == (char *) tuples);
     584              : 
     585              :             /*
     586              :              * Increase counter of heap tuples
     587              :              */
     588        23777 :             GinPageGetOpaque(page)->maxoff++;
     589              : 
     590        23777 :             PageSetLSN(page, lsn);
     591        23777 :             MarkBufferDirty(buffer);
     592              :         }
     593        23795 :         if (BufferIsValid(buffer))
     594        23795 :             UnlockReleaseBuffer(buffer);
     595              :     }
     596          188 :     else if (data->prevTail != InvalidBlockNumber)
     597              :     {
     598              :         /*
     599              :          * New tail
     600              :          */
     601          175 :         if (XLogReadBufferForRedo(record, 1, &buffer) == BLK_NEEDS_REDO)
     602              :         {
     603          175 :             Page        page = BufferGetPage(buffer);
     604              : 
     605          175 :             GinPageGetOpaque(page)->rightlink = data->newRightlink;
     606              : 
     607          175 :             PageSetLSN(page, lsn);
     608          175 :             MarkBufferDirty(buffer);
     609              :         }
     610          175 :         if (BufferIsValid(buffer))
     611          175 :             UnlockReleaseBuffer(buffer);
     612              :     }
     613              : 
     614        23983 :     UnlockReleaseBuffer(metabuffer);
     615        23983 : }
     616              : 
     617              : static void
     618          180 : ginRedoInsertListPage(XLogReaderState *record)
     619              : {
     620          180 :     XLogRecPtr  lsn = record->EndRecPtr;
     621          180 :     ginxlogInsertListPage *data = (ginxlogInsertListPage *) XLogRecGetData(record);
     622              :     Buffer      buffer;
     623              :     Page        page;
     624              :     OffsetNumber l,
     625          180 :                 off = FirstOffsetNumber;
     626              :     int         i,
     627              :                 tupsize;
     628              :     char       *payload;
     629              :     IndexTuple  tuples;
     630              :     Size        totaltupsize;
     631              : 
     632              :     /* We always re-initialize the page. */
     633          180 :     buffer = XLogInitBufferForRedo(record, 0);
     634          180 :     page = BufferGetPage(buffer);
     635              : 
     636          180 :     GinInitBuffer(buffer, GIN_LIST);
     637          180 :     GinPageGetOpaque(page)->rightlink = data->rightlink;
     638          180 :     if (data->rightlink == InvalidBlockNumber)
     639              :     {
     640              :         /* tail of sublist */
     641          180 :         GinPageSetFullRow(page);
     642          180 :         GinPageGetOpaque(page)->maxoff = 1;
     643              :     }
     644              :     else
     645              :     {
     646            0 :         GinPageGetOpaque(page)->maxoff = 0;
     647              :     }
     648              : 
     649          180 :     payload = XLogRecGetBlockData(record, 0, &totaltupsize);
     650              : 
     651          180 :     tuples = (IndexTuple) payload;
     652          717 :     for (i = 0; i < data->ntuples; i++)
     653              :     {
     654          537 :         tupsize = IndexTupleSize(tuples);
     655              : 
     656          537 :         l = PageAddItem(page, tuples, tupsize, off, false, false);
     657              : 
     658          537 :         if (l == InvalidOffsetNumber)
     659            0 :             elog(ERROR, "failed to add item to index page");
     660              : 
     661          537 :         tuples = (IndexTuple) (((char *) tuples) + tupsize);
     662          537 :         off++;
     663              :     }
     664              :     Assert((char *) tuples == payload + totaltupsize);
     665              : 
     666          180 :     PageSetLSN(page, lsn);
     667          180 :     MarkBufferDirty(buffer);
     668              : 
     669          180 :     UnlockReleaseBuffer(buffer);
     670          180 : }
     671              : 
     672              : static void
     673           14 : ginRedoDeleteListPages(XLogReaderState *record)
     674              : {
     675           14 :     XLogRecPtr  lsn = record->EndRecPtr;
     676           14 :     ginxlogDeleteListPages *data = (ginxlogDeleteListPages *) XLogRecGetData(record);
     677              :     Buffer      metabuffer;
     678              :     Page        metapage;
     679              :     int         i;
     680              : 
     681           14 :     metabuffer = XLogInitBufferForRedo(record, 0);
     682              :     Assert(BufferGetBlockNumber(metabuffer) == GIN_METAPAGE_BLKNO);
     683           14 :     metapage = BufferGetPage(metabuffer);
     684              : 
     685           14 :     GinInitMetabuffer(metabuffer);
     686              : 
     687           14 :     memcpy(GinPageGetMeta(metapage), &data->metadata, sizeof(GinMetaPageData));
     688           14 :     PageSetLSN(metapage, lsn);
     689           14 :     MarkBufferDirty(metabuffer);
     690              : 
     691              :     /*
     692              :      * In normal operation, shiftList() takes exclusive lock on all the
     693              :      * pages-to-be-deleted simultaneously.  During replay, however, it should
     694              :      * be all right to lock them one at a time.  This is dependent on the fact
     695              :      * that we are deleting pages from the head of the list, and that readers
     696              :      * share-lock the next page before releasing the one they are on. So we
     697              :      * cannot get past a reader that is on, or due to visit, any page we are
     698              :      * going to delete.  New incoming readers will block behind our metapage
     699              :      * lock and then see a fully updated page list.
     700              :      *
     701              :      * No full-page images are taken of the deleted pages. Instead, they are
     702              :      * re-initialized as empty, deleted pages. Their right-links don't need to
     703              :      * be preserved, because no new readers can see the pages, as explained
     704              :      * above.
     705              :      */
     706          194 :     for (i = 0; i < data->ndeleted; i++)
     707              :     {
     708              :         Buffer      buffer;
     709              :         Page        page;
     710              : 
     711          180 :         buffer = XLogInitBufferForRedo(record, i + 1);
     712          180 :         page = BufferGetPage(buffer);
     713          180 :         GinInitBuffer(buffer, GIN_DELETED);
     714              : 
     715          180 :         PageSetLSN(page, lsn);
     716          180 :         MarkBufferDirty(buffer);
     717              : 
     718          180 :         UnlockReleaseBuffer(buffer);
     719              :     }
     720           14 :     UnlockReleaseBuffer(metabuffer);
     721           14 : }
     722              : 
     723              : void
     724        51340 : gin_redo(XLogReaderState *record)
     725              : {
     726        51340 :     uint8       info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
     727              :     MemoryContext oldCtx;
     728              : 
     729              :     /*
     730              :      * GIN indexes do not require any conflict processing. NB: If we ever
     731              :      * implement a similar optimization as we have in b-tree, and remove
     732              :      * killed tuples outside VACUUM, we'll need to handle that here.
     733              :      */
     734              : 
     735        51340 :     oldCtx = MemoryContextSwitchTo(opCtx);
     736        51340 :     switch (info)
     737              :     {
     738            6 :         case XLOG_GIN_CREATE_PTREE:
     739            6 :             ginRedoCreatePTree(record);
     740            6 :             break;
     741        27012 :         case XLOG_GIN_INSERT:
     742        27012 :             ginRedoInsert(record);
     743        27012 :             break;
     744          142 :         case XLOG_GIN_SPLIT:
     745          142 :             ginRedoSplit(record);
     746          142 :             break;
     747            0 :         case XLOG_GIN_VACUUM_PAGE:
     748            0 :             ginRedoVacuumPage(record);
     749            0 :             break;
     750            3 :         case XLOG_GIN_VACUUM_DATA_LEAF_PAGE:
     751            3 :             ginRedoVacuumDataLeafPage(record);
     752            3 :             break;
     753            0 :         case XLOG_GIN_DELETE_PAGE:
     754            0 :             ginRedoDeletePage(record);
     755            0 :             break;
     756        23983 :         case XLOG_GIN_UPDATE_META_PAGE:
     757        23983 :             ginRedoUpdateMetapage(record);
     758        23983 :             break;
     759          180 :         case XLOG_GIN_INSERT_LISTPAGE:
     760          180 :             ginRedoInsertListPage(record);
     761          180 :             break;
     762           14 :         case XLOG_GIN_DELETE_LISTPAGE:
     763           14 :             ginRedoDeleteListPages(record);
     764           14 :             break;
     765            0 :         default:
     766            0 :             elog(PANIC, "gin_redo: unknown op code %u", info);
     767              :     }
     768        51340 :     MemoryContextSwitchTo(oldCtx);
     769        51340 :     MemoryContextReset(opCtx);
     770        51340 : }
     771              : 
     772              : void
     773          215 : gin_xlog_startup(void)
     774              : {
     775          215 :     opCtx = AllocSetContextCreate(CurrentMemoryContext,
     776              :                                   "GIN recovery temporary context",
     777              :                                   ALLOCSET_DEFAULT_SIZES);
     778          215 : }
     779              : 
     780              : void
     781          154 : gin_xlog_cleanup(void)
     782              : {
     783          154 :     MemoryContextDelete(opCtx);
     784          154 :     opCtx = NULL;
     785          154 : }
     786              : 
     787              : /*
     788              :  * Mask a GIN page before running consistency checks on it.
     789              :  */
     790              : void
     791       150828 : gin_mask(char *pagedata, BlockNumber blkno)
     792              : {
     793       150828 :     Page        page = (Page) pagedata;
     794       150828 :     PageHeader  pagehdr = (PageHeader) page;
     795              :     GinPageOpaque opaque;
     796              : 
     797       150828 :     mask_page_lsn_and_checksum(page);
     798       150828 :     opaque = GinPageGetOpaque(page);
     799              : 
     800       150828 :     mask_page_hint_bits(page);
     801              : 
     802              :     /*
     803              :      * For a GIN_DELETED page, the page is initialized to empty.  Hence, mask
     804              :      * the whole page content.  For other pages, mask the hole if pd_lower
     805              :      * appears to have been set correctly.
     806              :      */
     807       150828 :     if (opaque->flags & GIN_DELETED)
     808          360 :         mask_page_content(page);
     809       150468 :     else if (pagehdr->pd_lower > SizeOfPageHeaderData)
     810       150468 :         mask_unused_space(page);
     811       150828 : }
        

Generated by: LCOV version 2.0-1