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