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

Generated by: LCOV version 2.3.2-1