LCOV - code coverage report
Current view: top level - contrib/amcheck - verify_gin.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 93 241 38.6 %
Date: 2025-04-01 15:15:16 Functions: 6 8 75.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * verify_gin.c
       4             :  *      Verifies the integrity of GIN indexes based on invariants.
       5             :  *
       6             :  *
       7             :  * GIN index verification checks a number of invariants:
       8             :  *
       9             :  * - consistency: Paths in GIN graph have to contain consistent keys: tuples
      10             :  *   on parent pages consistently include tuples from children pages.
      11             :  *
      12             :  * - graph invariants: Each internal page must have at least one downlink, and
      13             :  *   can reference either only leaf pages or only internal pages.
      14             :  *
      15             :  *
      16             :  * Copyright (c) 2016-2025, PostgreSQL Global Development Group
      17             :  *
      18             :  * IDENTIFICATION
      19             :  *    contrib/amcheck/verify_gin.c
      20             :  *
      21             :  *-------------------------------------------------------------------------
      22             :  */
      23             : #include "postgres.h"
      24             : 
      25             : #include "access/gin_private.h"
      26             : #include "access/nbtree.h"
      27             : #include "catalog/pg_am.h"
      28             : #include "utils/memutils.h"
      29             : #include "utils/rel.h"
      30             : #include "verify_common.h"
      31             : #include "string.h"
      32             : 
      33             : /*
      34             :  * GinScanItem represents one item of depth-first scan of the index.
      35             :  */
      36             : typedef struct GinScanItem
      37             : {
      38             :     int         depth;
      39             :     IndexTuple  parenttup;
      40             :     BlockNumber parentblk;
      41             :     XLogRecPtr  parentlsn;
      42             :     BlockNumber blkno;
      43             :     struct GinScanItem *next;
      44             : } GinScanItem;
      45             : 
      46             : /*
      47             :  * GinPostingTreeScanItem represents one item of a depth-first posting tree scan.
      48             :  */
      49             : typedef struct GinPostingTreeScanItem
      50             : {
      51             :     int         depth;
      52             :     ItemPointerData parentkey;
      53             :     BlockNumber parentblk;
      54             :     BlockNumber blkno;
      55             :     struct GinPostingTreeScanItem *next;
      56             : } GinPostingTreeScanItem;
      57             : 
      58             : 
      59          42 : PG_FUNCTION_INFO_V1(gin_index_check);
      60             : 
      61             : static void gin_check_parent_keys_consistency(Relation rel,
      62             :                                               Relation heaprel,
      63             :                                               void *callback_state, bool readonly);
      64             : static void check_index_page(Relation rel, Buffer buffer, BlockNumber blockNo);
      65             : static IndexTuple gin_refind_parent(Relation rel,
      66             :                                     BlockNumber parentblkno,
      67             :                                     BlockNumber childblkno,
      68             :                                     BufferAccessStrategy strategy);
      69             : static ItemId PageGetItemIdCareful(Relation rel, BlockNumber block, Page page,
      70             :                                    OffsetNumber offset);
      71             : 
      72             : /*
      73             :  * gin_index_check(index regclass)
      74             :  *
      75             :  * Verify integrity of GIN index.
      76             :  *
      77             :  * Acquires AccessShareLock on heap & index relations.
      78             :  */
      79             : Datum
      80          92 : gin_index_check(PG_FUNCTION_ARGS)
      81             : {
      82          92 :     Oid         indrelid = PG_GETARG_OID(0);
      83             : 
      84          92 :     amcheck_lock_relation_and_check(indrelid,
      85             :                                     GIN_AM_OID,
      86             :                                     gin_check_parent_keys_consistency,
      87             :                                     AccessShareLock,
      88             :                                     NULL);
      89             : 
      90          92 :     PG_RETURN_VOID();
      91             : }
      92             : 
      93             : /*
      94             :  * Read item pointers from leaf entry tuple.
      95             :  *
      96             :  * Returns a palloc'd array of ItemPointers. The number of items is returned
      97             :  * in *nitems.
      98             :  */
      99             : static ItemPointer
     100        3296 : ginReadTupleWithoutState(IndexTuple itup, int *nitems)
     101             : {
     102        3296 :     Pointer     ptr = GinGetPosting(itup);
     103        3296 :     int         nipd = GinGetNPosting(itup);
     104             :     ItemPointer ipd;
     105             :     int         ndecoded;
     106             : 
     107        3296 :     if (GinItupIsCompressed(itup))
     108             :     {
     109        3296 :         if (nipd > 0)
     110             :         {
     111        3296 :             ipd = ginPostingListDecode((GinPostingList *) ptr, &ndecoded);
     112        3296 :             if (nipd != ndecoded)
     113           0 :                 elog(ERROR, "number of items mismatch in GIN entry tuple, %d in tuple header, %d decoded",
     114             :                      nipd, ndecoded);
     115             :         }
     116             :         else
     117           0 :             ipd = palloc(0);
     118             :     }
     119             :     else
     120             :     {
     121           0 :         ipd = (ItemPointer) palloc(sizeof(ItemPointerData) * nipd);
     122           0 :         memcpy(ipd, ptr, sizeof(ItemPointerData) * nipd);
     123             :     }
     124        3296 :     *nitems = nipd;
     125        3296 :     return ipd;
     126             : }
     127             : 
     128             : /*
     129             :  * Scans through a posting tree (given by the root), and verifies that the keys
     130             :  * on a child keys are consistent with the parent.
     131             :  *
     132             :  * Allocates a separate memory context and scans through posting tree graph.
     133             :  */
     134             : static void
     135           0 : gin_check_posting_tree_parent_keys_consistency(Relation rel, BlockNumber posting_tree_root)
     136             : {
     137           0 :     BufferAccessStrategy strategy = GetAccessStrategy(BAS_BULKREAD);
     138             :     GinPostingTreeScanItem *stack;
     139             :     MemoryContext mctx;
     140             :     MemoryContext oldcontext;
     141             : 
     142             :     int         leafdepth;
     143             : 
     144           0 :     mctx = AllocSetContextCreate(CurrentMemoryContext,
     145             :                                  "posting tree check context",
     146             :                                  ALLOCSET_DEFAULT_SIZES);
     147           0 :     oldcontext = MemoryContextSwitchTo(mctx);
     148             : 
     149             :     /*
     150             :      * We don't know the height of the tree yet, but as soon as we encounter a
     151             :      * leaf page, we will set 'leafdepth' to its depth.
     152             :      */
     153           0 :     leafdepth = -1;
     154             : 
     155             :     /* Start the scan at the root page */
     156           0 :     stack = (GinPostingTreeScanItem *) palloc0(sizeof(GinPostingTreeScanItem));
     157           0 :     stack->depth = 0;
     158           0 :     ItemPointerSetInvalid(&stack->parentkey);
     159           0 :     stack->parentblk = InvalidBlockNumber;
     160           0 :     stack->blkno = posting_tree_root;
     161             : 
     162           0 :     elog(DEBUG3, "processing posting tree at blk %u", posting_tree_root);
     163             : 
     164           0 :     while (stack)
     165             :     {
     166             :         GinPostingTreeScanItem *stack_next;
     167             :         Buffer      buffer;
     168             :         Page        page;
     169             :         OffsetNumber i,
     170             :                     maxoff;
     171             :         BlockNumber rightlink;
     172             : 
     173           0 :         CHECK_FOR_INTERRUPTS();
     174             : 
     175           0 :         buffer = ReadBufferExtended(rel, MAIN_FORKNUM, stack->blkno,
     176             :                                     RBM_NORMAL, strategy);
     177           0 :         LockBuffer(buffer, GIN_SHARE);
     178           0 :         page = (Page) BufferGetPage(buffer);
     179             : 
     180             :         Assert(GinPageIsData(page));
     181             : 
     182             :         /* Check that the tree has the same height in all branches */
     183           0 :         if (GinPageIsLeaf(page))
     184             :         {
     185             :             ItemPointerData minItem;
     186             :             int         nlist;
     187             :             ItemPointerData *list;
     188             :             char        tidrange_buf[MAXPGPATH];
     189             : 
     190           0 :             ItemPointerSetMin(&minItem);
     191             : 
     192           0 :             elog(DEBUG1, "page blk: %u, type leaf", stack->blkno);
     193             : 
     194           0 :             if (leafdepth == -1)
     195           0 :                 leafdepth = stack->depth;
     196           0 :             else if (stack->depth != leafdepth)
     197           0 :                 ereport(ERROR,
     198             :                         (errcode(ERRCODE_INDEX_CORRUPTED),
     199             :                          errmsg("index \"%s\": internal pages traversal encountered leaf page unexpectedly on block %u",
     200             :                                 RelationGetRelationName(rel), stack->blkno)));
     201           0 :             list = GinDataLeafPageGetItems(page, &nlist, minItem);
     202             : 
     203           0 :             if (nlist > 0)
     204           0 :                 snprintf(tidrange_buf, sizeof(tidrange_buf),
     205             :                          "%d tids (%u, %u) - (%u, %u)",
     206             :                          nlist,
     207             :                          ItemPointerGetBlockNumberNoCheck(&list[0]),
     208           0 :                          ItemPointerGetOffsetNumberNoCheck(&list[0]),
     209           0 :                          ItemPointerGetBlockNumberNoCheck(&list[nlist - 1]),
     210           0 :                          ItemPointerGetOffsetNumberNoCheck(&list[nlist - 1]));
     211             :             else
     212           0 :                 snprintf(tidrange_buf, sizeof(tidrange_buf), "0 tids");
     213             : 
     214           0 :             if (stack->parentblk != InvalidBlockNumber)
     215           0 :                 elog(DEBUG3, "blk %u: parent %u highkey (%u, %u), %s",
     216             :                      stack->blkno,
     217             :                      stack->parentblk,
     218             :                      ItemPointerGetBlockNumberNoCheck(&stack->parentkey),
     219             :                      ItemPointerGetOffsetNumberNoCheck(&stack->parentkey),
     220             :                      tidrange_buf);
     221             :             else
     222           0 :                 elog(DEBUG3, "blk %u: root leaf, %s",
     223             :                      stack->blkno,
     224             :                      tidrange_buf);
     225             : 
     226           0 :             if (stack->parentblk != InvalidBlockNumber &&
     227           0 :                 ItemPointerGetOffsetNumberNoCheck(&stack->parentkey) != InvalidOffsetNumber &&
     228           0 :                 nlist > 0 && ItemPointerCompare(&stack->parentkey, &list[nlist - 1]) < 0)
     229           0 :                 ereport(ERROR,
     230             :                         (errcode(ERRCODE_INDEX_CORRUPTED),
     231             :                          errmsg("index \"%s\": tid exceeds parent's high key in postingTree leaf on block %u",
     232             :                                 RelationGetRelationName(rel), stack->blkno)));
     233             :         }
     234             :         else
     235             :         {
     236             :             LocationIndex pd_lower;
     237             :             ItemPointerData bound;
     238             :             int         lowersize;
     239             : 
     240             :             /*
     241             :              * Check that tuples in each page are properly ordered and
     242             :              * consistent with parent high key
     243             :              */
     244           0 :             maxoff = GinPageGetOpaque(page)->maxoff;
     245           0 :             rightlink = GinPageGetOpaque(page)->rightlink;
     246             : 
     247           0 :             elog(DEBUG1, "page blk: %u, type data, maxoff %d", stack->blkno, maxoff);
     248             : 
     249           0 :             if (stack->parentblk != InvalidBlockNumber)
     250           0 :                 elog(DEBUG3, "blk %u: internal posting tree page with %u items, parent %u highkey (%u, %u)",
     251             :                      stack->blkno, maxoff, stack->parentblk,
     252             :                      ItemPointerGetBlockNumberNoCheck(&stack->parentkey),
     253             :                      ItemPointerGetOffsetNumberNoCheck(&stack->parentkey));
     254             :             else
     255           0 :                 elog(DEBUG3, "blk %u: root internal posting tree page with %u items",
     256             :                      stack->blkno, maxoff);
     257             : 
     258             :             /*
     259             :              * A GIN posting tree internal page stores PostingItems in the
     260             :              * 'lower' part of the page. The 'upper' part is unused. The
     261             :              * number of elements is stored in the opaque area (maxoff). Make
     262             :              * sure the size of the 'lower' part agrees with 'maxoff'
     263             :              *
     264             :              * We didn't set pd_lower until PostgreSQL version 9.4, so if this
     265             :              * check fails, it could also be because the index was
     266             :              * binary-upgraded from an earlier version. That was a long time
     267             :              * ago, though, so let's warn if it doesn't match.
     268             :              */
     269           0 :             pd_lower = ((PageHeader) page)->pd_lower;
     270           0 :             lowersize = pd_lower - MAXALIGN(SizeOfPageHeaderData);
     271           0 :             if ((lowersize - MAXALIGN(sizeof(ItemPointerData))) / sizeof(PostingItem) != maxoff)
     272           0 :                 ereport(ERROR,
     273             :                         (errcode(ERRCODE_INDEX_CORRUPTED),
     274             :                          errmsg("index \"%s\" has unexpected pd_lower %u in posting tree block %u with maxoff %u)",
     275             :                                 RelationGetRelationName(rel), pd_lower, stack->blkno, maxoff)));
     276             : 
     277             :             /*
     278             :              * Before the PostingItems, there's one ItemPointerData in the
     279             :              * 'lower' part that stores the page's high key.
     280             :              */
     281           0 :             bound = *GinDataPageGetRightBound(page);
     282             : 
     283             :             /*
     284             :              * Gin page right bound has a sane value only when not a highkey
     285             :              * on the rightmost page (at a given level). For the rightmost
     286             :              * page does not store the highkey explicitly, and the value is
     287             :              * infinity.
     288             :              */
     289           0 :             if (ItemPointerIsValid(&stack->parentkey) &&
     290           0 :                 rightlink != InvalidBlockNumber &&
     291           0 :                 !ItemPointerEquals(&stack->parentkey, &bound))
     292           0 :                 ereport(ERROR,
     293             :                         (errcode(ERRCODE_INDEX_CORRUPTED),
     294             :                          errmsg("index \"%s\": posting tree page's high key (%u, %u) doesn't match the downlink on block %u (parent blk %u, key (%u, %u))",
     295             :                                 RelationGetRelationName(rel),
     296             :                                 ItemPointerGetBlockNumberNoCheck(&bound),
     297             :                                 ItemPointerGetOffsetNumberNoCheck(&bound),
     298             :                                 stack->blkno, stack->parentblk,
     299             :                                 ItemPointerGetBlockNumberNoCheck(&stack->parentkey),
     300             :                                 ItemPointerGetOffsetNumberNoCheck(&stack->parentkey))));
     301             : 
     302           0 :             for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
     303             :             {
     304             :                 GinPostingTreeScanItem *ptr;
     305           0 :                 PostingItem *posting_item = GinDataPageGetPostingItem(page, i);
     306             : 
     307             :                 /* ItemPointerGetOffsetNumber expects a valid pointer */
     308           0 :                 if (!(i == maxoff &&
     309             :                       rightlink == InvalidBlockNumber))
     310           0 :                     elog(DEBUG3, "key (%u, %u) -> %u",
     311             :                          ItemPointerGetBlockNumber(&posting_item->key),
     312             :                          ItemPointerGetOffsetNumber(&posting_item->key),
     313             :                          BlockIdGetBlockNumber(&posting_item->child_blkno));
     314             :                 else
     315           0 :                     elog(DEBUG3, "key (%u, %u) -> %u",
     316             :                          0, 0, BlockIdGetBlockNumber(&posting_item->child_blkno));
     317             : 
     318           0 :                 if (i == maxoff && rightlink == InvalidBlockNumber)
     319             :                 {
     320             :                     /*
     321             :                      * The rightmost item in the tree level has (0, 0) as the
     322             :                      * key
     323             :                      */
     324           0 :                     if (ItemPointerGetBlockNumberNoCheck(&posting_item->key) != 0 ||
     325           0 :                         ItemPointerGetOffsetNumberNoCheck(&posting_item->key) != 0)
     326           0 :                         ereport(ERROR,
     327             :                                 (errcode(ERRCODE_INDEX_CORRUPTED),
     328             :                                  errmsg("index \"%s\": rightmost posting tree page (blk %u) has unexpected last key (%u, %u)",
     329             :                                         RelationGetRelationName(rel),
     330             :                                         stack->blkno,
     331             :                                         ItemPointerGetBlockNumberNoCheck(&posting_item->key),
     332             :                                         ItemPointerGetOffsetNumberNoCheck(&posting_item->key))));
     333             :                 }
     334           0 :                 else if (i != FirstOffsetNumber)
     335             :                 {
     336           0 :                     PostingItem *previous_posting_item = GinDataPageGetPostingItem(page, i - 1);
     337             : 
     338           0 :                     if (ItemPointerCompare(&posting_item->key, &previous_posting_item->key) < 0)
     339           0 :                         ereport(ERROR,
     340             :                                 (errcode(ERRCODE_INDEX_CORRUPTED),
     341             :                                  errmsg("index \"%s\" has wrong tuple order in posting tree, block %u, offset %u",
     342             :                                         RelationGetRelationName(rel), stack->blkno, i)));
     343             :                 }
     344             : 
     345             :                 /*
     346             :                  * Check if this tuple is consistent with the downlink in the
     347             :                  * parent.
     348             :                  */
     349           0 :                 if (stack->parentblk != InvalidBlockNumber && i == maxoff &&
     350           0 :                     ItemPointerCompare(&stack->parentkey, &posting_item->key) < 0)
     351           0 :                     ereport(ERROR,
     352             :                             (errcode(ERRCODE_INDEX_CORRUPTED),
     353             :                              errmsg("index \"%s\": posting item exceeds parent's high key in postingTree internal page on block %u offset %u",
     354             :                                     RelationGetRelationName(rel),
     355             :                                     stack->blkno, i)));
     356             : 
     357             :                 /* This is an internal page, recurse into the child. */
     358           0 :                 ptr = (GinPostingTreeScanItem *) palloc(sizeof(GinPostingTreeScanItem));
     359           0 :                 ptr->depth = stack->depth + 1;
     360             : 
     361             :                 /*
     362             :                  * Set rightmost parent key to invalid iterm pointer. Its
     363             :                  * value is 'Infinity' and not explicitly stored.
     364             :                  */
     365           0 :                 if (rightlink == InvalidBlockNumber)
     366           0 :                     ItemPointerSetInvalid(&ptr->parentkey);
     367             :                 else
     368           0 :                     ptr->parentkey = posting_item->key;
     369             : 
     370           0 :                 ptr->parentblk = stack->blkno;
     371           0 :                 ptr->blkno = BlockIdGetBlockNumber(&posting_item->child_blkno);
     372           0 :                 ptr->next = stack->next;
     373           0 :                 stack->next = ptr;
     374             :             }
     375             :         }
     376           0 :         LockBuffer(buffer, GIN_UNLOCK);
     377           0 :         ReleaseBuffer(buffer);
     378             : 
     379             :         /* Step to next item in the queue */
     380           0 :         stack_next = stack->next;
     381           0 :         pfree(stack);
     382           0 :         stack = stack_next;
     383             :     }
     384             : 
     385           0 :     MemoryContextSwitchTo(oldcontext);
     386           0 :     MemoryContextDelete(mctx);
     387           0 : }
     388             : 
     389             : /*
     390             :  * Main entry point for GIN checks.
     391             :  *
     392             :  * Allocates memory context and scans through the whole GIN graph.
     393             :  */
     394             : static void
     395          92 : gin_check_parent_keys_consistency(Relation rel,
     396             :                                   Relation heaprel,
     397             :                                   void *callback_state,
     398             :                                   bool readonly)
     399             : {
     400          92 :     BufferAccessStrategy strategy = GetAccessStrategy(BAS_BULKREAD);
     401             :     GinScanItem *stack;
     402             :     MemoryContext mctx;
     403             :     MemoryContext oldcontext;
     404             :     GinState    state;
     405             :     int         leafdepth;
     406             : 
     407          92 :     mctx = AllocSetContextCreate(CurrentMemoryContext,
     408             :                                  "amcheck consistency check context",
     409             :                                  ALLOCSET_DEFAULT_SIZES);
     410          92 :     oldcontext = MemoryContextSwitchTo(mctx);
     411          92 :     initGinState(&state, rel);
     412             : 
     413             :     /*
     414             :      * We don't know the height of the tree yet, but as soon as we encounter a
     415             :      * leaf page, we will set 'leafdepth' to its depth.
     416             :      */
     417          92 :     leafdepth = -1;
     418             : 
     419             :     /* Start the scan at the root page */
     420          92 :     stack = (GinScanItem *) palloc0(sizeof(GinScanItem));
     421          92 :     stack->depth = 0;
     422          92 :     stack->parenttup = NULL;
     423          92 :     stack->parentblk = InvalidBlockNumber;
     424          92 :     stack->parentlsn = InvalidXLogRecPtr;
     425          92 :     stack->blkno = GIN_ROOT_BLKNO;
     426             : 
     427         414 :     while (stack)
     428             :     {
     429             :         GinScanItem *stack_next;
     430             :         Buffer      buffer;
     431             :         Page        page;
     432             :         OffsetNumber i,
     433             :                     maxoff,
     434             :                     prev_attnum;
     435             :         XLogRecPtr  lsn;
     436             :         IndexTuple  prev_tuple;
     437             :         BlockNumber rightlink;
     438             : 
     439         322 :         CHECK_FOR_INTERRUPTS();
     440             : 
     441         322 :         buffer = ReadBufferExtended(rel, MAIN_FORKNUM, stack->blkno,
     442             :                                     RBM_NORMAL, strategy);
     443         322 :         LockBuffer(buffer, GIN_SHARE);
     444         322 :         page = (Page) BufferGetPage(buffer);
     445         322 :         lsn = BufferGetLSNAtomic(buffer);
     446         322 :         maxoff = PageGetMaxOffsetNumber(page);
     447         322 :         rightlink = GinPageGetOpaque(page)->rightlink;
     448             : 
     449             :         /* Do basic sanity checks on the page headers */
     450         322 :         check_index_page(rel, buffer, stack->blkno);
     451             : 
     452         322 :         elog(DEBUG3, "processing entry tree page at blk %u, maxoff: %u", stack->blkno, maxoff);
     453             : 
     454             :         /*
     455             :          * It's possible that the page was split since we looked at the
     456             :          * parent, so that we didn't missed the downlink of the right sibling
     457             :          * when we scanned the parent.  If so, add the right sibling to the
     458             :          * stack now.
     459             :          */
     460         322 :         if (stack->parenttup != NULL)
     461             :         {
     462             :             GinNullCategory parent_key_category;
     463           0 :             Datum       parent_key = gintuple_get_key(&state,
     464             :                                                       stack->parenttup,
     465             :                                                       &parent_key_category);
     466           0 :             ItemId      iid = PageGetItemIdCareful(rel, stack->blkno,
     467             :                                                    page, maxoff);
     468           0 :             IndexTuple  idxtuple = (IndexTuple) PageGetItem(page, iid);
     469           0 :             OffsetNumber attnum = gintuple_get_attrnum(&state, idxtuple);
     470             :             GinNullCategory page_max_key_category;
     471           0 :             Datum       page_max_key = gintuple_get_key(&state, idxtuple, &page_max_key_category);
     472             : 
     473           0 :             if (rightlink != InvalidBlockNumber &&
     474           0 :                 ginCompareEntries(&state, attnum, page_max_key,
     475             :                                   page_max_key_category, parent_key,
     476             :                                   parent_key_category) > 0)
     477             :             {
     478             :                 /* split page detected, install right link to the stack */
     479             :                 GinScanItem *ptr;
     480             : 
     481           0 :                 elog(DEBUG3, "split detected for blk: %u, parent blk: %u", stack->blkno, stack->parentblk);
     482             : 
     483           0 :                 ptr = (GinScanItem *) palloc(sizeof(GinScanItem));
     484           0 :                 ptr->depth = stack->depth;
     485           0 :                 ptr->parenttup = CopyIndexTuple(stack->parenttup);
     486           0 :                 ptr->parentblk = stack->parentblk;
     487           0 :                 ptr->parentlsn = stack->parentlsn;
     488           0 :                 ptr->blkno = rightlink;
     489           0 :                 ptr->next = stack->next;
     490           0 :                 stack->next = ptr;
     491             :             }
     492             :         }
     493             : 
     494             :         /* Check that the tree has the same height in all branches */
     495         322 :         if (GinPageIsLeaf(page))
     496             :         {
     497         316 :             if (leafdepth == -1)
     498          92 :                 leafdepth = stack->depth;
     499         224 :             else if (stack->depth != leafdepth)
     500           0 :                 ereport(ERROR,
     501             :                         (errcode(ERRCODE_INDEX_CORRUPTED),
     502             :                          errmsg("index \"%s\": internal pages traversal encountered leaf page unexpectedly on block %u",
     503             :                                 RelationGetRelationName(rel), stack->blkno)));
     504             :         }
     505             : 
     506             :         /*
     507             :          * Check that tuples in each page are properly ordered and consistent
     508             :          * with parent high key
     509             :          */
     510         322 :         prev_tuple = NULL;
     511         322 :         prev_attnum = InvalidAttrNumber;
     512        3848 :         for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
     513             :         {
     514        3526 :             ItemId      iid = PageGetItemIdCareful(rel, stack->blkno, page, i);
     515        3526 :             IndexTuple  idxtuple = (IndexTuple) PageGetItem(page, iid);
     516        3526 :             OffsetNumber attnum = gintuple_get_attrnum(&state, idxtuple);
     517             :             GinNullCategory prev_key_category;
     518             :             Datum       prev_key;
     519             :             GinNullCategory current_key_category;
     520             :             Datum       current_key;
     521             : 
     522        3526 :             if (MAXALIGN(ItemIdGetLength(iid)) != MAXALIGN(IndexTupleSize(idxtuple)))
     523           0 :                 ereport(ERROR,
     524             :                         (errcode(ERRCODE_INDEX_CORRUPTED),
     525             :                          errmsg("index \"%s\" has inconsistent tuple sizes, block %u, offset %u",
     526             :                                 RelationGetRelationName(rel), stack->blkno, i)));
     527             : 
     528        3526 :             current_key = gintuple_get_key(&state, idxtuple, &current_key_category);
     529             : 
     530             :             /*
     531             :              * First block is metadata, skip order check. Also, never check
     532             :              * for high key on rightmost page, as this key is not really
     533             :              * stored explicitly.
     534             :              *
     535             :              * Also make sure to not compare entries for different attnums,
     536             :              * which may be stored on the same page.
     537             :              */
     538        3526 :             if (i != FirstOffsetNumber && attnum == prev_attnum && stack->blkno != GIN_ROOT_BLKNO &&
     539         230 :                 !(i == maxoff && rightlink == InvalidBlockNumber))
     540             :             {
     541        2186 :                 prev_key = gintuple_get_key(&state, prev_tuple, &prev_key_category);
     542        2186 :                 if (ginCompareEntries(&state, attnum, prev_key,
     543             :                                       prev_key_category, current_key,
     544             :                                       current_key_category) >= 0)
     545           0 :                     ereport(ERROR,
     546             :                             (errcode(ERRCODE_INDEX_CORRUPTED),
     547             :                              errmsg("index \"%s\" has wrong tuple order on entry tree page, block %u, offset %u, rightlink %u",
     548             :                                     RelationGetRelationName(rel), stack->blkno, i, rightlink)));
     549             :             }
     550             : 
     551             :             /*
     552             :              * Check if this tuple is consistent with the downlink in the
     553             :              * parent.
     554             :              */
     555        3526 :             if (stack->parenttup &&
     556             :                 i == maxoff)
     557             :             {
     558             :                 GinNullCategory parent_key_category;
     559           0 :                 Datum       parent_key = gintuple_get_key(&state,
     560             :                                                           stack->parenttup,
     561             :                                                           &parent_key_category);
     562             : 
     563           0 :                 if (ginCompareEntries(&state, attnum, current_key,
     564             :                                       current_key_category, parent_key,
     565             :                                       parent_key_category) > 0)
     566             :                 {
     567             :                     /*
     568             :                      * There was a discrepancy between parent and child
     569             :                      * tuples. We need to verify it is not a result of
     570             :                      * concurrent call of gistplacetopage(). So, lock parent
     571             :                      * and try to find downlink for current page. It may be
     572             :                      * missing due to concurrent page split, this is OK.
     573             :                      */
     574           0 :                     pfree(stack->parenttup);
     575           0 :                     stack->parenttup = gin_refind_parent(rel, stack->parentblk,
     576             :                                                          stack->blkno, strategy);
     577             : 
     578             :                     /* We found it - make a final check before failing */
     579           0 :                     if (!stack->parenttup)
     580           0 :                         elog(NOTICE, "Unable to find parent tuple for block %u on block %u due to concurrent split",
     581             :                              stack->blkno, stack->parentblk);
     582             :                     else
     583             :                     {
     584           0 :                         parent_key = gintuple_get_key(&state,
     585             :                                                       stack->parenttup,
     586             :                                                       &parent_key_category);
     587             : 
     588             :                         /*
     589             :                          * Check if it is properly adjusted. If succeed,
     590             :                          * procced to the next key.
     591             :                          */
     592           0 :                         if (ginCompareEntries(&state, attnum, current_key,
     593             :                                               current_key_category, parent_key,
     594             :                                               parent_key_category) > 0)
     595           0 :                             ereport(ERROR,
     596             :                                     (errcode(ERRCODE_INDEX_CORRUPTED),
     597             :                                      errmsg("index \"%s\" has inconsistent records on page %u offset %u",
     598             :                                             RelationGetRelationName(rel), stack->blkno, i)));
     599             :                     }
     600             :                 }
     601             :             }
     602             : 
     603             :             /* If this is an internal page, recurse into the child */
     604        3526 :             if (!GinPageIsLeaf(page))
     605             :             {
     606             :                 GinScanItem *ptr;
     607             : 
     608         230 :                 ptr = (GinScanItem *) palloc(sizeof(GinScanItem));
     609         230 :                 ptr->depth = stack->depth + 1;
     610             :                 /* last tuple in layer has no high key */
     611         230 :                 if (i != maxoff && !GinPageGetOpaque(page)->rightlink)
     612           0 :                     ptr->parenttup = CopyIndexTuple(idxtuple);
     613             :                 else
     614         230 :                     ptr->parenttup = NULL;
     615         230 :                 ptr->parentblk = stack->blkno;
     616         230 :                 ptr->blkno = GinGetDownlink(idxtuple);
     617         230 :                 ptr->parentlsn = lsn;
     618         230 :                 ptr->next = stack->next;
     619         230 :                 stack->next = ptr;
     620             :             }
     621             :             /* If this item is a pointer to a posting tree, recurse into it */
     622        3296 :             else if (GinIsPostingTree(idxtuple))
     623             :             {
     624           0 :                 BlockNumber rootPostingTree = GinGetPostingTree(idxtuple);
     625             : 
     626           0 :                 gin_check_posting_tree_parent_keys_consistency(rel, rootPostingTree);
     627             :             }
     628             :             else
     629             :             {
     630             :                 ItemPointer ipd;
     631             :                 int         nipd;
     632             : 
     633        3296 :                 ipd = ginReadTupleWithoutState(idxtuple, &nipd);
     634             : 
     635      700048 :                 for (int j = 0; j < nipd; j++)
     636             :                 {
     637      696752 :                     if (!OffsetNumberIsValid(ItemPointerGetOffsetNumber(&ipd[j])))
     638           0 :                         ereport(ERROR,
     639             :                                 (errcode(ERRCODE_INDEX_CORRUPTED),
     640             :                                  errmsg("index \"%s\": posting list contains invalid heap pointer on block %u",
     641             :                                         RelationGetRelationName(rel), stack->blkno)));
     642             :                 }
     643        3296 :                 pfree(ipd);
     644             :             }
     645             : 
     646        3526 :             prev_tuple = CopyIndexTuple(idxtuple);
     647        3526 :             prev_attnum = attnum;
     648             :         }
     649             : 
     650         322 :         LockBuffer(buffer, GIN_UNLOCK);
     651         322 :         ReleaseBuffer(buffer);
     652             : 
     653             :         /* Step to next item in the queue */
     654         322 :         stack_next = stack->next;
     655         322 :         if (stack->parenttup)
     656           0 :             pfree(stack->parenttup);
     657         322 :         pfree(stack);
     658         322 :         stack = stack_next;
     659             :     }
     660             : 
     661          92 :     MemoryContextSwitchTo(oldcontext);
     662          92 :     MemoryContextDelete(mctx);
     663          92 : }
     664             : 
     665             : /*
     666             :  * Verify that a freshly-read page looks sane.
     667             :  */
     668             : static void
     669         322 : check_index_page(Relation rel, Buffer buffer, BlockNumber blockNo)
     670             : {
     671         322 :     Page        page = BufferGetPage(buffer);
     672             : 
     673             :     /*
     674             :      * ReadBuffer verifies that every newly-read page passes
     675             :      * PageHeaderIsValid, which means it either contains a reasonably sane
     676             :      * page header or is all-zero.  We have to defend against the all-zero
     677             :      * case, however.
     678             :      */
     679         322 :     if (PageIsNew(page))
     680           0 :         ereport(ERROR,
     681             :                 (errcode(ERRCODE_INDEX_CORRUPTED),
     682             :                  errmsg("index \"%s\" contains unexpected zero page at block %u",
     683             :                         RelationGetRelationName(rel),
     684             :                         BufferGetBlockNumber(buffer)),
     685             :                  errhint("Please REINDEX it.")));
     686             : 
     687             :     /*
     688             :      * Additionally check that the special area looks sane.
     689             :      */
     690         322 :     if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GinPageOpaqueData)))
     691           0 :         ereport(ERROR,
     692             :                 (errcode(ERRCODE_INDEX_CORRUPTED),
     693             :                  errmsg("index \"%s\" contains corrupted page at block %u",
     694             :                         RelationGetRelationName(rel),
     695             :                         BufferGetBlockNumber(buffer)),
     696             :                  errhint("Please REINDEX it.")));
     697             : 
     698         322 :     if (GinPageIsDeleted(page))
     699             :     {
     700           0 :         if (!GinPageIsLeaf(page))
     701           0 :             ereport(ERROR,
     702             :                     (errcode(ERRCODE_INDEX_CORRUPTED),
     703             :                      errmsg("index \"%s\" has deleted internal page %d",
     704             :                             RelationGetRelationName(rel), blockNo)));
     705           0 :         if (PageGetMaxOffsetNumber(page) > InvalidOffsetNumber)
     706           0 :             ereport(ERROR,
     707             :                     (errcode(ERRCODE_INDEX_CORRUPTED),
     708             :                      errmsg("index \"%s\" has deleted page %d with tuples",
     709             :                             RelationGetRelationName(rel), blockNo)));
     710             :     }
     711         322 :     else if (PageGetMaxOffsetNumber(page) > MaxIndexTuplesPerPage)
     712           0 :         ereport(ERROR,
     713             :                 (errcode(ERRCODE_INDEX_CORRUPTED),
     714             :                  errmsg("index \"%s\" has page %d with exceeding count of tuples",
     715             :                         RelationGetRelationName(rel), blockNo)));
     716         322 : }
     717             : 
     718             : /*
     719             :  * Try to re-find downlink pointing to 'blkno', in 'parentblkno'.
     720             :  *
     721             :  * If found, returns a palloc'd copy of the downlink tuple. Otherwise,
     722             :  * returns NULL.
     723             :  */
     724             : static IndexTuple
     725           0 : gin_refind_parent(Relation rel, BlockNumber parentblkno,
     726             :                   BlockNumber childblkno, BufferAccessStrategy strategy)
     727             : {
     728             :     Buffer      parentbuf;
     729             :     Page        parentpage;
     730             :     OffsetNumber o,
     731             :                 parent_maxoff;
     732           0 :     IndexTuple  result = NULL;
     733             : 
     734           0 :     parentbuf = ReadBufferExtended(rel, MAIN_FORKNUM, parentblkno, RBM_NORMAL,
     735             :                                    strategy);
     736             : 
     737           0 :     LockBuffer(parentbuf, GIN_SHARE);
     738           0 :     parentpage = BufferGetPage(parentbuf);
     739             : 
     740           0 :     if (GinPageIsLeaf(parentpage))
     741             :     {
     742           0 :         UnlockReleaseBuffer(parentbuf);
     743           0 :         return result;
     744             :     }
     745             : 
     746           0 :     parent_maxoff = PageGetMaxOffsetNumber(parentpage);
     747           0 :     for (o = FirstOffsetNumber; o <= parent_maxoff; o = OffsetNumberNext(o))
     748             :     {
     749           0 :         ItemId      p_iid = PageGetItemIdCareful(rel, parentblkno, parentpage, o);
     750           0 :         IndexTuple  itup = (IndexTuple) PageGetItem(parentpage, p_iid);
     751             : 
     752           0 :         if (ItemPointerGetBlockNumber(&(itup->t_tid)) == childblkno)
     753             :         {
     754             :             /* Found it! Make copy and return it */
     755           0 :             result = CopyIndexTuple(itup);
     756           0 :             break;
     757             :         }
     758             :     }
     759             : 
     760           0 :     UnlockReleaseBuffer(parentbuf);
     761             : 
     762           0 :     return result;
     763             : }
     764             : 
     765             : static ItemId
     766        3526 : PageGetItemIdCareful(Relation rel, BlockNumber block, Page page,
     767             :                      OffsetNumber offset)
     768             : {
     769        3526 :     ItemId      itemid = PageGetItemId(page, offset);
     770             : 
     771        3526 :     if (ItemIdGetOffset(itemid) + ItemIdGetLength(itemid) >
     772             :         BLCKSZ - MAXALIGN(sizeof(GinPageOpaqueData)))
     773           0 :         ereport(ERROR,
     774             :                 (errcode(ERRCODE_INDEX_CORRUPTED),
     775             :                  errmsg("line pointer points past end of tuple space in index \"%s\"",
     776             :                         RelationGetRelationName(rel)),
     777             :                  errdetail_internal("Index tid=(%u,%u) lp_off=%u, lp_len=%u lp_flags=%u.",
     778             :                                     block, offset, ItemIdGetOffset(itemid),
     779             :                                     ItemIdGetLength(itemid),
     780             :                                     ItemIdGetFlags(itemid))));
     781             : 
     782             :     /*
     783             :      * Verify that line pointer isn't LP_REDIRECT or LP_UNUSED or LP_DEAD,
     784             :      * since GIN never uses all three.  Verify that line pointer has storage,
     785             :      * too.
     786             :      */
     787        3526 :     if (ItemIdIsRedirected(itemid) || !ItemIdIsUsed(itemid) ||
     788        3526 :         ItemIdIsDead(itemid) || ItemIdGetLength(itemid) == 0)
     789           0 :         ereport(ERROR,
     790             :                 (errcode(ERRCODE_INDEX_CORRUPTED),
     791             :                  errmsg("invalid line pointer storage in index \"%s\"",
     792             :                         RelationGetRelationName(rel)),
     793             :                  errdetail_internal("Index tid=(%u,%u) lp_off=%u, lp_len=%u lp_flags=%u.",
     794             :                                     block, offset, ItemIdGetOffset(itemid),
     795             :                                     ItemIdGetLength(itemid),
     796             :                                     ItemIdGetFlags(itemid))));
     797             : 
     798        3526 :     return itemid;
     799             : }

Generated by: LCOV version 1.14