PostgreSQL Source Code  git master
verify_heapam.c
Go to the documentation of this file.
1 /*-------------------------------------------------------------------------
2  *
3  * verify_heapam.c
4  * Functions to check postgresql heap relations for corruption
5  *
6  * Copyright (c) 2016-2021, PostgreSQL Global Development Group
7  *
8  * contrib/amcheck/verify_heapam.c
9  *-------------------------------------------------------------------------
10  */
11 #include "postgres.h"
12 
13 #include "access/detoast.h"
14 #include "access/genam.h"
15 #include "access/heapam.h"
16 #include "access/heaptoast.h"
17 #include "access/multixact.h"
18 #include "access/toast_internals.h"
19 #include "access/visibilitymap.h"
20 #include "catalog/pg_am.h"
21 #include "funcapi.h"
22 #include "miscadmin.h"
23 #include "storage/bufmgr.h"
24 #include "storage/procarray.h"
25 #include "utils/builtins.h"
26 #include "utils/fmgroids.h"
27 
29 
30 /* The number of columns in tuples returned by verify_heapam */
31 #define HEAPCHECK_RELATION_COLS 4
32 
33 /*
34  * Despite the name, we use this for reporting problems with both XIDs and
35  * MXIDs.
36  */
37 typedef enum XidBoundsViolation
38 {
45 
46 typedef enum XidCommitStatus
47 {
53 
54 typedef enum SkipPages
55 {
59 } SkipPages;
60 
61 /*
62  * Struct holding information about a toasted attribute sufficient to both
63  * check the toasted attribute and, if found to be corrupt, to report where it
64  * was encountered in the main table.
65  */
66 typedef struct ToastedAttribute
67 {
69  BlockNumber blkno; /* block in main table */
70  OffsetNumber offnum; /* offset in main table */
71  AttrNumber attnum; /* attribute in main table */
73 
74 /*
75  * Struct holding the running context information during
76  * a lifetime of a verify_heapam execution.
77  */
78 typedef struct HeapCheckContext
79 {
80  /*
81  * Cached copies of values from ShmemVariableCache and computed values
82  * from them.
83  */
84  FullTransactionId next_fxid; /* ShmemVariableCache->nextXid */
85  TransactionId next_xid; /* 32-bit version of next_fxid */
86  TransactionId oldest_xid; /* ShmemVariableCache->oldestXid */
87  FullTransactionId oldest_fxid; /* 64-bit version of oldest_xid, computed
88  * relative to next_fxid */
89  TransactionId safe_xmin; /* this XID and newer ones can't become
90  * all-visible while we're running */
91 
92  /*
93  * Cached copy of value from MultiXactState
94  */
95  MultiXactId next_mxact; /* MultiXactState->nextMXact */
96  MultiXactId oldest_mxact; /* MultiXactState->oldestMultiXactId */
97 
98  /*
99  * Cached copies of the most recently checked xid and its status.
100  */
103 
104  /* Values concerning the heap relation being checked */
113 
114  /* Values for iterating over pages in the relation */
119 
120  /* Values for iterating over tuples within a page */
126  int natts;
127 
128  /* Values for iterating over attributes within the tuple */
129  uint32 offset; /* offset in tuple data */
131 
132  /* True if tuple's xmax makes it eligible for pruning */
134 
135  /*
136  * List of ToastedAttribute structs for toasted attributes which are not
137  * eligible for pruning and should be checked
138  */
140 
141  /* Whether verify_heapam has yet encountered any corrupt tuples */
143 
144  /* The descriptor and tuplestore for verify_heapam's result tuples */
148 
149 /* Internal implementation */
150 static void check_tuple(HeapCheckContext *ctx);
151 static void check_toast_tuple(HeapTuple toasttup, HeapCheckContext *ctx,
152  ToastedAttribute *ta, int32 *expected_chunk_seq,
153  uint32 extsize);
154 
155 static bool check_tuple_attribute(HeapCheckContext *ctx);
157  ToastedAttribute *ta);
158 
159 static bool check_tuple_header(HeapCheckContext *ctx);
160 static bool check_tuple_visibility(HeapCheckContext *ctx);
161 
162 static void report_corruption(HeapCheckContext *ctx, char *msg);
164  ToastedAttribute *ta, char *msg);
165 static TupleDesc verify_heapam_tupdesc(void);
167  const HeapCheckContext *ctx);
168 static void update_cached_xid_range(HeapCheckContext *ctx);
171  HeapCheckContext *ctx);
173  HeapCheckContext *ctx);
175  HeapCheckContext *ctx,
177 
178 /*
179  * Scan and report corruption in heap pages, optionally reconciling toasted
180  * attributes with entries in the associated toast table. Intended to be
181  * called from SQL with the following parameters:
182  *
183  * relation:
184  * The Oid of the heap relation to be checked.
185  *
186  * on_error_stop:
187  * Whether to stop at the end of the first page for which errors are
188  * detected. Note that multiple rows may be returned.
189  *
190  * check_toast:
191  * Whether to check each toasted attribute against the toast table to
192  * verify that it can be found there.
193  *
194  * skip:
195  * What kinds of pages in the heap relation should be skipped. Valid
196  * options are "all-visible", "all-frozen", and "none".
197  *
198  * Returns to the SQL caller a set of tuples, each containing the location
199  * and a description of a corruption found in the heap.
200  *
201  * This code goes to some trouble to avoid crashing the server even if the
202  * table pages are badly corrupted, but it's probably not perfect. If
203  * check_toast is true, we'll use regular index lookups to try to fetch TOAST
204  * tuples, which can certainly cause crashes if the right kind of corruption
205  * exists in the toast table or index. No matter what parameters you pass,
206  * we can't protect against crashes that might occur trying to look up the
207  * commit status of transaction IDs (though we avoid trying to do such lookups
208  * for transaction IDs that can't legally appear in the table).
209  */
210 Datum
212 {
213  ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
214  MemoryContext old_context;
215  bool random_access;
216  HeapCheckContext ctx;
217  Buffer vmbuffer = InvalidBuffer;
218  Oid relid;
219  bool on_error_stop;
220  bool check_toast;
221  SkipPages skip_option = SKIP_PAGES_NONE;
222  BlockNumber first_block;
223  BlockNumber last_block;
224  BlockNumber nblocks;
225  const char *skip;
226 
227  /* Check to see if caller supports us returning a tuplestore */
228  if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
229  ereport(ERROR,
230  (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
231  errmsg("set-valued function called in context that cannot accept a set")));
232  if (!(rsinfo->allowedModes & SFRM_Materialize))
233  ereport(ERROR,
234  (errcode(ERRCODE_SYNTAX_ERROR),
235  errmsg("materialize mode required, but it is not allowed in this context")));
236 
237  /* Check supplied arguments */
238  if (PG_ARGISNULL(0))
239  ereport(ERROR,
240  (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
241  errmsg("relation cannot be null")));
242  relid = PG_GETARG_OID(0);
243 
244  if (PG_ARGISNULL(1))
245  ereport(ERROR,
246  (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
247  errmsg("on_error_stop cannot be null")));
248  on_error_stop = PG_GETARG_BOOL(1);
249 
250  if (PG_ARGISNULL(2))
251  ereport(ERROR,
252  (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
253  errmsg("check_toast cannot be null")));
254  check_toast = PG_GETARG_BOOL(2);
255 
256  if (PG_ARGISNULL(3))
257  ereport(ERROR,
258  (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
259  errmsg("skip cannot be null")));
261  if (pg_strcasecmp(skip, "all-visible") == 0)
262  skip_option = SKIP_PAGES_ALL_VISIBLE;
263  else if (pg_strcasecmp(skip, "all-frozen") == 0)
264  skip_option = SKIP_PAGES_ALL_FROZEN;
265  else if (pg_strcasecmp(skip, "none") == 0)
266  skip_option = SKIP_PAGES_NONE;
267  else
268  ereport(ERROR,
269  (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
270  errmsg("invalid skip option"),
271  errhint("Valid skip options are \"all-visible\", \"all-frozen\", and \"none\".")));
272 
273  memset(&ctx, 0, sizeof(HeapCheckContext));
274  ctx.cached_xid = InvalidTransactionId;
275  ctx.toasted_attributes = NIL;
276 
277  /*
278  * Any xmin newer than the xmin of our snapshot can't become all-visible
279  * while we're running.
280  */
281  ctx.safe_xmin = GetTransactionSnapshot()->xmin;
282 
283  /*
284  * If we report corruption when not examining some individual attribute,
285  * we need attnum to be reported as NULL. Set that up before any
286  * corruption reporting might happen.
287  */
288  ctx.attnum = -1;
289 
290  /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
291  old_context = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
292  random_access = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
293  ctx.tupdesc = verify_heapam_tupdesc();
294  ctx.tupstore = tuplestore_begin_heap(random_access, false, work_mem);
295  rsinfo->returnMode = SFRM_Materialize;
296  rsinfo->setResult = ctx.tupstore;
297  rsinfo->setDesc = ctx.tupdesc;
298  MemoryContextSwitchTo(old_context);
299 
300  /* Open relation, check relkind and access method */
301  ctx.rel = relation_open(relid, AccessShareLock);
302 
303  /*
304  * Check that a relation's relkind and access method are both supported.
305  */
306  if (ctx.rel->rd_rel->relkind != RELKIND_RELATION &&
307  ctx.rel->rd_rel->relkind != RELKIND_MATVIEW &&
308  ctx.rel->rd_rel->relkind != RELKIND_TOASTVALUE)
309  ereport(ERROR,
310  (errcode(ERRCODE_WRONG_OBJECT_TYPE),
311  errmsg("cannot check relation \"%s\"",
312  RelationGetRelationName(ctx.rel)),
313  errdetail_relkind_not_supported(ctx.rel->rd_rel->relkind)));
314 
315  if (ctx.rel->rd_rel->relam != HEAP_TABLE_AM_OID)
316  ereport(ERROR,
317  (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
318  errmsg("only heap AM is supported")));
319 
320  /* Early exit if the relation is empty */
321  nblocks = RelationGetNumberOfBlocks(ctx.rel);
322  if (!nblocks)
323  {
325  PG_RETURN_NULL();
326  }
327 
328  ctx.bstrategy = GetAccessStrategy(BAS_BULKREAD);
329  ctx.buffer = InvalidBuffer;
330  ctx.page = NULL;
331 
332  /* Validate block numbers, or handle nulls. */
333  if (PG_ARGISNULL(4))
334  first_block = 0;
335  else
336  {
337  int64 fb = PG_GETARG_INT64(4);
338 
339  if (fb < 0 || fb >= nblocks)
340  ereport(ERROR,
341  (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
342  errmsg("starting block number must be between 0 and %u",
343  nblocks - 1)));
344  first_block = (BlockNumber) fb;
345  }
346  if (PG_ARGISNULL(5))
347  last_block = nblocks - 1;
348  else
349  {
350  int64 lb = PG_GETARG_INT64(5);
351 
352  if (lb < 0 || lb >= nblocks)
353  ereport(ERROR,
354  (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
355  errmsg("ending block number must be between 0 and %u",
356  nblocks - 1)));
357  last_block = (BlockNumber) lb;
358  }
359 
360  /* Optionally open the toast relation, if any. */
361  if (ctx.rel->rd_rel->reltoastrelid && check_toast)
362  {
363  int offset;
364 
365  /* Main relation has associated toast relation */
366  ctx.toast_rel = table_open(ctx.rel->rd_rel->reltoastrelid,
368  offset = toast_open_indexes(ctx.toast_rel,
370  &(ctx.toast_indexes),
371  &(ctx.num_toast_indexes));
372  ctx.valid_toast_index = ctx.toast_indexes[offset];
373  }
374  else
375  {
376  /*
377  * Main relation has no associated toast relation, or we're
378  * intentionally skipping it.
379  */
380  ctx.toast_rel = NULL;
381  ctx.toast_indexes = NULL;
382  ctx.num_toast_indexes = 0;
383  }
384 
387  ctx.relfrozenxid = ctx.rel->rd_rel->relfrozenxid;
388  ctx.relfrozenfxid = FullTransactionIdFromXidAndCtx(ctx.relfrozenxid, &ctx);
389  ctx.relminmxid = ctx.rel->rd_rel->relminmxid;
390 
391  if (TransactionIdIsNormal(ctx.relfrozenxid))
392  ctx.oldest_xid = ctx.relfrozenxid;
393 
394  for (ctx.blkno = first_block; ctx.blkno <= last_block; ctx.blkno++)
395  {
396  OffsetNumber maxoff;
397 
398  /* Optionally skip over all-frozen or all-visible blocks */
399  if (skip_option != SKIP_PAGES_NONE)
400  {
401  int32 mapbits;
402 
403  mapbits = (int32) visibilitymap_get_status(ctx.rel, ctx.blkno,
404  &vmbuffer);
405  if (skip_option == SKIP_PAGES_ALL_FROZEN)
406  {
407  if ((mapbits & VISIBILITYMAP_ALL_FROZEN) != 0)
408  continue;
409  }
410 
411  if (skip_option == SKIP_PAGES_ALL_VISIBLE)
412  {
413  if ((mapbits & VISIBILITYMAP_ALL_VISIBLE) != 0)
414  continue;
415  }
416  }
417 
418  /* Read and lock the next page. */
419  ctx.buffer = ReadBufferExtended(ctx.rel, MAIN_FORKNUM, ctx.blkno,
420  RBM_NORMAL, ctx.bstrategy);
421  LockBuffer(ctx.buffer, BUFFER_LOCK_SHARE);
422  ctx.page = BufferGetPage(ctx.buffer);
423 
424  /* Perform tuple checks */
425  maxoff = PageGetMaxOffsetNumber(ctx.page);
426  for (ctx.offnum = FirstOffsetNumber; ctx.offnum <= maxoff;
427  ctx.offnum = OffsetNumberNext(ctx.offnum))
428  {
429  ctx.itemid = PageGetItemId(ctx.page, ctx.offnum);
430 
431  /* Skip over unused/dead line pointers */
432  if (!ItemIdIsUsed(ctx.itemid) || ItemIdIsDead(ctx.itemid))
433  continue;
434 
435  /*
436  * If this line pointer has been redirected, check that it
437  * redirects to a valid offset within the line pointer array
438  */
439  if (ItemIdIsRedirected(ctx.itemid))
440  {
441  OffsetNumber rdoffnum = ItemIdGetRedirect(ctx.itemid);
442  ItemId rditem;
443 
444  if (rdoffnum < FirstOffsetNumber)
445  {
446  report_corruption(&ctx,
447  psprintf("line pointer redirection to item at offset %u precedes minimum offset %u",
448  (unsigned) rdoffnum,
449  (unsigned) FirstOffsetNumber));
450  continue;
451  }
452  if (rdoffnum > maxoff)
453  {
454  report_corruption(&ctx,
455  psprintf("line pointer redirection to item at offset %u exceeds maximum offset %u",
456  (unsigned) rdoffnum,
457  (unsigned) maxoff));
458  continue;
459  }
460  rditem = PageGetItemId(ctx.page, rdoffnum);
461  if (!ItemIdIsUsed(rditem))
462  report_corruption(&ctx,
463  psprintf("line pointer redirection to unused item at offset %u",
464  (unsigned) rdoffnum));
465  continue;
466  }
467 
468  /* Sanity-check the line pointer's offset and length values */
469  ctx.lp_len = ItemIdGetLength(ctx.itemid);
470  ctx.lp_off = ItemIdGetOffset(ctx.itemid);
471 
472  if (ctx.lp_off != MAXALIGN(ctx.lp_off))
473  {
474  report_corruption(&ctx,
475  psprintf("line pointer to page offset %u is not maximally aligned",
476  ctx.lp_off));
477  continue;
478  }
479  if (ctx.lp_len < MAXALIGN(SizeofHeapTupleHeader))
480  {
481  report_corruption(&ctx,
482  psprintf("line pointer length %u is less than the minimum tuple header size %u",
483  ctx.lp_len,
484  (unsigned) MAXALIGN(SizeofHeapTupleHeader)));
485  continue;
486  }
487  if (ctx.lp_off + ctx.lp_len > BLCKSZ)
488  {
489  report_corruption(&ctx,
490  psprintf("line pointer to page offset %u with length %u ends beyond maximum page offset %u",
491  ctx.lp_off,
492  ctx.lp_len,
493  (unsigned) BLCKSZ));
494  continue;
495  }
496 
497  /* It should be safe to examine the tuple's header, at least */
498  ctx.tuphdr = (HeapTupleHeader) PageGetItem(ctx.page, ctx.itemid);
499  ctx.natts = HeapTupleHeaderGetNatts(ctx.tuphdr);
500 
501  /* Ok, ready to check this next tuple */
502  check_tuple(&ctx);
503  }
504 
505  /* clean up */
506  UnlockReleaseBuffer(ctx.buffer);
507 
508  /*
509  * Check any toast pointers from the page whose lock we just released
510  */
511  if (ctx.toasted_attributes != NIL)
512  {
513  ListCell *cell;
514 
515  foreach(cell, ctx.toasted_attributes)
516  check_toasted_attribute(&ctx, lfirst(cell));
517  list_free_deep(ctx.toasted_attributes);
518  ctx.toasted_attributes = NIL;
519  }
520 
521  if (on_error_stop && ctx.is_corrupt)
522  break;
523  }
524 
525  if (vmbuffer != InvalidBuffer)
526  ReleaseBuffer(vmbuffer);
527 
528  /* Close the associated toast table and indexes, if any. */
529  if (ctx.toast_indexes)
530  toast_close_indexes(ctx.toast_indexes, ctx.num_toast_indexes,
532  if (ctx.toast_rel)
533  table_close(ctx.toast_rel, AccessShareLock);
534 
535  /* Close the main relation */
537 
538  PG_RETURN_NULL();
539 }
540 
541 /*
542  * Shared internal implementation for report_corruption and
543  * report_toast_corruption.
544  */
545 static void
548  AttrNumber attnum, char *msg)
549 {
551  bool nulls[HEAPCHECK_RELATION_COLS];
552  HeapTuple tuple;
553 
554  MemSet(values, 0, sizeof(values));
555  MemSet(nulls, 0, sizeof(nulls));
556  values[0] = Int64GetDatum(blkno);
557  values[1] = Int32GetDatum(offnum);
558  values[2] = Int32GetDatum(attnum);
559  nulls[2] = (attnum < 0);
560  values[3] = CStringGetTextDatum(msg);
561 
562  /*
563  * In principle, there is nothing to prevent a scan over a large, highly
564  * corrupted table from using work_mem worth of memory building up the
565  * tuplestore. That's ok, but if we also leak the msg argument memory
566  * until the end of the query, we could exceed work_mem by more than a
567  * trivial amount. Therefore, free the msg argument each time we are
568  * called rather than waiting for our current memory context to be freed.
569  */
570  pfree(msg);
571 
572  tuple = heap_form_tuple(tupdesc, values, nulls);
573  tuplestore_puttuple(tupstore, tuple);
574 }
575 
576 /*
577  * Record a single corruption found in the main table. The values in ctx should
578  * indicate the location of the corruption, and the msg argument should contain
579  * a human-readable description of the corruption.
580  *
581  * The msg argument is pfree'd by this function.
582  */
583 static void
585 {
587  ctx->offnum, ctx->attnum, msg);
588  ctx->is_corrupt = true;
589 }
590 
591 /*
592  * Record corruption found in the toast table. The values in ta should
593  * indicate the location in the main table where the toast pointer was
594  * encountered, and the msg argument should contain a human-readable
595  * description of the toast table corruption.
596  *
597  * As above, the msg argument is pfree'd by this function.
598  */
599 static void
601  char *msg)
602 {
604  ta->offnum, ta->attnum, msg);
605  ctx->is_corrupt = true;
606 }
607 
608 /*
609  * Construct the TupleDesc used to report messages about corruptions found
610  * while scanning the heap.
611  */
612 static TupleDesc
614 {
615  TupleDesc tupdesc;
616  AttrNumber a = 0;
617 
619  TupleDescInitEntry(tupdesc, ++a, "blkno", INT8OID, -1, 0);
620  TupleDescInitEntry(tupdesc, ++a, "offnum", INT4OID, -1, 0);
621  TupleDescInitEntry(tupdesc, ++a, "attnum", INT4OID, -1, 0);
622  TupleDescInitEntry(tupdesc, ++a, "msg", TEXTOID, -1, 0);
624 
625  return BlessTupleDesc(tupdesc);
626 }
627 
628 /*
629  * Check for tuple header corruption.
630  *
631  * Some kinds of corruption make it unsafe to check the tuple attributes, for
632  * example when the line pointer refers to a range of bytes outside the page.
633  * In such cases, we return false (not checkable) after recording appropriate
634  * corruption messages.
635  *
636  * Some other kinds of tuple header corruption confuse the question of where
637  * the tuple attributes begin, or how long the nulls bitmap is, etc., making it
638  * unreasonable to attempt to check attributes, even if all candidate answers
639  * to those questions would not result in reading past the end of the line
640  * pointer or page. In such cases, like above, we record corruption messages
641  * about the header and then return false.
642  *
643  * Other kinds of tuple header corruption do not bear on the question of
644  * whether the tuple attributes can be checked, so we record corruption
645  * messages for them but we do not return false merely because we detected
646  * them.
647  *
648  * Returns whether the tuple is sufficiently sensible to undergo visibility and
649  * attribute checks.
650  */
651 static bool
653 {
654  HeapTupleHeader tuphdr = ctx->tuphdr;
655  uint16 infomask = tuphdr->t_infomask;
656  bool result = true;
657  unsigned expected_hoff;
658 
659  if (ctx->tuphdr->t_hoff > ctx->lp_len)
660  {
661  report_corruption(ctx,
662  psprintf("data begins at offset %u beyond the tuple length %u",
663  ctx->tuphdr->t_hoff, ctx->lp_len));
664  result = false;
665  }
666 
667  if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
669  {
670  report_corruption(ctx,
671  pstrdup("multixact should not be marked committed"));
672 
673  /*
674  * This condition is clearly wrong, but it's not enough to justify
675  * skipping further checks, because we don't rely on this to determine
676  * whether the tuple is visible or to interpret other relevant header
677  * fields.
678  */
679  }
680 
681  if (infomask & HEAP_HASNULL)
682  expected_hoff = MAXALIGN(SizeofHeapTupleHeader + BITMAPLEN(ctx->natts));
683  else
684  expected_hoff = MAXALIGN(SizeofHeapTupleHeader);
685  if (ctx->tuphdr->t_hoff != expected_hoff)
686  {
687  if ((infomask & HEAP_HASNULL) && ctx->natts == 1)
688  report_corruption(ctx,
689  psprintf("tuple data should begin at byte %u, but actually begins at byte %u (1 attribute, has nulls)",
690  expected_hoff, ctx->tuphdr->t_hoff));
691  else if ((infomask & HEAP_HASNULL))
692  report_corruption(ctx,
693  psprintf("tuple data should begin at byte %u, but actually begins at byte %u (%u attributes, has nulls)",
694  expected_hoff, ctx->tuphdr->t_hoff, ctx->natts));
695  else if (ctx->natts == 1)
696  report_corruption(ctx,
697  psprintf("tuple data should begin at byte %u, but actually begins at byte %u (1 attribute, no nulls)",
698  expected_hoff, ctx->tuphdr->t_hoff));
699  else
700  report_corruption(ctx,
701  psprintf("tuple data should begin at byte %u, but actually begins at byte %u (%u attributes, no nulls)",
702  expected_hoff, ctx->tuphdr->t_hoff, ctx->natts));
703  result = false;
704  }
705 
706  return result;
707 }
708 
709 /*
710  * Checks tuple visibility so we know which further checks are safe to
711  * perform.
712  *
713  * If a tuple could have been inserted by a transaction that also added a
714  * column to the table, but which ultimately did not commit, or which has not
715  * yet committed, then the table's current TupleDesc might differ from the one
716  * used to construct this tuple, so we must not check it.
717  *
718  * As a special case, if our own transaction inserted the tuple, even if we
719  * added a column to the table, our TupleDesc should match. We could check the
720  * tuple, but choose not to do so.
721  *
722  * If a tuple has been updated or deleted, we can still read the old tuple for
723  * corruption checking purposes, as long as we are careful about concurrent
724  * vacuums. The main table tuple itself cannot be vacuumed away because we
725  * hold a buffer lock on the page, but if the deleting transaction is older
726  * than our transaction snapshot's xmin, then vacuum could remove the toast at
727  * any time, so we must not try to follow TOAST pointers.
728  *
729  * If xmin or xmax values are older than can be checked against clog, or appear
730  * to be in the future (possibly due to wrap-around), then we cannot make a
731  * determination about the visibility of the tuple, so we skip further checks.
732  *
733  * Returns true if the tuple itself should be checked, false otherwise. Sets
734  * ctx->tuple_could_be_pruned if the tuple -- and thus also any associated
735  * TOAST tuples -- are eligible for pruning.
736  */
737 static bool
739 {
740  TransactionId xmin;
741  TransactionId xvac;
742  TransactionId xmax;
743  XidCommitStatus xmin_status;
744  XidCommitStatus xvac_status;
745  XidCommitStatus xmax_status;
746  HeapTupleHeader tuphdr = ctx->tuphdr;
747 
748  ctx->tuple_could_be_pruned = true; /* have not yet proven otherwise */
749 
750  /* If xmin is normal, it should be within valid range */
751  xmin = HeapTupleHeaderGetXmin(tuphdr);
752  switch (get_xid_status(xmin, ctx, &xmin_status))
753  {
754  case XID_INVALID:
755  case XID_BOUNDS_OK:
756  break;
757  case XID_IN_FUTURE:
758  report_corruption(ctx,
759  psprintf("xmin %u equals or exceeds next valid transaction ID %u:%u",
760  xmin,
763  return false;
765  report_corruption(ctx,
766  psprintf("xmin %u precedes oldest valid transaction ID %u:%u",
767  xmin,
770  return false;
771  case XID_PRECEDES_RELMIN:
772  report_corruption(ctx,
773  psprintf("xmin %u precedes relation freeze threshold %u:%u",
774  xmin,
777  return false;
778  }
779 
780  /*
781  * Has inserting transaction committed?
782  */
783  if (!HeapTupleHeaderXminCommitted(tuphdr))
784  {
785  if (HeapTupleHeaderXminInvalid(tuphdr))
786  return false; /* inserter aborted, don't check */
787  /* Used by pre-9.0 binary upgrades */
788  else if (tuphdr->t_infomask & HEAP_MOVED_OFF)
789  {
790  xvac = HeapTupleHeaderGetXvac(tuphdr);
791 
792  switch (get_xid_status(xvac, ctx, &xvac_status))
793  {
794  case XID_INVALID:
795  report_corruption(ctx,
796  pstrdup("old-style VACUUM FULL transaction ID for moved off tuple is invalid"));
797  return false;
798  case XID_IN_FUTURE:
799  report_corruption(ctx,
800  psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple equals or exceeds next valid transaction ID %u:%u",
801  xvac,
804  return false;
805  case XID_PRECEDES_RELMIN:
806  report_corruption(ctx,
807  psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple precedes relation freeze threshold %u:%u",
808  xvac,
811  return false;
813  report_corruption(ctx,
814  psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple precedes oldest valid transaction ID %u:%u",
815  xvac,
818  return false;
819  case XID_BOUNDS_OK:
820  break;
821  }
822 
823  switch (xvac_status)
824  {
825  case XID_IS_CURRENT_XID:
826  report_corruption(ctx,
827  psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple matches our current transaction ID",
828  xvac));
829  return false;
830  case XID_IN_PROGRESS:
831  report_corruption(ctx,
832  psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple appears to be in progress",
833  xvac));
834  return false;
835 
836  case XID_COMMITTED:
837 
838  /*
839  * The tuple is dead, because the xvac transaction moved
840  * it off and committed. It's checkable, but also
841  * prunable.
842  */
843  return true;
844 
845  case XID_ABORTED:
846 
847  /*
848  * The original xmin must have committed, because the xvac
849  * transaction tried to move it later. Since xvac is
850  * aborted, whether it's still alive now depends on the
851  * status of xmax.
852  */
853  break;
854  }
855  }
856  /* Used by pre-9.0 binary upgrades */
857  else if (tuphdr->t_infomask & HEAP_MOVED_IN)
858  {
859  xvac = HeapTupleHeaderGetXvac(tuphdr);
860 
861  switch (get_xid_status(xvac, ctx, &xvac_status))
862  {
863  case XID_INVALID:
864  report_corruption(ctx,
865  pstrdup("old-style VACUUM FULL transaction ID for moved in tuple is invalid"));
866  return false;
867  case XID_IN_FUTURE:
868  report_corruption(ctx,
869  psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple equals or exceeds next valid transaction ID %u:%u",
870  xvac,
873  return false;
874  case XID_PRECEDES_RELMIN:
875  report_corruption(ctx,
876  psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple precedes relation freeze threshold %u:%u",
877  xvac,
880  return false;
882  report_corruption(ctx,
883  psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple precedes oldest valid transaction ID %u:%u",
884  xvac,
887  return false;
888  case XID_BOUNDS_OK:
889  break;
890  }
891 
892  switch (xvac_status)
893  {
894  case XID_IS_CURRENT_XID:
895  report_corruption(ctx,
896  psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple matches our current transaction ID",
897  xvac));
898  return false;
899  case XID_IN_PROGRESS:
900  report_corruption(ctx,
901  psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple appears to be in progress",
902  xvac));
903  return false;
904 
905  case XID_COMMITTED:
906 
907  /*
908  * The original xmin must have committed, because the xvac
909  * transaction moved it later. Whether it's still alive
910  * now depends on the status of xmax.
911  */
912  break;
913 
914  case XID_ABORTED:
915 
916  /*
917  * The tuple is dead, because the xvac transaction moved
918  * it off and committed. It's checkable, but also
919  * prunable.
920  */
921  return true;
922  }
923  }
924  else if (xmin_status != XID_COMMITTED)
925  {
926  /*
927  * Inserting transaction is not in progress, and not committed, so
928  * it might have changed the TupleDesc in ways we don't know
929  * about. Thus, don't try to check the tuple structure.
930  *
931  * If xmin_status happens to be XID_IS_CURRENT_XID, then in theory
932  * any such DDL changes ought to be visible to us, so perhaps we
933  * could check anyway in that case. But, for now, let's be
934  * conservative and treat this like any other uncommitted insert.
935  */
936  return false;
937  }
938  }
939 
940  /*
941  * Okay, the inserter committed, so it was good at some point. Now what
942  * about the deleting transaction?
943  */
944 
945  if (tuphdr->t_infomask & HEAP_XMAX_IS_MULTI)
946  {
947  /*
948  * xmax is a multixact, so sanity-check the MXID. Note that we do this
949  * prior to checking for HEAP_XMAX_INVALID or
950  * HEAP_XMAX_IS_LOCKED_ONLY. This might therefore complain about
951  * things that wouldn't actually be a problem during a normal scan,
952  * but eventually we're going to have to freeze, and that process will
953  * ignore hint bits.
954  *
955  * Even if the MXID is out of range, we still know that the original
956  * insert committed, so we can check the tuple itself. However, we
957  * can't rule out the possibility that this tuple is dead, so don't
958  * clear ctx->tuple_could_be_pruned. Possibly we should go ahead and
959  * clear that flag anyway if HEAP_XMAX_INVALID is set or if
960  * HEAP_XMAX_IS_LOCKED_ONLY is true, but for now we err on the side of
961  * avoiding possibly-bogus complaints about missing TOAST entries.
962  */
963  xmax = HeapTupleHeaderGetRawXmax(tuphdr);
964  switch (check_mxid_valid_in_rel(xmax, ctx))
965  {
966  case XID_INVALID:
967  report_corruption(ctx,
968  pstrdup("multitransaction ID is invalid"));
969  return true;
970  case XID_PRECEDES_RELMIN:
971  report_corruption(ctx,
972  psprintf("multitransaction ID %u precedes relation minimum multitransaction ID threshold %u",
973  xmax, ctx->relminmxid));
974  return true;
976  report_corruption(ctx,
977  psprintf("multitransaction ID %u precedes oldest valid multitransaction ID threshold %u",
978  xmax, ctx->oldest_mxact));
979  return true;
980  case XID_IN_FUTURE:
981  report_corruption(ctx,
982  psprintf("multitransaction ID %u equals or exceeds next valid multitransaction ID %u",
983  xmax,
984  ctx->next_mxact));
985  return true;
986  case XID_BOUNDS_OK:
987  break;
988  }
989  }
990 
991  if (tuphdr->t_infomask & HEAP_XMAX_INVALID)
992  {
993  /*
994  * This tuple is live. A concurrently running transaction could
995  * delete it before we get around to checking the toast, but any such
996  * running transaction is surely not less than our safe_xmin, so the
997  * toast cannot be vacuumed out from under us.
998  */
999  ctx->tuple_could_be_pruned = false;
1000  return true;
1001  }
1002 
1003  if (HEAP_XMAX_IS_LOCKED_ONLY(tuphdr->t_infomask))
1004  {
1005  /*
1006  * "Deleting" xact really only locked it, so the tuple is live in any
1007  * case. As above, a concurrently running transaction could delete
1008  * it, but it cannot be vacuumed out from under us.
1009  */
1010  ctx->tuple_could_be_pruned = false;
1011  return true;
1012  }
1013 
1014  if (tuphdr->t_infomask & HEAP_XMAX_IS_MULTI)
1015  {
1016  /*
1017  * We already checked above that this multixact is within limits for
1018  * this table. Now check the update xid from this multixact.
1019  */
1020  xmax = HeapTupleGetUpdateXid(tuphdr);
1021  switch (get_xid_status(xmax, ctx, &xmax_status))
1022  {
1023  case XID_INVALID:
1024  /* not LOCKED_ONLY, so it has to have an xmax */
1025  report_corruption(ctx,
1026  pstrdup("update xid is invalid"));
1027  return true;
1028  case XID_IN_FUTURE:
1029  report_corruption(ctx,
1030  psprintf("update xid %u equals or exceeds next valid transaction ID %u:%u",
1031  xmax,
1034  return true;
1035  case XID_PRECEDES_RELMIN:
1036  report_corruption(ctx,
1037  psprintf("update xid %u precedes relation freeze threshold %u:%u",
1038  xmax,
1041  return true;
1043  report_corruption(ctx,
1044  psprintf("update xid %u precedes oldest valid transaction ID %u:%u",
1045  xmax,
1048  return true;
1049  case XID_BOUNDS_OK:
1050  break;
1051  }
1052 
1053  switch (xmax_status)
1054  {
1055  case XID_IS_CURRENT_XID:
1056  case XID_IN_PROGRESS:
1057 
1058  /*
1059  * The delete is in progress, so it cannot be visible to our
1060  * snapshot.
1061  */
1062  ctx->tuple_could_be_pruned = false;
1063  break;
1064  case XID_COMMITTED:
1065 
1066  /*
1067  * The delete committed. Whether the toast can be vacuumed
1068  * away depends on how old the deleting transaction is.
1069  */
1071  ctx->safe_xmin);
1072  break;
1073  case XID_ABORTED:
1074 
1075  /*
1076  * The delete aborted or crashed. The tuple is still live.
1077  */
1078  ctx->tuple_could_be_pruned = false;
1079  break;
1080  }
1081 
1082  /* Tuple itself is checkable even if it's dead. */
1083  return true;
1084  }
1085 
1086  /* xmax is an XID, not a MXID. Sanity check it. */
1087  xmax = HeapTupleHeaderGetRawXmax(tuphdr);
1088  switch (get_xid_status(xmax, ctx, &xmax_status))
1089  {
1090  case XID_IN_FUTURE:
1091  report_corruption(ctx,
1092  psprintf("xmax %u equals or exceeds next valid transaction ID %u:%u",
1093  xmax,
1096  return false; /* corrupt */
1097  case XID_PRECEDES_RELMIN:
1098  report_corruption(ctx,
1099  psprintf("xmax %u precedes relation freeze threshold %u:%u",
1100  xmax,
1103  return false; /* corrupt */
1105  report_corruption(ctx,
1106  psprintf("xmax %u precedes oldest valid transaction ID %u:%u",
1107  xmax,
1110  return false; /* corrupt */
1111  case XID_BOUNDS_OK:
1112  case XID_INVALID:
1113  break;
1114  }
1115 
1116  /*
1117  * Whether the toast can be vacuumed away depends on how old the deleting
1118  * transaction is.
1119  */
1120  switch (xmax_status)
1121  {
1122  case XID_IS_CURRENT_XID:
1123  case XID_IN_PROGRESS:
1124 
1125  /*
1126  * The delete is in progress, so it cannot be visible to our
1127  * snapshot.
1128  */
1129  ctx->tuple_could_be_pruned = false;
1130  break;
1131 
1132  case XID_COMMITTED:
1133 
1134  /*
1135  * The delete committed. Whether the toast can be vacuumed away
1136  * depends on how old the deleting transaction is.
1137  */
1139  ctx->safe_xmin);
1140  break;
1141 
1142  case XID_ABORTED:
1143 
1144  /*
1145  * The delete aborted or crashed. The tuple is still live.
1146  */
1147  ctx->tuple_could_be_pruned = false;
1148  break;
1149  }
1150 
1151  /* Tuple itself is checkable even if it's dead. */
1152  return true;
1153 }
1154 
1155 
1156 /*
1157  * Check the current toast tuple against the state tracked in ctx, recording
1158  * any corruption found in ctx->tupstore.
1159  *
1160  * This is not equivalent to running verify_heapam on the toast table itself,
1161  * and is not hardened against corruption of the toast table. Rather, when
1162  * validating a toasted attribute in the main table, the sequence of toast
1163  * tuples that store the toasted value are retrieved and checked in order, with
1164  * each toast tuple being checked against where we are in the sequence, as well
1165  * as each toast tuple having its varlena structure sanity checked.
1166  *
1167  * On entry, *expected_chunk_seq should be the chunk_seq value that we expect
1168  * to find in toasttup. On exit, it will be updated to the value the next call
1169  * to this function should expect to see.
1170  */
1171 static void
1173  ToastedAttribute *ta, int32 *expected_chunk_seq,
1174  uint32 extsize)
1175 {
1176  int32 chunk_seq;
1177  int32 last_chunk_seq = (extsize - 1) / TOAST_MAX_CHUNK_SIZE;
1178  Pointer chunk;
1179  bool isnull;
1180  int32 chunksize;
1181  int32 expected_size;
1182 
1183  /* Sanity-check the sequence number. */
1184  chunk_seq = DatumGetInt32(fastgetattr(toasttup, 2,
1185  ctx->toast_rel->rd_att, &isnull));
1186  if (isnull)
1187  {
1188  report_toast_corruption(ctx, ta,
1189  psprintf("toast value %u has toast chunk with null sequence number",
1190  ta->toast_pointer.va_valueid));
1191  return;
1192  }
1193  if (chunk_seq != *expected_chunk_seq)
1194  {
1195  /* Either the TOAST index is corrupt, or we don't have all chunks. */
1196  report_toast_corruption(ctx, ta,
1197  psprintf("toast value %u index scan returned chunk %d when expecting chunk %d",
1199  chunk_seq, *expected_chunk_seq));
1200  }
1201  *expected_chunk_seq = chunk_seq + 1;
1202 
1203  /* Sanity-check the chunk data. */
1204  chunk = DatumGetPointer(fastgetattr(toasttup, 3,
1205  ctx->toast_rel->rd_att, &isnull));
1206  if (isnull)
1207  {
1208  report_toast_corruption(ctx, ta,
1209  psprintf("toast value %u chunk %d has null data",
1211  chunk_seq));
1212  return;
1213  }
1214  if (!VARATT_IS_EXTENDED(chunk))
1215  chunksize = VARSIZE(chunk) - VARHDRSZ;
1216  else if (VARATT_IS_SHORT(chunk))
1217  {
1218  /*
1219  * could happen due to heap_form_tuple doing its thing
1220  */
1221  chunksize = VARSIZE_SHORT(chunk) - VARHDRSZ_SHORT;
1222  }
1223  else
1224  {
1225  /* should never happen */
1226  uint32 header = ((varattrib_4b *) chunk)->va_4byte.va_header;
1227 
1228  report_toast_corruption(ctx, ta,
1229  psprintf("toast value %u chunk %d has invalid varlena header %0x",
1231  chunk_seq, header));
1232  return;
1233  }
1234 
1235  /*
1236  * Some checks on the data we've found
1237  */
1238  if (chunk_seq > last_chunk_seq)
1239  {
1240  report_toast_corruption(ctx, ta,
1241  psprintf("toast value %u chunk %d follows last expected chunk %d",
1243  chunk_seq, last_chunk_seq));
1244  return;
1245  }
1246 
1247  expected_size = chunk_seq < last_chunk_seq ? TOAST_MAX_CHUNK_SIZE
1248  : extsize - (last_chunk_seq * TOAST_MAX_CHUNK_SIZE);
1249 
1250  if (chunksize != expected_size)
1251  report_toast_corruption(ctx, ta,
1252  psprintf("toast value %u chunk %d has size %u, but expected size %u",
1254  chunk_seq, chunksize, expected_size));
1255 }
1256 
1257 /*
1258  * Check the current attribute as tracked in ctx, recording any corruption
1259  * found in ctx->tupstore.
1260  *
1261  * This function follows the logic performed by heap_deform_tuple(), and in the
1262  * case of a toasted value, optionally stores the toast pointer so later it can
1263  * be checked following the logic of detoast_external_attr(), checking for any
1264  * conditions that would result in either of those functions Asserting or
1265  * crashing the backend. The checks performed by Asserts present in those two
1266  * functions are also performed here and in check_toasted_attribute. In cases
1267  * where those two functions are a bit cavalier in their assumptions about data
1268  * being correct, we perform additional checks not present in either of those
1269  * two functions. Where some condition is checked in both of those functions,
1270  * we perform it here twice, as we parallel the logical flow of those two
1271  * functions. The presence of duplicate checks seems a reasonable price to pay
1272  * for keeping this code tightly coupled with the code it protects.
1273  *
1274  * Returns true if the tuple attribute is sane enough for processing to
1275  * continue on to the next attribute, false otherwise.
1276  */
1277 static bool
1279 {
1280  Datum attdatum;
1281  struct varlena *attr;
1282  char *tp; /* pointer to the tuple data */
1283  uint16 infomask;
1284  Form_pg_attribute thisatt;
1285  struct varatt_external toast_pointer;
1286 
1287  infomask = ctx->tuphdr->t_infomask;
1288  thisatt = TupleDescAttr(RelationGetDescr(ctx->rel), ctx->attnum);
1289 
1290  tp = (char *) ctx->tuphdr + ctx->tuphdr->t_hoff;
1291 
1292  if (ctx->tuphdr->t_hoff + ctx->offset > ctx->lp_len)
1293  {
1294  report_corruption(ctx,
1295  psprintf("attribute with length %u starts at offset %u beyond total tuple length %u",
1296  thisatt->attlen,
1297  ctx->tuphdr->t_hoff + ctx->offset,
1298  ctx->lp_len));
1299  return false;
1300  }
1301 
1302  /* Skip null values */
1303  if (infomask & HEAP_HASNULL && att_isnull(ctx->attnum, ctx->tuphdr->t_bits))
1304  return true;
1305 
1306  /* Skip non-varlena values, but update offset first */
1307  if (thisatt->attlen != -1)
1308  {
1309  ctx->offset = att_align_nominal(ctx->offset, thisatt->attalign);
1310  ctx->offset = att_addlength_pointer(ctx->offset, thisatt->attlen,
1311  tp + ctx->offset);
1312  if (ctx->tuphdr->t_hoff + ctx->offset > ctx->lp_len)
1313  {
1314  report_corruption(ctx,
1315  psprintf("attribute with length %u ends at offset %u beyond total tuple length %u",
1316  thisatt->attlen,
1317  ctx->tuphdr->t_hoff + ctx->offset,
1318  ctx->lp_len));
1319  return false;
1320  }
1321  return true;
1322  }
1323 
1324  /* Ok, we're looking at a varlena attribute. */
1325  ctx->offset = att_align_pointer(ctx->offset, thisatt->attalign, -1,
1326  tp + ctx->offset);
1327 
1328  /* Get the (possibly corrupt) varlena datum */
1329  attdatum = fetchatt(thisatt, tp + ctx->offset);
1330 
1331  /*
1332  * We have the datum, but we cannot decode it carelessly, as it may still
1333  * be corrupt.
1334  */
1335 
1336  /*
1337  * Check that VARTAG_SIZE won't hit a TrapMacro on a corrupt va_tag before
1338  * risking a call into att_addlength_pointer
1339  */
1340  if (VARATT_IS_EXTERNAL(tp + ctx->offset))
1341  {
1342  uint8 va_tag = VARTAG_EXTERNAL(tp + ctx->offset);
1343 
1344  if (va_tag != VARTAG_ONDISK)
1345  {
1346  report_corruption(ctx,
1347  psprintf("toasted attribute has unexpected TOAST tag %u",
1348  va_tag));
1349  /* We can't know where the next attribute begins */
1350  return false;
1351  }
1352  }
1353 
1354  /* Ok, should be safe now */
1355  ctx->offset = att_addlength_pointer(ctx->offset, thisatt->attlen,
1356  tp + ctx->offset);
1357 
1358  if (ctx->tuphdr->t_hoff + ctx->offset > ctx->lp_len)
1359  {
1360  report_corruption(ctx,
1361  psprintf("attribute with length %u ends at offset %u beyond total tuple length %u",
1362  thisatt->attlen,
1363  ctx->tuphdr->t_hoff + ctx->offset,
1364  ctx->lp_len));
1365 
1366  return false;
1367  }
1368 
1369  /*
1370  * heap_deform_tuple would be done with this attribute at this point,
1371  * having stored it in values[], and would continue to the next attribute.
1372  * We go further, because we need to check if the toast datum is corrupt.
1373  */
1374 
1375  attr = (struct varlena *) DatumGetPointer(attdatum);
1376 
1377  /*
1378  * Now we follow the logic of detoast_external_attr(), with the same
1379  * caveats about being paranoid about corruption.
1380  */
1381 
1382  /* Skip values that are not external */
1383  if (!VARATT_IS_EXTERNAL(attr))
1384  return true;
1385 
1386  /* It is external, and we're looking at a page on disk */
1387 
1388  /*
1389  * Must copy attr into toast_pointer for alignment considerations
1390  */
1391  VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
1392 
1393  /* The tuple header better claim to contain toasted values */
1394  if (!(infomask & HEAP_HASEXTERNAL))
1395  {
1396  report_corruption(ctx,
1397  psprintf("toast value %u is external but tuple header flag HEAP_HASEXTERNAL not set",
1398  toast_pointer.va_valueid));
1399  return true;
1400  }
1401 
1402  /* The relation better have a toast table */
1403  if (!ctx->rel->rd_rel->reltoastrelid)
1404  {
1405  report_corruption(ctx,
1406  psprintf("toast value %u is external but relation has no toast relation",
1407  toast_pointer.va_valueid));
1408  return true;
1409  }
1410 
1411  /* If we were told to skip toast checking, then we're done. */
1412  if (ctx->toast_rel == NULL)
1413  return true;
1414 
1415  /*
1416  * If this tuple is eligible to be pruned, we cannot check the toast.
1417  * Otherwise, we push a copy of the toast tuple so we can check it after
1418  * releasing the main table buffer lock.
1419  */
1420  if (!ctx->tuple_could_be_pruned)
1421  {
1422  ToastedAttribute *ta;
1423 
1424  ta = (ToastedAttribute *) palloc0(sizeof(ToastedAttribute));
1425 
1427  ta->blkno = ctx->blkno;
1428  ta->offnum = ctx->offnum;
1429  ta->attnum = ctx->attnum;
1431  }
1432 
1433  return true;
1434 }
1435 
1436 /*
1437  * For each attribute collected in ctx->toasted_attributes, look up the value
1438  * in the toast table and perform checks on it. This function should only be
1439  * called on toast pointers which cannot be vacuumed away during our
1440  * processing.
1441  */
1442 static void
1444 {
1445  SnapshotData SnapshotToast;
1446  ScanKeyData toastkey;
1447  SysScanDesc toastscan;
1448  bool found_toasttup;
1449  HeapTuple toasttup;
1450  uint32 extsize;
1451  int32 expected_chunk_seq = 0;
1452  int32 last_chunk_seq;
1453 
1455  last_chunk_seq = (extsize - 1) / TOAST_MAX_CHUNK_SIZE;
1456 
1457  /*
1458  * Setup a scan key to find chunks in toast table with matching va_valueid
1459  */
1460  ScanKeyInit(&toastkey,
1461  (AttrNumber) 1,
1462  BTEqualStrategyNumber, F_OIDEQ,
1464 
1465  /*
1466  * Check if any chunks for this toasted object exist in the toast table,
1467  * accessible via the index.
1468  */
1469  init_toast_snapshot(&SnapshotToast);
1470  toastscan = systable_beginscan_ordered(ctx->toast_rel,
1471  ctx->valid_toast_index,
1472  &SnapshotToast, 1,
1473  &toastkey);
1474  found_toasttup = false;
1475  while ((toasttup =
1476  systable_getnext_ordered(toastscan,
1477  ForwardScanDirection)) != NULL)
1478  {
1479  found_toasttup = true;
1480  check_toast_tuple(toasttup, ctx, ta, &expected_chunk_seq, extsize);
1481  }
1482  systable_endscan_ordered(toastscan);
1483 
1484  if (!found_toasttup)
1485  report_toast_corruption(ctx, ta,
1486  psprintf("toast value %u not found in toast table",
1487  ta->toast_pointer.va_valueid));
1488  else if (expected_chunk_seq <= last_chunk_seq)
1489  report_toast_corruption(ctx, ta,
1490  psprintf("toast value %u was expected to end at chunk %d, but ended while expecting chunk %d",
1492  last_chunk_seq, expected_chunk_seq));
1493 }
1494 
1495 /*
1496  * Check the current tuple as tracked in ctx, recording any corruption found in
1497  * ctx->tupstore.
1498  */
1499 static void
1501 {
1502  /*
1503  * Check various forms of tuple header corruption, and if the header is
1504  * too corrupt, do not continue with other checks.
1505  */
1506  if (!check_tuple_header(ctx))
1507  return;
1508 
1509  /*
1510  * Check tuple visibility. If the inserting transaction aborted, we
1511  * cannot assume our relation description matches the tuple structure, and
1512  * therefore cannot check it.
1513  */
1514  if (!check_tuple_visibility(ctx))
1515  return;
1516 
1517  /*
1518  * The tuple is visible, so it must be compatible with the current version
1519  * of the relation descriptor. It might have fewer columns than are
1520  * present in the relation descriptor, but it cannot have more.
1521  */
1522  if (RelationGetDescr(ctx->rel)->natts < ctx->natts)
1523  {
1524  report_corruption(ctx,
1525  psprintf("number of attributes %u exceeds maximum expected for table %u",
1526  ctx->natts,
1527  RelationGetDescr(ctx->rel)->natts));
1528  return;
1529  }
1530 
1531  /*
1532  * Check each attribute unless we hit corruption that confuses what to do
1533  * next, at which point we abort further attribute checks for this tuple.
1534  * Note that we don't abort for all types of corruption, only for those
1535  * types where we don't know how to continue. We also don't abort the
1536  * checking of toasted attributes collected from the tuple prior to
1537  * aborting. Those will still be checked later along with other toasted
1538  * attributes collected from the page.
1539  */
1540  ctx->offset = 0;
1541  for (ctx->attnum = 0; ctx->attnum < ctx->natts; ctx->attnum++)
1542  if (!check_tuple_attribute(ctx))
1543  break; /* cannot continue */
1544 
1545  /* revert attnum to -1 until we again examine individual attributes */
1546  ctx->attnum = -1;
1547 }
1548 
1549 /*
1550  * Convert a TransactionId into a FullTransactionId using our cached values of
1551  * the valid transaction ID range. It is the caller's responsibility to have
1552  * already updated the cached values, if necessary.
1553  */
1554 static FullTransactionId
1556 {
1557  uint32 epoch;
1558 
1559  if (!TransactionIdIsNormal(xid))
1560  return FullTransactionIdFromEpochAndXid(0, xid);
1561  epoch = EpochFromFullTransactionId(ctx->next_fxid);
1562  if (xid > ctx->next_xid)
1563  epoch--;
1564  return FullTransactionIdFromEpochAndXid(epoch, xid);
1565 }
1566 
1567 /*
1568  * Update our cached range of valid transaction IDs.
1569  */
1570 static void
1572 {
1573  /* Make cached copies */
1574  LWLockAcquire(XidGenLock, LW_SHARED);
1577  LWLockRelease(XidGenLock);
1578 
1579  /* And compute alternate versions of the same */
1582 }
1583 
1584 /*
1585  * Update our cached range of valid multitransaction IDs.
1586  */
1587 static void
1589 {
1591 }
1592 
1593 /*
1594  * Return whether the given FullTransactionId is within our cached valid
1595  * transaction ID range.
1596  */
1597 static inline bool
1599 {
1600  return (FullTransactionIdPrecedesOrEquals(ctx->oldest_fxid, fxid) &&
1601  FullTransactionIdPrecedes(fxid, ctx->next_fxid));
1602 }
1603 
1604 /*
1605  * Checks whether a multitransaction ID is in the cached valid range, returning
1606  * the nature of the range violation, if any.
1607  */
1608 static XidBoundsViolation
1610 {
1611  if (!TransactionIdIsValid(mxid))
1612  return XID_INVALID;
1613  if (MultiXactIdPrecedes(mxid, ctx->relminmxid))
1614  return XID_PRECEDES_RELMIN;
1615  if (MultiXactIdPrecedes(mxid, ctx->oldest_mxact))
1616  return XID_PRECEDES_CLUSTERMIN;
1617  if (MultiXactIdPrecedesOrEquals(ctx->next_mxact, mxid))
1618  return XID_IN_FUTURE;
1619  return XID_BOUNDS_OK;
1620 }
1621 
1622 /*
1623  * Checks whether the given mxid is valid to appear in the heap being checked,
1624  * returning the nature of the range violation, if any.
1625  *
1626  * This function attempts to return quickly by caching the known valid mxid
1627  * range in ctx. Callers should already have performed the initial setup of
1628  * the cache prior to the first call to this function.
1629  */
1630 static XidBoundsViolation
1632 {
1633  XidBoundsViolation result;
1634 
1635  result = check_mxid_in_range(mxid, ctx);
1636  if (result == XID_BOUNDS_OK)
1637  return XID_BOUNDS_OK;
1638 
1639  /* The range may have advanced. Recheck. */
1641  return check_mxid_in_range(mxid, ctx);
1642 }
1643 
1644 /*
1645  * Checks whether the given transaction ID is (or was recently) valid to appear
1646  * in the heap being checked, or whether it is too old or too new to appear in
1647  * the relation, returning information about the nature of the bounds violation.
1648  *
1649  * We cache the range of valid transaction IDs. If xid is in that range, we
1650  * conclude that it is valid, even though concurrent changes to the table might
1651  * invalidate it under certain corrupt conditions. (For example, if the table
1652  * contains corrupt all-frozen bits, a concurrent vacuum might skip the page(s)
1653  * containing the xid and then truncate clog and advance the relfrozenxid
1654  * beyond xid.) Reporting the xid as valid under such conditions seems
1655  * acceptable, since if we had checked it earlier in our scan it would have
1656  * truly been valid at that time.
1657  *
1658  * If the status argument is not NULL, and if and only if the transaction ID
1659  * appears to be valid in this relation, the status argument will be set with
1660  * the commit status of the transaction ID.
1661  */
1662 static XidBoundsViolation
1665 {
1666  FullTransactionId fxid;
1667  FullTransactionId clog_horizon;
1668 
1669  /* Quick check for special xids */
1670  if (!TransactionIdIsValid(xid))
1671  return XID_INVALID;
1672  else if (xid == BootstrapTransactionId || xid == FrozenTransactionId)
1673  {
1674  if (status != NULL)
1675  *status = XID_COMMITTED;
1676  return XID_BOUNDS_OK;
1677  }
1678 
1679  /* Check if the xid is within bounds */
1680  fxid = FullTransactionIdFromXidAndCtx(xid, ctx);
1681  if (!fxid_in_cached_range(fxid, ctx))
1682  {
1683  /*
1684  * We may have been checking against stale values. Update the cached
1685  * range to be sure, and since we relied on the cached range when we
1686  * performed the full xid conversion, reconvert.
1687  */
1689  fxid = FullTransactionIdFromXidAndCtx(xid, ctx);
1690  }
1691 
1693  return XID_IN_FUTURE;
1694  if (FullTransactionIdPrecedes(fxid, ctx->oldest_fxid))
1695  return XID_PRECEDES_CLUSTERMIN;
1696  if (FullTransactionIdPrecedes(fxid, ctx->relfrozenfxid))
1697  return XID_PRECEDES_RELMIN;
1698 
1699  /* Early return if the caller does not request clog checking */
1700  if (status == NULL)
1701  return XID_BOUNDS_OK;
1702 
1703  /* Early return if we just checked this xid in a prior call */
1704  if (xid == ctx->cached_xid)
1705  {
1706  *status = ctx->cached_status;
1707  return XID_BOUNDS_OK;
1708  }
1709 
1710  *status = XID_COMMITTED;
1711  LWLockAcquire(XactTruncationLock, LW_SHARED);
1712  clog_horizon =
1714  ctx);
1715  if (FullTransactionIdPrecedesOrEquals(clog_horizon, fxid))
1716  {
1718  *status = XID_IS_CURRENT_XID;
1719  else if (TransactionIdIsInProgress(xid))
1720  *status = XID_IN_PROGRESS;
1721  else if (TransactionIdDidCommit(xid))
1722  *status = XID_COMMITTED;
1723  else
1724  *status = XID_ABORTED;
1725  }
1726  LWLockRelease(XactTruncationLock);
1727  ctx->cached_xid = xid;
1728  ctx->cached_status = *status;
1729  return XID_BOUNDS_OK;
1730 }
BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype)
Definition: freelist.c:542
int errdetail_relkind_not_supported(char relkind)
Definition: pg_class.c:24
#define NIL
Definition: pg_list.h:65
static void check_toasted_attribute(HeapCheckContext *ctx, ToastedAttribute *ta)
BlockNumber blkno
TransactionId oldest_xid
Definition: verify_heapam.c:86
#define SizeofHeapTupleHeader
Definition: htup_details.h:184
FullTransactionId oldest_fxid
Definition: verify_heapam.c:87
#define IsA(nodeptr, _type_)
Definition: nodes.h:590
void table_close(Relation relation, LOCKMODE lockmode)
Definition: table.c:167
int errhint(const char *fmt,...)
Definition: elog.c:1156
#define fastgetattr(tup, attnum, tupleDesc, isnull)
Definition: htup_details.h:711
#define TOAST_MAX_CHUNK_SIZE
Definition: heaptoast.h:84
#define att_align_nominal(cur_offset, attalign)
Definition: tupmacs.h:148
#define ItemIdIsRedirected(itemId)
Definition: itemid.h:106
HeapTupleHeader tuphdr
uint32 TransactionId
Definition: c.h:587
bits8 t_bits[FLEXIBLE_ARRAY_MEMBER]
Definition: htup_details.h:177
TupleDesc CreateTemplateTupleDesc(int natts)
Definition: tupdesc.c:45
#define DatumGetInt32(X)
Definition: postgres.h:516
BufferAccessStrategy bstrategy
#define RelationGetDescr(relation)
Definition: rel.h:503
bool TransactionIdIsCurrentTransactionId(TransactionId xid)
Definition: xact.c:869
struct varatt_external toast_pointer
Definition: verify_heapam.c:68
#define VARHDRSZ_SHORT
Definition: postgres.h:292
#define VARSIZE(PTR)
Definition: postgres.h:316
#define VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr)
Definition: detoast.h:22
bool TransactionIdIsInProgress(TransactionId xid)
Definition: procarray.c:1359
#define att_isnull(ATT, BITS)
Definition: tupmacs.h:25
TransactionId HeapTupleGetUpdateXid(HeapTupleHeader tuple)
Definition: heapam.c:6816
HeapTupleHeaderData * HeapTupleHeader
Definition: htup.h:23
void init_toast_snapshot(Snapshot toast_snapshot)
#define HEAPCHECK_RELATION_COLS
Definition: verify_heapam.c:31
#define TupleDescAttr(tupdesc, i)
Definition: tupdesc.h:92
#define VARHDRSZ
Definition: c.h:627
#define ItemIdGetRedirect(itemId)
Definition: itemid.h:78
FullTransactionId next_fxid
Definition: verify_heapam.c:84
MultiXactId oldest_mxact
Definition: verify_heapam.c:96
#define VISIBILITYMAP_ALL_FROZEN
Definition: visibilitymap.h:27
char * pstrdup(const char *in)
Definition: mcxt.c:1299
char * psprintf(const char *fmt,...)
Definition: psprintf.c:46
BlockNumber blkno
Definition: verify_heapam.c:69
Datum verify_heapam(PG_FUNCTION_ARGS)
Buffer ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum, ReadBufferMode mode, BufferAccessStrategy strategy)
Definition: bufmgr.c:741
static const struct exclude_list_item skip[]
Definition: pg_checksums.c:116
#define ItemIdIsUsed(itemId)
Definition: itemid.h:92
static MemoryContext MemoryContextSwitchTo(MemoryContext context)
Definition: palloc.h:109
unsigned char uint8
Definition: c.h:439
#define AccessShareLock
Definition: lockdefs.h:36
#define InvalidBuffer
Definition: buf.h:25
TransactionId cached_xid
TransactionId oldestXid
Definition: transam.h:222
int errcode(int sqlerrcode)
Definition: elog.c:698
static void report_corruption(HeapCheckContext *ctx, char *msg)
static bool check_tuple_header(HeapCheckContext *ctx)
#define MemSet(start, val, len)
Definition: c.h:1008
uint32 BlockNumber
Definition: block.h:31
void ReleaseBuffer(Buffer buffer)
Definition: bufmgr.c:3772
#define BITMAPLEN(NATTS)
Definition: htup_details.h:546
#define HeapTupleHeaderXminInvalid(tup)
Definition: htup_details.h:329
OffsetNumber offnum
Definition: verify_heapam.c:70
#define HEAP_XMAX_COMMITTED
Definition: htup_details.h:206
TransactionId safe_xmin
Definition: verify_heapam.c:89
HeapTuple heap_form_tuple(TupleDesc tupleDescriptor, Datum *values, bool *isnull)
Definition: heaptuple.c:1020
#define PG_GETARG_BOOL(n)
Definition: fmgr.h:274
int pg_strcasecmp(const char *s1, const char *s2)
Definition: pgstrcasecmp.c:36
AttrNumber attnum
bool TransactionIdDidCommit(TransactionId transactionId)
Definition: transam.c:125
HeapTuple systable_getnext_ordered(SysScanDesc sysscan, ScanDirection direction)
Definition: genam.c:707
PG_FUNCTION_INFO_V1(verify_heapam)
Relation valid_toast_index
Form_pg_class rd_rel
Definition: rel.h:109
unsigned int Oid
Definition: postgres_ext.h:31
static int fb(int x)
Definition: preproc-init.c:92
#define VARTAG_EXTERNAL(PTR)
Definition: postgres.h:321
#define ItemIdIsDead(itemId)
Definition: itemid.h:113
Snapshot GetTransactionSnapshot(void)
Definition: snapmgr.c:250
FullTransactionId nextXid
Definition: transam.h:220
#define PageGetMaxOffsetNumber(page)
Definition: bufpage.h:357
void list_free_deep(List *list)
Definition: list.c:1405
#define fetchatt(A, T)
Definition: tupmacs.h:41
MultiXactId next_mxact
Definition: verify_heapam.c:95
signed int int32
Definition: c.h:429
#define PG_GETARG_TEXT_PP(n)
Definition: fmgr.h:309
#define XidFromFullTransactionId(x)
Definition: transam.h:48
uint16 OffsetNumber
Definition: off.h:24
void LWLockRelease(LWLock *lock)
Definition: lwlock.c:1803
static void update_cached_xid_range(HeapCheckContext *ctx)
AttrNumber attnum
Definition: verify_heapam.c:71
#define VARATT_IS_EXTERNAL(PTR)
Definition: postgres.h:326
static void check_toast_tuple(HeapTuple toasttup, HeapCheckContext *ctx, ToastedAttribute *ta, int32 *expected_chunk_seq, uint32 extsize)
#define FullTransactionIdPrecedesOrEquals(a, b)
Definition: transam.h:52
#define HeapTupleHeaderGetRawXmax(tup)
Definition: htup_details.h:375
unsigned short uint16
Definition: c.h:440
void pfree(void *pointer)
Definition: mcxt.c:1169
#define ItemIdGetLength(itemId)
Definition: itemid.h:59
char * Pointer
Definition: c.h:418
#define HEAP_HASNULL
Definition: htup_details.h:189
void UnlockReleaseBuffer(Buffer buffer)
Definition: bufmgr.c:3795
#define ObjectIdGetDatum(X)
Definition: postgres.h:551
#define ERROR
Definition: elog.h:46
TransactionId relfrozenxid
static bool check_tuple_attribute(HeapCheckContext *ctx)
#define VARATT_IS_SHORT(PTR)
Definition: postgres.h:339
#define HEAP_XMAX_INVALID
Definition: htup_details.h:207
#define HeapTupleHeaderGetNatts(tup)
Definition: htup_details.h:530
Relation relation_open(Oid relationId, LOCKMODE lockmode)
Definition: relation.c:48
static void report_corruption_internal(Tuplestorestate *tupstore, TupleDesc tupdesc, BlockNumber blkno, OffsetNumber offnum, AttrNumber attnum, char *msg)
void tuplestore_puttuple(Tuplestorestate *state, HeapTuple tuple)
Definition: tuplestore.c:730
TupleDesc BlessTupleDesc(TupleDesc tupdesc)
Definition: execTuples.c:2082
#define BootstrapTransactionId
Definition: transam.h:32
#define HeapTupleHeaderXminCommitted(tup)
Definition: htup_details.h:324
#define PG_GETARG_OID(n)
Definition: fmgr.h:275
#define FirstOffsetNumber
Definition: off.h:27
TransactionId oldestClogXid
Definition: transam.h:253
static XidBoundsViolation get_xid_status(TransactionId xid, HeapCheckContext *ctx, XidCommitStatus *status)
VariableCache ShmemVariableCache
Definition: varsup.c:34
void toast_close_indexes(Relation *toastidxs, int num_indexes, LOCKMODE lock)
#define InvalidTransactionId
Definition: transam.h:31
static bool check_tuple_visibility(HeapCheckContext *ctx)
#define HeapTupleHeaderGetXvac(tup)
Definition: htup_details.h:415
#define RelationGetRelationName(relation)
Definition: rel.h:511
static XidBoundsViolation check_mxid_in_range(MultiXactId mxid, HeapCheckContext *ctx)
FormData_pg_attribute * Form_pg_attribute
Definition: pg_attribute.h:207
unsigned int uint32
Definition: c.h:441
#define ItemIdGetOffset(itemId)
Definition: itemid.h:65
TransactionId xmin
Definition: snapshot.h:157
#define HEAP_MOVED_IN
Definition: htup_details.h:213
static void report_toast_corruption(HeapCheckContext *ctx, ToastedAttribute *ta, char *msg)
Datum Int64GetDatum(int64 X)
Definition: fmgr.c:1697
void TupleDescInitEntry(TupleDesc desc, AttrNumber attributeNumber, const char *attributeName, Oid oidtypeid, int32 typmod, int attdim)
Definition: tupdesc.c:583
static void update_cached_mxid_range(HeapCheckContext *ctx)
#define BufferGetPage(buffer)
Definition: bufmgr.h:169
#define att_addlength_pointer(cur_offset, attlen, attptr)
Definition: tupmacs.h:176
static void check_tuple(HeapCheckContext *ctx)
bool TransactionIdPrecedes(TransactionId id1, TransactionId id2)
Definition: transam.c:300
#define VARSIZE_SHORT(PTR)
Definition: postgres.h:318
OffsetNumber offnum
List * lappend(List *list, void *datum)
Definition: list.c:336
#define PageGetItemId(page, offsetNumber)
Definition: bufpage.h:235
Tuplestorestate * tuplestore_begin_heap(bool randomAccess, bool interXact, int maxKBytes)
Definition: tuplestore.c:318
int toast_open_indexes(Relation toastrel, LOCKMODE lock, Relation **toastidxs, int *num_indexes)
void * palloc0(Size size)
Definition: mcxt.c:1093
uintptr_t Datum
Definition: postgres.h:411
TransactionId next_xid
Definition: verify_heapam.c:85
#define HEAP_XMAX_IS_LOCKED_ONLY(infomask)
Definition: htup_details.h:230
void LockBuffer(Buffer buffer, int mode)
Definition: bufmgr.c:4011
#define att_align_pointer(cur_offset, attalign, attlen, attptr)
Definition: tupmacs.h:126
void systable_endscan_ordered(SysScanDesc sysscan)
Definition: genam.c:732
int work_mem
Definition: globals.c:124
#define RelationGetNumberOfBlocks(reln)
Definition: bufmgr.h:213
FullTransactionId relfrozenfxid
#define EpochFromFullTransactionId(x)
Definition: transam.h:47
TupleDesc rd_att
Definition: rel.h:110
#define HEAP_XMAX_IS_MULTI
Definition: htup_details.h:208
XidBoundsViolation
Definition: verify_heapam.c:37
SkipPages
Definition: verify_heapam.c:54
#define ereport(elevel,...)
Definition: elog.h:157
int allowedModes
Definition: execnodes.h:305
TransactionId MultiXactId
Definition: c.h:597
SetFunctionReturnMode returnMode
Definition: execnodes.h:307
#define PG_ARGISNULL(n)
Definition: fmgr.h:209
void relation_close(Relation relation, LOCKMODE lockmode)
Definition: relation.c:206
#define HEAP_MOVED_OFF
Definition: htup_details.h:210
#define Assert(condition)
Definition: c.h:804
#define lfirst(lc)
Definition: pg_list.h:169
static TupleDesc verify_heapam_tupdesc(void)
#define FrozenTransactionId
Definition: transam.h:33
#define VARATT_EXTERNAL_GET_EXTSIZE(toast_pointer)
Definition: postgres.h:371
#define HeapTupleHeaderGetXmin(tup)
Definition: htup_details.h:313
struct ToastedAttribute ToastedAttribute
#define OffsetNumberNext(offsetNumber)
Definition: off.h:52
XidCommitStatus
Definition: verify_heapam.c:46
bool LWLockAcquire(LWLock *lock, LWLockMode mode)
Definition: lwlock.c:1199
static FullTransactionId FullTransactionIdFromEpochAndXid(uint32 epoch, TransactionId xid)
Definition: transam.h:71
static bool fxid_in_cached_range(FullTransactionId fxid, const HeapCheckContext *ctx)
#define MAXALIGN(LEN)
Definition: c.h:757
bool MultiXactIdPrecedes(MultiXactId multi1, MultiXactId multi2)
Definition: multixact.c:3159
MemoryContext ecxt_per_query_memory
Definition: execnodes.h:233
#define VISIBILITYMAP_ALL_VISIBLE
Definition: visibilitymap.h:26
Tuplestorestate * tupstore
static void header(const char *fmt,...) pg_attribute_printf(1
Definition: pg_regress.c:210
List * toasted_attributes
#define VARATT_IS_EXTENDED(PTR)
Definition: postgres.h:340
Tuplestorestate * setResult
Definition: execnodes.h:310
#define DatumGetPointer(X)
Definition: postgres.h:593
static Datum values[MAXATTR]
Definition: bootstrap.c:166
char * text_to_cstring(const text *t)
Definition: varlena.c:223
ExprContext * econtext
Definition: execnodes.h:303
#define Int32GetDatum(X)
Definition: postgres.h:523
TupleDesc setDesc
Definition: execnodes.h:311
int errmsg(const char *fmt,...)
Definition: elog.c:909
SysScanDesc systable_beginscan_ordered(Relation heapRelation, Relation indexRelation, Snapshot snapshot, int nkeys, ScanKey key)
Definition: genam.c:642
uint8 visibilitymap_get_status(Relation rel, BlockNumber heapBlk, Buffer *buf)
XidCommitStatus cached_status
static const unsigned __int64 epoch
Definition: gettimeofday.c:34
bool MultiXactIdPrecedesOrEquals(MultiXactId multi1, MultiXactId multi2)
Definition: multixact.c:3173
void ScanKeyInit(ScanKey entry, AttrNumber attributeNumber, StrategyNumber strategy, RegProcedure procedure, Datum argument)
Definition: scankey.c:76
#define BUFFER_LOCK_SHARE
Definition: bufmgr.h:97
#define CStringGetTextDatum(s)
Definition: builtins.h:82
#define HEAP_HASEXTERNAL
Definition: htup_details.h:191
Definition: c.h:621
#define PG_FUNCTION_ARGS
Definition: fmgr.h:193
Relation * toast_indexes
#define FullTransactionIdPrecedes(a, b)
Definition: transam.h:51
struct HeapCheckContext HeapCheckContext
#define TransactionIdIsValid(xid)
Definition: transam.h:41
static FullTransactionId FullTransactionIdFromXidAndCtx(TransactionId xid, const HeapCheckContext *ctx)
static void static void status(const char *fmt,...) pg_attribute_printf(1
Definition: pg_regress.c:227
#define TransactionIdIsNormal(xid)
Definition: transam.h:42
Relation table_open(Oid relationId, LOCKMODE lockmode)
Definition: table.c:39
TransactionId relminmxid
#define PG_GETARG_INT64(n)
Definition: fmgr.h:283
Definition: pg_list.h:50
int Buffer
Definition: buf.h:23
int16 AttrNumber
Definition: attnum.h:21
void ReadMultiXactIdRange(MultiXactId *oldest, MultiXactId *next)
Definition: multixact.c:743
#define PG_RETURN_NULL()
Definition: fmgr.h:345
#define BTEqualStrategyNumber
Definition: stratnum.h:31
#define PageGetItem(page, itemId)
Definition: bufpage.h:340
static XidBoundsViolation check_mxid_valid_in_rel(MultiXactId mxid, HeapCheckContext *ctx)
Pointer Page
Definition: bufpage.h:78