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