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, ¤t_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 : }
|