PostgreSQL Source Code  git master
verify_heapam.c File Reference
#include "postgres.h"
#include "access/detoast.h"
#include "access/genam.h"
#include "access/heapam.h"
#include "access/heaptoast.h"
#include "access/multixact.h"
#include "access/toast_internals.h"
#include "access/visibilitymap.h"
#include "catalog/pg_am.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/procarray.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
Include dependency graph for verify_heapam.c:

Go to the source code of this file.

Data Structures

struct  ToastedAttribute
 
struct  HeapCheckContext
 

Macros

#define HEAPCHECK_RELATION_COLS   4
 

Typedefs

typedef enum XidBoundsViolation XidBoundsViolation
 
typedef enum XidCommitStatus XidCommitStatus
 
typedef enum SkipPages SkipPages
 
typedef struct ToastedAttribute ToastedAttribute
 
typedef struct HeapCheckContext HeapCheckContext
 

Enumerations

enum  XidBoundsViolation {
  XID_INVALID, XID_IN_FUTURE, XID_PRECEDES_CLUSTERMIN, XID_PRECEDES_RELMIN,
  XID_BOUNDS_OK
}
 
enum  XidCommitStatus { XID_COMMITTED, XID_IS_CURRENT_XID, XID_IN_PROGRESS, XID_ABORTED }
 
enum  SkipPages { SKIP_PAGES_ALL_FROZEN, SKIP_PAGES_ALL_VISIBLE, SKIP_PAGES_NONE }
 

Functions

 PG_FUNCTION_INFO_V1 (verify_heapam)
 
static void sanity_check_relation (Relation rel)
 
static void check_tuple (HeapCheckContext *ctx)
 
static void check_toast_tuple (HeapTuple toasttup, HeapCheckContext *ctx, ToastedAttribute *ta, int32 *expected_chunk_seq, uint32 extsize)
 
static bool check_tuple_attribute (HeapCheckContext *ctx)
 
static void check_toasted_attribute (HeapCheckContext *ctx, ToastedAttribute *ta)
 
static bool check_tuple_header (HeapCheckContext *ctx)
 
static bool check_tuple_visibility (HeapCheckContext *ctx)
 
static void report_corruption (HeapCheckContext *ctx, char *msg)
 
static void report_toast_corruption (HeapCheckContext *ctx, ToastedAttribute *ta, char *msg)
 
static TupleDesc verify_heapam_tupdesc (void)
 
static FullTransactionId FullTransactionIdFromXidAndCtx (TransactionId xid, const HeapCheckContext *ctx)
 
static void update_cached_xid_range (HeapCheckContext *ctx)
 
static void update_cached_mxid_range (HeapCheckContext *ctx)
 
static XidBoundsViolation check_mxid_in_range (MultiXactId mxid, HeapCheckContext *ctx)
 
static XidBoundsViolation check_mxid_valid_in_rel (MultiXactId mxid, HeapCheckContext *ctx)
 
static XidBoundsViolation get_xid_status (TransactionId xid, HeapCheckContext *ctx, XidCommitStatus *status)
 
Datum verify_heapam (PG_FUNCTION_ARGS)
 
static void report_corruption_internal (Tuplestorestate *tupstore, TupleDesc tupdesc, BlockNumber blkno, OffsetNumber offnum, AttrNumber attnum, char *msg)
 
static bool fxid_in_cached_range (FullTransactionId fxid, const HeapCheckContext *ctx)
 

Macro Definition Documentation

◆ HEAPCHECK_RELATION_COLS

#define HEAPCHECK_RELATION_COLS   4

Definition at line 31 of file verify_heapam.c.

Referenced by report_corruption_internal(), and verify_heapam_tupdesc().

Typedef Documentation

◆ HeapCheckContext

◆ SkipPages

typedef enum SkipPages SkipPages

◆ ToastedAttribute

◆ XidBoundsViolation

◆ XidCommitStatus

Enumeration Type Documentation

◆ SkipPages

enum SkipPages
Enumerator
SKIP_PAGES_ALL_FROZEN 
SKIP_PAGES_ALL_VISIBLE 
SKIP_PAGES_NONE 

Definition at line 54 of file verify_heapam.c.

◆ XidBoundsViolation

Enumerator
XID_INVALID 
XID_IN_FUTURE 
XID_PRECEDES_CLUSTERMIN 
XID_PRECEDES_RELMIN 
XID_BOUNDS_OK 

Definition at line 37 of file verify_heapam.c.

◆ XidCommitStatus

Enumerator
XID_COMMITTED 
XID_IS_CURRENT_XID 
XID_IN_PROGRESS 
XID_ABORTED 

Definition at line 46 of file verify_heapam.c.

Function Documentation

◆ check_mxid_in_range()

static XidBoundsViolation check_mxid_in_range ( MultiXactId  mxid,
HeapCheckContext ctx 
)
static

Definition at line 1614 of file verify_heapam.c.

References MultiXactIdPrecedes(), MultiXactIdPrecedesOrEquals(), HeapCheckContext::next_mxact, HeapCheckContext::oldest_mxact, HeapCheckContext::relminmxid, TransactionIdIsValid, XID_BOUNDS_OK, XID_IN_FUTURE, XID_INVALID, XID_PRECEDES_CLUSTERMIN, and XID_PRECEDES_RELMIN.

Referenced by check_mxid_valid_in_rel().

1615 {
1616  if (!TransactionIdIsValid(mxid))
1617  return XID_INVALID;
1618  if (MultiXactIdPrecedes(mxid, ctx->relminmxid))
1619  return XID_PRECEDES_RELMIN;
1620  if (MultiXactIdPrecedes(mxid, ctx->oldest_mxact))
1621  return XID_PRECEDES_CLUSTERMIN;
1622  if (MultiXactIdPrecedesOrEquals(ctx->next_mxact, mxid))
1623  return XID_IN_FUTURE;
1624  return XID_BOUNDS_OK;
1625 }
MultiXactId oldest_mxact
Definition: verify_heapam.c:96
MultiXactId next_mxact
Definition: verify_heapam.c:95
bool MultiXactIdPrecedes(MultiXactId multi1, MultiXactId multi2)
Definition: multixact.c:3159
bool MultiXactIdPrecedesOrEquals(MultiXactId multi1, MultiXactId multi2)
Definition: multixact.c:3173
#define TransactionIdIsValid(xid)
Definition: transam.h:41
TransactionId relminmxid

◆ check_mxid_valid_in_rel()

static XidBoundsViolation check_mxid_valid_in_rel ( MultiXactId  mxid,
HeapCheckContext ctx 
)
static

Definition at line 1636 of file verify_heapam.c.

References check_mxid_in_range(), update_cached_mxid_range(), and XID_BOUNDS_OK.

Referenced by check_tuple_visibility().

1637 {
1638  XidBoundsViolation result;
1639 
1640  result = check_mxid_in_range(mxid, ctx);
1641  if (result == XID_BOUNDS_OK)
1642  return XID_BOUNDS_OK;
1643 
1644  /* The range may have advanced. Recheck. */
1646  return check_mxid_in_range(mxid, ctx);
1647 }
static XidBoundsViolation check_mxid_in_range(MultiXactId mxid, HeapCheckContext *ctx)
static void update_cached_mxid_range(HeapCheckContext *ctx)
XidBoundsViolation
Definition: verify_heapam.c:37

◆ check_toast_tuple()

static void check_toast_tuple ( HeapTuple  toasttup,
HeapCheckContext ctx,
ToastedAttribute ta,
int32 expected_chunk_seq,
uint32  extsize 
)
static

Definition at line 1177 of file verify_heapam.c.

References DatumGetInt32, DatumGetPointer, fastgetattr, header(), psprintf(), RelationData::rd_att, report_toast_corruption(), TOAST_MAX_CHUNK_SIZE, ToastedAttribute::toast_pointer, HeapCheckContext::toast_rel, varatt_external::va_valueid, VARATT_IS_EXTENDED, VARATT_IS_SHORT, VARHDRSZ, VARHDRSZ_SHORT, VARSIZE, and VARSIZE_SHORT.

Referenced by check_toasted_attribute().

1180 {
1181  int32 chunk_seq;
1182  int32 last_chunk_seq = (extsize - 1) / TOAST_MAX_CHUNK_SIZE;
1183  Pointer chunk;
1184  bool isnull;
1185  int32 chunksize;
1186  int32 expected_size;
1187 
1188  /* Sanity-check the sequence number. */
1189  chunk_seq = DatumGetInt32(fastgetattr(toasttup, 2,
1190  ctx->toast_rel->rd_att, &isnull));
1191  if (isnull)
1192  {
1193  report_toast_corruption(ctx, ta,
1194  psprintf("toast value %u has toast chunk with null sequence number",
1195  ta->toast_pointer.va_valueid));
1196  return;
1197  }
1198  if (chunk_seq != *expected_chunk_seq)
1199  {
1200  /* Either the TOAST index is corrupt, or we don't have all chunks. */
1201  report_toast_corruption(ctx, ta,
1202  psprintf("toast value %u index scan returned chunk %d when expecting chunk %d",
1204  chunk_seq, *expected_chunk_seq));
1205  }
1206  *expected_chunk_seq = chunk_seq + 1;
1207 
1208  /* Sanity-check the chunk data. */
1209  chunk = DatumGetPointer(fastgetattr(toasttup, 3,
1210  ctx->toast_rel->rd_att, &isnull));
1211  if (isnull)
1212  {
1213  report_toast_corruption(ctx, ta,
1214  psprintf("toast value %u chunk %d has null data",
1216  chunk_seq));
1217  return;
1218  }
1219  if (!VARATT_IS_EXTENDED(chunk))
1220  chunksize = VARSIZE(chunk) - VARHDRSZ;
1221  else if (VARATT_IS_SHORT(chunk))
1222  {
1223  /*
1224  * could happen due to heap_form_tuple doing its thing
1225  */
1226  chunksize = VARSIZE_SHORT(chunk) - VARHDRSZ_SHORT;
1227  }
1228  else
1229  {
1230  /* should never happen */
1231  uint32 header = ((varattrib_4b *) chunk)->va_4byte.va_header;
1232 
1233  report_toast_corruption(ctx, ta,
1234  psprintf("toast value %u chunk %d has invalid varlena header %0x",
1236  chunk_seq, header));
1237  return;
1238  }
1239 
1240  /*
1241  * Some checks on the data we've found
1242  */
1243  if (chunk_seq > last_chunk_seq)
1244  {
1245  report_toast_corruption(ctx, ta,
1246  psprintf("toast value %u chunk %d follows last expected chunk %d",
1248  chunk_seq, last_chunk_seq));
1249  return;
1250  }
1251 
1252  expected_size = chunk_seq < last_chunk_seq ? TOAST_MAX_CHUNK_SIZE
1253  : extsize - (last_chunk_seq * TOAST_MAX_CHUNK_SIZE);
1254 
1255  if (chunksize != expected_size)
1256  report_toast_corruption(ctx, ta,
1257  psprintf("toast value %u chunk %d has size %u, but expected size %u",
1259  chunk_seq, chunksize, expected_size));
1260 }
#define fastgetattr(tup, attnum, tupleDesc, isnull)
Definition: htup_details.h:711
#define TOAST_MAX_CHUNK_SIZE
Definition: heaptoast.h:84
#define DatumGetInt32(X)
Definition: postgres.h:516
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 VARHDRSZ
Definition: c.h:627
char * psprintf(const char *fmt,...)
Definition: psprintf.c:46
signed int int32
Definition: c.h:429
char * Pointer
Definition: c.h:418
#define VARATT_IS_SHORT(PTR)
Definition: postgres.h:339
unsigned int uint32
Definition: c.h:441
static void report_toast_corruption(HeapCheckContext *ctx, ToastedAttribute *ta, char *msg)
#define VARSIZE_SHORT(PTR)
Definition: postgres.h:318
TupleDesc rd_att
Definition: rel.h:110
static void header(const char *fmt,...) pg_attribute_printf(1
Definition: pg_regress.c:210
#define VARATT_IS_EXTENDED(PTR)
Definition: postgres.h:340
#define DatumGetPointer(X)
Definition: postgres.h:593

◆ check_toasted_attribute()

static void check_toasted_attribute ( HeapCheckContext ctx,
ToastedAttribute ta 
)
static

Definition at line 1448 of file verify_heapam.c.

References BTEqualStrategyNumber, check_toast_tuple(), ForwardScanDirection, init_toast_snapshot(), ObjectIdGetDatum, psprintf(), report_toast_corruption(), ScanKeyInit(), systable_beginscan_ordered(), systable_endscan_ordered(), systable_getnext_ordered(), TOAST_MAX_CHUNK_SIZE, ToastedAttribute::toast_pointer, HeapCheckContext::toast_rel, varatt_external::va_valueid, HeapCheckContext::valid_toast_index, and VARATT_EXTERNAL_GET_EXTSIZE.

Referenced by verify_heapam().

1449 {
1450  SnapshotData SnapshotToast;
1451  ScanKeyData toastkey;
1452  SysScanDesc toastscan;
1453  bool found_toasttup;
1454  HeapTuple toasttup;
1455  uint32 extsize;
1456  int32 expected_chunk_seq = 0;
1457  int32 last_chunk_seq;
1458 
1460  last_chunk_seq = (extsize - 1) / TOAST_MAX_CHUNK_SIZE;
1461 
1462  /*
1463  * Setup a scan key to find chunks in toast table with matching va_valueid
1464  */
1465  ScanKeyInit(&toastkey,
1466  (AttrNumber) 1,
1467  BTEqualStrategyNumber, F_OIDEQ,
1469 
1470  /*
1471  * Check if any chunks for this toasted object exist in the toast table,
1472  * accessible via the index.
1473  */
1474  init_toast_snapshot(&SnapshotToast);
1475  toastscan = systable_beginscan_ordered(ctx->toast_rel,
1476  ctx->valid_toast_index,
1477  &SnapshotToast, 1,
1478  &toastkey);
1479  found_toasttup = false;
1480  while ((toasttup =
1481  systable_getnext_ordered(toastscan,
1482  ForwardScanDirection)) != NULL)
1483  {
1484  found_toasttup = true;
1485  check_toast_tuple(toasttup, ctx, ta, &expected_chunk_seq, extsize);
1486  }
1487  systable_endscan_ordered(toastscan);
1488 
1489  if (!found_toasttup)
1490  report_toast_corruption(ctx, ta,
1491  psprintf("toast value %u not found in toast table",
1492  ta->toast_pointer.va_valueid));
1493  else if (expected_chunk_seq <= last_chunk_seq)
1494  report_toast_corruption(ctx, ta,
1495  psprintf("toast value %u was expected to end at chunk %d, but ended while expecting chunk %d",
1497  last_chunk_seq, expected_chunk_seq));
1498 }
#define TOAST_MAX_CHUNK_SIZE
Definition: heaptoast.h:84
struct varatt_external toast_pointer
Definition: verify_heapam.c:68
void init_toast_snapshot(Snapshot toast_snapshot)
char * psprintf(const char *fmt,...)
Definition: psprintf.c:46
HeapTuple systable_getnext_ordered(SysScanDesc sysscan, ScanDirection direction)
Definition: genam.c:707
Relation valid_toast_index
signed int int32
Definition: c.h:429
static void check_toast_tuple(HeapTuple toasttup, HeapCheckContext *ctx, ToastedAttribute *ta, int32 *expected_chunk_seq, uint32 extsize)
#define ObjectIdGetDatum(X)
Definition: postgres.h:551
unsigned int uint32
Definition: c.h:441
static void report_toast_corruption(HeapCheckContext *ctx, ToastedAttribute *ta, char *msg)
void systable_endscan_ordered(SysScanDesc sysscan)
Definition: genam.c:732
#define VARATT_EXTERNAL_GET_EXTSIZE(toast_pointer)
Definition: postgres.h:371
SysScanDesc systable_beginscan_ordered(Relation heapRelation, Relation indexRelation, Snapshot snapshot, int nkeys, ScanKey key)
Definition: genam.c:642
void ScanKeyInit(ScanKey entry, AttrNumber attributeNumber, StrategyNumber strategy, RegProcedure procedure, Datum argument)
Definition: scankey.c:76
int16 AttrNumber
Definition: attnum.h:21
#define BTEqualStrategyNumber
Definition: stratnum.h:31

◆ check_tuple()

static void check_tuple ( HeapCheckContext ctx)
static

Definition at line 1505 of file verify_heapam.c.

References HeapCheckContext::attnum, check_tuple_attribute(), check_tuple_header(), check_tuple_visibility(), HeapCheckContext::natts, HeapCheckContext::offset, psprintf(), HeapCheckContext::rel, RelationGetDescr, and report_corruption().

Referenced by verify_heapam().

1506 {
1507  /*
1508  * Check various forms of tuple header corruption, and if the header is
1509  * too corrupt, do not continue with other checks.
1510  */
1511  if (!check_tuple_header(ctx))
1512  return;
1513 
1514  /*
1515  * Check tuple visibility. If the inserting transaction aborted, we
1516  * cannot assume our relation description matches the tuple structure, and
1517  * therefore cannot check it.
1518  */
1519  if (!check_tuple_visibility(ctx))
1520  return;
1521 
1522  /*
1523  * The tuple is visible, so it must be compatible with the current version
1524  * of the relation descriptor. It might have fewer columns than are
1525  * present in the relation descriptor, but it cannot have more.
1526  */
1527  if (RelationGetDescr(ctx->rel)->natts < ctx->natts)
1528  {
1529  report_corruption(ctx,
1530  psprintf("number of attributes %u exceeds maximum expected for table %u",
1531  ctx->natts,
1532  RelationGetDescr(ctx->rel)->natts));
1533  return;
1534  }
1535 
1536  /*
1537  * Check each attribute unless we hit corruption that confuses what to do
1538  * next, at which point we abort further attribute checks for this tuple.
1539  * Note that we don't abort for all types of corruption, only for those
1540  * types where we don't know how to continue. We also don't abort the
1541  * checking of toasted attributes collected from the tuple prior to
1542  * aborting. Those will still be checked later along with other toasted
1543  * attributes collected from the page.
1544  */
1545  ctx->offset = 0;
1546  for (ctx->attnum = 0; ctx->attnum < ctx->natts; ctx->attnum++)
1547  if (!check_tuple_attribute(ctx))
1548  break; /* cannot continue */
1549 
1550  /* revert attnum to -1 until we again examine individual attributes */
1551  ctx->attnum = -1;
1552 }
#define RelationGetDescr(relation)
Definition: rel.h:503
char * psprintf(const char *fmt,...)
Definition: psprintf.c:46
static void report_corruption(HeapCheckContext *ctx, char *msg)
static bool check_tuple_header(HeapCheckContext *ctx)
AttrNumber attnum
static bool check_tuple_attribute(HeapCheckContext *ctx)
static bool check_tuple_visibility(HeapCheckContext *ctx)

◆ check_tuple_attribute()

static bool check_tuple_attribute ( HeapCheckContext ctx)
static

Definition at line 1283 of file verify_heapam.c.

References att_addlength_pointer, att_align_nominal, att_align_pointer, att_isnull, ToastedAttribute::attnum, HeapCheckContext::attnum, ToastedAttribute::blkno, HeapCheckContext::blkno, DatumGetPointer, fetchatt, HEAP_HASEXTERNAL, HEAP_HASNULL, lappend(), HeapCheckContext::lp_len, ToastedAttribute::offnum, HeapCheckContext::offnum, HeapCheckContext::offset, palloc0(), psprintf(), RelationData::rd_rel, HeapCheckContext::rel, RelationGetDescr, report_corruption(), HeapTupleHeaderData::t_bits, HeapTupleHeaderData::t_hoff, HeapTupleHeaderData::t_infomask, ToastedAttribute::toast_pointer, HeapCheckContext::toast_rel, HeapCheckContext::toasted_attributes, HeapCheckContext::tuphdr, HeapCheckContext::tuple_could_be_pruned, TupleDescAttr, varatt_external::va_valueid, VARATT_EXTERNAL_GET_POINTER, VARATT_IS_EXTERNAL, VARTAG_EXTERNAL, and VARTAG_ONDISK.

Referenced by check_tuple().

1284 {
1285  Datum attdatum;
1286  struct varlena *attr;
1287  char *tp; /* pointer to the tuple data */
1288  uint16 infomask;
1289  Form_pg_attribute thisatt;
1290  struct varatt_external toast_pointer;
1291 
1292  infomask = ctx->tuphdr->t_infomask;
1293  thisatt = TupleDescAttr(RelationGetDescr(ctx->rel), ctx->attnum);
1294 
1295  tp = (char *) ctx->tuphdr + ctx->tuphdr->t_hoff;
1296 
1297  if (ctx->tuphdr->t_hoff + ctx->offset > ctx->lp_len)
1298  {
1299  report_corruption(ctx,
1300  psprintf("attribute with length %u starts at offset %u beyond total tuple length %u",
1301  thisatt->attlen,
1302  ctx->tuphdr->t_hoff + ctx->offset,
1303  ctx->lp_len));
1304  return false;
1305  }
1306 
1307  /* Skip null values */
1308  if (infomask & HEAP_HASNULL && att_isnull(ctx->attnum, ctx->tuphdr->t_bits))
1309  return true;
1310 
1311  /* Skip non-varlena values, but update offset first */
1312  if (thisatt->attlen != -1)
1313  {
1314  ctx->offset = att_align_nominal(ctx->offset, thisatt->attalign);
1315  ctx->offset = att_addlength_pointer(ctx->offset, thisatt->attlen,
1316  tp + ctx->offset);
1317  if (ctx->tuphdr->t_hoff + ctx->offset > ctx->lp_len)
1318  {
1319  report_corruption(ctx,
1320  psprintf("attribute with length %u ends at offset %u beyond total tuple length %u",
1321  thisatt->attlen,
1322  ctx->tuphdr->t_hoff + ctx->offset,
1323  ctx->lp_len));
1324  return false;
1325  }
1326  return true;
1327  }
1328 
1329  /* Ok, we're looking at a varlena attribute. */
1330  ctx->offset = att_align_pointer(ctx->offset, thisatt->attalign, -1,
1331  tp + ctx->offset);
1332 
1333  /* Get the (possibly corrupt) varlena datum */
1334  attdatum = fetchatt(thisatt, tp + ctx->offset);
1335 
1336  /*
1337  * We have the datum, but we cannot decode it carelessly, as it may still
1338  * be corrupt.
1339  */
1340 
1341  /*
1342  * Check that VARTAG_SIZE won't hit a TrapMacro on a corrupt va_tag before
1343  * risking a call into att_addlength_pointer
1344  */
1345  if (VARATT_IS_EXTERNAL(tp + ctx->offset))
1346  {
1347  uint8 va_tag = VARTAG_EXTERNAL(tp + ctx->offset);
1348 
1349  if (va_tag != VARTAG_ONDISK)
1350  {
1351  report_corruption(ctx,
1352  psprintf("toasted attribute has unexpected TOAST tag %u",
1353  va_tag));
1354  /* We can't know where the next attribute begins */
1355  return false;
1356  }
1357  }
1358 
1359  /* Ok, should be safe now */
1360  ctx->offset = att_addlength_pointer(ctx->offset, thisatt->attlen,
1361  tp + ctx->offset);
1362 
1363  if (ctx->tuphdr->t_hoff + ctx->offset > ctx->lp_len)
1364  {
1365  report_corruption(ctx,
1366  psprintf("attribute with length %u ends at offset %u beyond total tuple length %u",
1367  thisatt->attlen,
1368  ctx->tuphdr->t_hoff + ctx->offset,
1369  ctx->lp_len));
1370 
1371  return false;
1372  }
1373 
1374  /*
1375  * heap_deform_tuple would be done with this attribute at this point,
1376  * having stored it in values[], and would continue to the next attribute.
1377  * We go further, because we need to check if the toast datum is corrupt.
1378  */
1379 
1380  attr = (struct varlena *) DatumGetPointer(attdatum);
1381 
1382  /*
1383  * Now we follow the logic of detoast_external_attr(), with the same
1384  * caveats about being paranoid about corruption.
1385  */
1386 
1387  /* Skip values that are not external */
1388  if (!VARATT_IS_EXTERNAL(attr))
1389  return true;
1390 
1391  /* It is external, and we're looking at a page on disk */
1392 
1393  /*
1394  * Must copy attr into toast_pointer for alignment considerations
1395  */
1396  VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
1397 
1398  /* The tuple header better claim to contain toasted values */
1399  if (!(infomask & HEAP_HASEXTERNAL))
1400  {
1401  report_corruption(ctx,
1402  psprintf("toast value %u is external but tuple header flag HEAP_HASEXTERNAL not set",
1403  toast_pointer.va_valueid));
1404  return true;
1405  }
1406 
1407  /* The relation better have a toast table */
1408  if (!ctx->rel->rd_rel->reltoastrelid)
1409  {
1410  report_corruption(ctx,
1411  psprintf("toast value %u is external but relation has no toast relation",
1412  toast_pointer.va_valueid));
1413  return true;
1414  }
1415 
1416  /* If we were told to skip toast checking, then we're done. */
1417  if (ctx->toast_rel == NULL)
1418  return true;
1419 
1420  /*
1421  * If this tuple is eligible to be pruned, we cannot check the toast.
1422  * Otherwise, we push a copy of the toast tuple so we can check it after
1423  * releasing the main table buffer lock.
1424  */
1425  if (!ctx->tuple_could_be_pruned)
1426  {
1427  ToastedAttribute *ta;
1428 
1429  ta = (ToastedAttribute *) palloc0(sizeof(ToastedAttribute));
1430 
1432  ta->blkno = ctx->blkno;
1433  ta->offnum = ctx->offnum;
1434  ta->attnum = ctx->attnum;
1436  }
1437 
1438  return true;
1439 }
BlockNumber blkno
#define att_align_nominal(cur_offset, attalign)
Definition: tupmacs.h:148
HeapTupleHeader tuphdr
bits8 t_bits[FLEXIBLE_ARRAY_MEMBER]
Definition: htup_details.h:177
#define RelationGetDescr(relation)
Definition: rel.h:503
struct varatt_external toast_pointer
Definition: verify_heapam.c:68
#define VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr)
Definition: detoast.h:22
#define att_isnull(ATT, BITS)
Definition: tupmacs.h:25
#define TupleDescAttr(tupdesc, i)
Definition: tupdesc.h:92
char * psprintf(const char *fmt,...)
Definition: psprintf.c:46
BlockNumber blkno
Definition: verify_heapam.c:69
unsigned char uint8
Definition: c.h:439
static void report_corruption(HeapCheckContext *ctx, char *msg)
OffsetNumber offnum
Definition: verify_heapam.c:70
AttrNumber attnum
Form_pg_class rd_rel
Definition: rel.h:109
#define VARTAG_EXTERNAL(PTR)
Definition: postgres.h:321
#define fetchatt(A, T)
Definition: tupmacs.h:41
AttrNumber attnum
Definition: verify_heapam.c:71
#define VARATT_IS_EXTERNAL(PTR)
Definition: postgres.h:326
unsigned short uint16
Definition: c.h:440
#define HEAP_HASNULL
Definition: htup_details.h:189
FormData_pg_attribute * Form_pg_attribute
Definition: pg_attribute.h:207
#define att_addlength_pointer(cur_offset, attlen, attptr)
Definition: tupmacs.h:176
OffsetNumber offnum
List * lappend(List *list, void *datum)
Definition: list.c:336
void * palloc0(Size size)
Definition: mcxt.c:1093
uintptr_t Datum
Definition: postgres.h:411
#define att_align_pointer(cur_offset, attalign, attlen, attptr)
Definition: tupmacs.h:126
List * toasted_attributes
#define DatumGetPointer(X)
Definition: postgres.h:593
#define HEAP_HASEXTERNAL
Definition: htup_details.h:191
Definition: c.h:621

◆ check_tuple_header()

static bool check_tuple_header ( HeapCheckContext ctx)
static

Definition at line 657 of file verify_heapam.c.

References BITMAPLEN, HEAP_HASNULL, HEAP_XMAX_COMMITTED, HEAP_XMAX_IS_MULTI, HeapCheckContext::lp_len, MAXALIGN, HeapCheckContext::natts, psprintf(), pstrdup(), report_corruption(), SizeofHeapTupleHeader, HeapTupleHeaderData::t_hoff, HeapTupleHeaderData::t_infomask, and HeapCheckContext::tuphdr.

Referenced by check_tuple().

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 }
#define SizeofHeapTupleHeader
Definition: htup_details.h:184
HeapTupleHeader tuphdr
char * pstrdup(const char *in)
Definition: mcxt.c:1299
char * psprintf(const char *fmt,...)
Definition: psprintf.c:46
static void report_corruption(HeapCheckContext *ctx, char *msg)
#define BITMAPLEN(NATTS)
Definition: htup_details.h:546
#define HEAP_XMAX_COMMITTED
Definition: htup_details.h:206
unsigned short uint16
Definition: c.h:440
#define HEAP_HASNULL
Definition: htup_details.h:189
#define HEAP_XMAX_IS_MULTI
Definition: htup_details.h:208
#define MAXALIGN(LEN)
Definition: c.h:757

◆ check_tuple_visibility()

static bool check_tuple_visibility ( HeapCheckContext ctx)
static

Definition at line 743 of file verify_heapam.c.

References check_mxid_valid_in_rel(), EpochFromFullTransactionId, get_xid_status(), HEAP_MOVED_IN, HEAP_MOVED_OFF, HEAP_XMAX_INVALID, HEAP_XMAX_IS_LOCKED_ONLY, HEAP_XMAX_IS_MULTI, HeapTupleGetUpdateXid(), HeapTupleHeaderGetRawXmax, HeapTupleHeaderGetXmin, HeapTupleHeaderGetXvac, HeapTupleHeaderXminCommitted, HeapTupleHeaderXminInvalid, HeapCheckContext::next_fxid, HeapCheckContext::next_mxact, HeapCheckContext::oldest_fxid, HeapCheckContext::oldest_mxact, psprintf(), pstrdup(), HeapCheckContext::relfrozenfxid, HeapCheckContext::relminmxid, report_corruption(), HeapCheckContext::safe_xmin, HeapTupleHeaderData::t_infomask, TransactionIdPrecedes(), HeapCheckContext::tuphdr, HeapCheckContext::tuple_could_be_pruned, XID_ABORTED, XID_BOUNDS_OK, XID_COMMITTED, XID_IN_FUTURE, XID_IN_PROGRESS, XID_INVALID, XID_IS_CURRENT_XID, XID_PRECEDES_CLUSTERMIN, XID_PRECEDES_RELMIN, and XidFromFullTransactionId.

Referenced by check_tuple().

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  /*
844  * The tuple is dead, because the xvac transaction moved
845  * it off and committed. It's checkable, but also
846  * prunable.
847  */
848  return true;
849 
850  case XID_ABORTED:
851 
852  /*
853  * The original xmin must have committed, because the xvac
854  * transaction tried to move it later. Since xvac is
855  * aborted, whether it's still alive now depends on the
856  * status of xmax.
857  */
858  break;
859  }
860  }
861  /* Used by pre-9.0 binary upgrades */
862  else if (tuphdr->t_infomask & HEAP_MOVED_IN)
863  {
864  xvac = HeapTupleHeaderGetXvac(tuphdr);
865 
866  switch (get_xid_status(xvac, ctx, &xvac_status))
867  {
868  case XID_INVALID:
869  report_corruption(ctx,
870  pstrdup("old-style VACUUM FULL transaction ID for moved in tuple is invalid"));
871  return false;
872  case XID_IN_FUTURE:
873  report_corruption(ctx,
874  psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple equals or exceeds next valid transaction ID %u:%u",
875  xvac,
878  return false;
879  case XID_PRECEDES_RELMIN:
880  report_corruption(ctx,
881  psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple precedes relation freeze threshold %u:%u",
882  xvac,
885  return false;
887  report_corruption(ctx,
888  psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple precedes oldest valid transaction ID %u:%u",
889  xvac,
892  return false;
893  case XID_BOUNDS_OK:
894  break;
895  }
896 
897  switch (xvac_status)
898  {
899  case XID_IS_CURRENT_XID:
900  report_corruption(ctx,
901  psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple matches our current transaction ID",
902  xvac));
903  return false;
904  case XID_IN_PROGRESS:
905  report_corruption(ctx,
906  psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple appears to be in progress",
907  xvac));
908  return false;
909 
910  case XID_COMMITTED:
911 
912  /*
913  * The original xmin must have committed, because the xvac
914  * transaction moved it later. Whether it's still alive
915  * now depends on the status of xmax.
916  */
917  break;
918 
919  case XID_ABORTED:
920 
921  /*
922  * The tuple is dead, because the xvac transaction moved
923  * it off and committed. It's checkable, but also
924  * prunable.
925  */
926  return true;
927  }
928  }
929  else if (xmin_status != XID_COMMITTED)
930  {
931  /*
932  * Inserting transaction is not in progress, and not committed, so
933  * it might have changed the TupleDesc in ways we don't know
934  * about. Thus, don't try to check the tuple structure.
935  *
936  * If xmin_status happens to be XID_IS_CURRENT_XID, then in theory
937  * any such DDL changes ought to be visible to us, so perhaps we
938  * could check anyway in that case. But, for now, let's be
939  * conservative and treat this like any other uncommitted insert.
940  */
941  return false;
942  }
943  }
944 
945  /*
946  * Okay, the inserter committed, so it was good at some point. Now what
947  * about the deleting transaction?
948  */
949 
950  if (tuphdr->t_infomask & HEAP_XMAX_IS_MULTI)
951  {
952  /*
953  * xmax is a multixact, so sanity-check the MXID. Note that we do this
954  * prior to checking for HEAP_XMAX_INVALID or
955  * HEAP_XMAX_IS_LOCKED_ONLY. This might therefore complain about
956  * things that wouldn't actually be a problem during a normal scan,
957  * but eventually we're going to have to freeze, and that process will
958  * ignore hint bits.
959  *
960  * Even if the MXID is out of range, we still know that the original
961  * insert committed, so we can check the tuple itself. However, we
962  * can't rule out the possibility that this tuple is dead, so don't
963  * clear ctx->tuple_could_be_pruned. Possibly we should go ahead and
964  * clear that flag anyway if HEAP_XMAX_INVALID is set or if
965  * HEAP_XMAX_IS_LOCKED_ONLY is true, but for now we err on the side of
966  * avoiding possibly-bogus complaints about missing TOAST entries.
967  */
968  xmax = HeapTupleHeaderGetRawXmax(tuphdr);
969  switch (check_mxid_valid_in_rel(xmax, ctx))
970  {
971  case XID_INVALID:
972  report_corruption(ctx,
973  pstrdup("multitransaction ID is invalid"));
974  return true;
975  case XID_PRECEDES_RELMIN:
976  report_corruption(ctx,
977  psprintf("multitransaction ID %u precedes relation minimum multitransaction ID threshold %u",
978  xmax, ctx->relminmxid));
979  return true;
981  report_corruption(ctx,
982  psprintf("multitransaction ID %u precedes oldest valid multitransaction ID threshold %u",
983  xmax, ctx->oldest_mxact));
984  return true;
985  case XID_IN_FUTURE:
986  report_corruption(ctx,
987  psprintf("multitransaction ID %u equals or exceeds next valid multitransaction ID %u",
988  xmax,
989  ctx->next_mxact));
990  return true;
991  case XID_BOUNDS_OK:
992  break;
993  }
994  }
995 
996  if (tuphdr->t_infomask & HEAP_XMAX_INVALID)
997  {
998  /*
999  * This tuple is live. A concurrently running transaction could
1000  * delete it before we get around to checking the toast, but any such
1001  * running transaction is surely not less than our safe_xmin, so the
1002  * toast cannot be vacuumed out from under us.
1003  */
1004  ctx->tuple_could_be_pruned = false;
1005  return true;
1006  }
1007 
1008  if (HEAP_XMAX_IS_LOCKED_ONLY(tuphdr->t_infomask))
1009  {
1010  /*
1011  * "Deleting" xact really only locked it, so the tuple is live in any
1012  * case. As above, a concurrently running transaction could delete
1013  * it, but it cannot be vacuumed out from under us.
1014  */
1015  ctx->tuple_could_be_pruned = false;
1016  return true;
1017  }
1018 
1019  if (tuphdr->t_infomask & HEAP_XMAX_IS_MULTI)
1020  {
1021  /*
1022  * We already checked above that this multixact is within limits for
1023  * this table. Now check the update xid from this multixact.
1024  */
1025  xmax = HeapTupleGetUpdateXid(tuphdr);
1026  switch (get_xid_status(xmax, ctx, &xmax_status))
1027  {
1028  case XID_INVALID:
1029  /* not LOCKED_ONLY, so it has to have an xmax */
1030  report_corruption(ctx,
1031  pstrdup("update xid is invalid"));
1032  return true;
1033  case XID_IN_FUTURE:
1034  report_corruption(ctx,
1035  psprintf("update xid %u equals or exceeds next valid transaction ID %u:%u",
1036  xmax,
1039  return true;
1040  case XID_PRECEDES_RELMIN:
1041  report_corruption(ctx,
1042  psprintf("update xid %u precedes relation freeze threshold %u:%u",
1043  xmax,
1046  return true;
1048  report_corruption(ctx,
1049  psprintf("update xid %u precedes oldest valid transaction ID %u:%u",
1050  xmax,
1053  return true;
1054  case XID_BOUNDS_OK:
1055  break;
1056  }
1057 
1058  switch (xmax_status)
1059  {
1060  case XID_IS_CURRENT_XID:
1061  case XID_IN_PROGRESS:
1062 
1063  /*
1064  * The delete is in progress, so it cannot be visible to our
1065  * snapshot.
1066  */
1067  ctx->tuple_could_be_pruned = false;
1068  break;
1069  case XID_COMMITTED:
1070 
1071  /*
1072  * The delete committed. Whether the toast can be vacuumed
1073  * away depends on how old the deleting transaction is.
1074  */
1076  ctx->safe_xmin);
1077  break;
1078  case XID_ABORTED:
1079 
1080  /*
1081  * The delete aborted or crashed. The tuple is still live.
1082  */
1083  ctx->tuple_could_be_pruned = false;
1084  break;
1085  }
1086 
1087  /* Tuple itself is checkable even if it's dead. */
1088  return true;
1089  }
1090 
1091  /* xmax is an XID, not a MXID. Sanity check it. */
1092  xmax = HeapTupleHeaderGetRawXmax(tuphdr);
1093  switch (get_xid_status(xmax, ctx, &xmax_status))
1094  {
1095  case XID_IN_FUTURE:
1096  report_corruption(ctx,
1097  psprintf("xmax %u equals or exceeds next valid transaction ID %u:%u",
1098  xmax,
1101  return false; /* corrupt */
1102  case XID_PRECEDES_RELMIN:
1103  report_corruption(ctx,
1104  psprintf("xmax %u precedes relation freeze threshold %u:%u",
1105  xmax,
1108  return false; /* corrupt */
1110  report_corruption(ctx,
1111  psprintf("xmax %u precedes oldest valid transaction ID %u:%u",
1112  xmax,
1115  return false; /* corrupt */
1116  case XID_BOUNDS_OK:
1117  case XID_INVALID:
1118  break;
1119  }
1120 
1121  /*
1122  * Whether the toast can be vacuumed away depends on how old the deleting
1123  * transaction is.
1124  */
1125  switch (xmax_status)
1126  {
1127  case XID_IS_CURRENT_XID:
1128  case XID_IN_PROGRESS:
1129 
1130  /*
1131  * The delete is in progress, so it cannot be visible to our
1132  * snapshot.
1133  */
1134  ctx->tuple_could_be_pruned = false;
1135  break;
1136 
1137  case XID_COMMITTED:
1138 
1139  /*
1140  * The delete committed. Whether the toast can be vacuumed away
1141  * depends on how old the deleting transaction is.
1142  */
1144  ctx->safe_xmin);
1145  break;
1146 
1147  case XID_ABORTED:
1148 
1149  /*
1150  * The delete aborted or crashed. The tuple is still live.
1151  */
1152  ctx->tuple_could_be_pruned = false;
1153  break;
1154  }
1155 
1156  /* Tuple itself is checkable even if it's dead. */
1157  return true;
1158 }
FullTransactionId oldest_fxid
Definition: verify_heapam.c:87
HeapTupleHeader tuphdr
uint32 TransactionId
Definition: c.h:587
TransactionId HeapTupleGetUpdateXid(HeapTupleHeader tuple)
Definition: heapam.c:6816
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
static void report_corruption(HeapCheckContext *ctx, char *msg)
#define HeapTupleHeaderXminInvalid(tup)
Definition: htup_details.h:329
TransactionId safe_xmin
Definition: verify_heapam.c:89
MultiXactId next_mxact
Definition: verify_heapam.c:95
#define XidFromFullTransactionId(x)
Definition: transam.h:48
#define HeapTupleHeaderGetRawXmax(tup)
Definition: htup_details.h:375
#define HEAP_XMAX_INVALID
Definition: htup_details.h:207
#define HeapTupleHeaderXminCommitted(tup)
Definition: htup_details.h:324
static XidBoundsViolation get_xid_status(TransactionId xid, HeapCheckContext *ctx, XidCommitStatus *status)
#define HeapTupleHeaderGetXvac(tup)
Definition: htup_details.h:415
#define HEAP_MOVED_IN
Definition: htup_details.h:213
bool TransactionIdPrecedes(TransactionId id1, TransactionId id2)
Definition: transam.c:300
#define HEAP_XMAX_IS_LOCKED_ONLY(infomask)
Definition: htup_details.h:230
FullTransactionId relfrozenfxid
#define EpochFromFullTransactionId(x)
Definition: transam.h:47
#define HEAP_XMAX_IS_MULTI
Definition: htup_details.h:208
#define HEAP_MOVED_OFF
Definition: htup_details.h:210
#define HeapTupleHeaderGetXmin(tup)
Definition: htup_details.h:313
XidCommitStatus
Definition: verify_heapam.c:46
TransactionId relminmxid
static XidBoundsViolation check_mxid_valid_in_rel(MultiXactId mxid, HeapCheckContext *ctx)

◆ FullTransactionIdFromXidAndCtx()

static FullTransactionId FullTransactionIdFromXidAndCtx ( TransactionId  xid,
const HeapCheckContext ctx 
)
static

Definition at line 1560 of file verify_heapam.c.

References epoch, EpochFromFullTransactionId, FullTransactionIdFromEpochAndXid(), HeapCheckContext::next_fxid, HeapCheckContext::next_xid, and TransactionIdIsNormal.

Referenced by get_xid_status(), update_cached_xid_range(), and verify_heapam().

1561 {
1562  uint32 epoch;
1563 
1564  if (!TransactionIdIsNormal(xid))
1565  return FullTransactionIdFromEpochAndXid(0, xid);
1566  epoch = EpochFromFullTransactionId(ctx->next_fxid);
1567  if (xid > ctx->next_xid)
1568  epoch--;
1569  return FullTransactionIdFromEpochAndXid(epoch, xid);
1570 }
FullTransactionId next_fxid
Definition: verify_heapam.c:84
unsigned int uint32
Definition: c.h:441
TransactionId next_xid
Definition: verify_heapam.c:85
#define EpochFromFullTransactionId(x)
Definition: transam.h:47
static FullTransactionId FullTransactionIdFromEpochAndXid(uint32 epoch, TransactionId xid)
Definition: transam.h:71
static const unsigned __int64 epoch
Definition: gettimeofday.c:34
#define TransactionIdIsNormal(xid)
Definition: transam.h:42

◆ fxid_in_cached_range()

static bool fxid_in_cached_range ( FullTransactionId  fxid,
const HeapCheckContext ctx 
)
inlinestatic

Definition at line 1603 of file verify_heapam.c.

References FullTransactionIdPrecedes, FullTransactionIdPrecedesOrEquals, HeapCheckContext::next_fxid, and HeapCheckContext::oldest_fxid.

Referenced by get_xid_status().

1604 {
1605  return (FullTransactionIdPrecedesOrEquals(ctx->oldest_fxid, fxid) &&
1606  FullTransactionIdPrecedes(fxid, ctx->next_fxid));
1607 }
FullTransactionId oldest_fxid
Definition: verify_heapam.c:87
FullTransactionId next_fxid
Definition: verify_heapam.c:84
#define FullTransactionIdPrecedesOrEquals(a, b)
Definition: transam.h:52
#define FullTransactionIdPrecedes(a, b)
Definition: transam.h:51

◆ get_xid_status()

static XidBoundsViolation get_xid_status ( TransactionId  xid,
HeapCheckContext ctx,
XidCommitStatus status 
)
static

Definition at line 1668 of file verify_heapam.c.

References BootstrapTransactionId, HeapCheckContext::cached_status, HeapCheckContext::cached_xid, FrozenTransactionId, FullTransactionIdFromXidAndCtx(), FullTransactionIdPrecedes, FullTransactionIdPrecedesOrEquals, fxid_in_cached_range(), LW_SHARED, LWLockAcquire(), LWLockRelease(), HeapCheckContext::next_fxid, HeapCheckContext::oldest_fxid, VariableCacheData::oldestClogXid, HeapCheckContext::relfrozenfxid, ShmemVariableCache, status(), TransactionIdDidCommit(), TransactionIdIsCurrentTransactionId(), TransactionIdIsInProgress(), TransactionIdIsValid, update_cached_xid_range(), XID_ABORTED, XID_BOUNDS_OK, XID_COMMITTED, XID_IN_FUTURE, XID_IN_PROGRESS, XID_INVALID, XID_IS_CURRENT_XID, XID_PRECEDES_CLUSTERMIN, and XID_PRECEDES_RELMIN.

Referenced by check_tuple_visibility().

1670 {
1671  FullTransactionId fxid;
1672  FullTransactionId clog_horizon;
1673 
1674  /* Quick check for special xids */
1675  if (!TransactionIdIsValid(xid))
1676  return XID_INVALID;
1677  else if (xid == BootstrapTransactionId || xid == FrozenTransactionId)
1678  {
1679  if (status != NULL)
1680  *status = XID_COMMITTED;
1681  return XID_BOUNDS_OK;
1682  }
1683 
1684  /* Check if the xid is within bounds */
1685  fxid = FullTransactionIdFromXidAndCtx(xid, ctx);
1686  if (!fxid_in_cached_range(fxid, ctx))
1687  {
1688  /*
1689  * We may have been checking against stale values. Update the cached
1690  * range to be sure, and since we relied on the cached range when we
1691  * performed the full xid conversion, reconvert.
1692  */
1694  fxid = FullTransactionIdFromXidAndCtx(xid, ctx);
1695  }
1696 
1698  return XID_IN_FUTURE;
1699  if (FullTransactionIdPrecedes(fxid, ctx->oldest_fxid))
1700  return XID_PRECEDES_CLUSTERMIN;
1701  if (FullTransactionIdPrecedes(fxid, ctx->relfrozenfxid))
1702  return XID_PRECEDES_RELMIN;
1703 
1704  /* Early return if the caller does not request clog checking */
1705  if (status == NULL)
1706  return XID_BOUNDS_OK;
1707 
1708  /* Early return if we just checked this xid in a prior call */
1709  if (xid == ctx->cached_xid)
1710  {
1711  *status = ctx->cached_status;
1712  return XID_BOUNDS_OK;
1713  }
1714 
1715  *status = XID_COMMITTED;
1716  LWLockAcquire(XactTruncationLock, LW_SHARED);
1717  clog_horizon =
1719  ctx);
1720  if (FullTransactionIdPrecedesOrEquals(clog_horizon, fxid))
1721  {
1724  else if (TransactionIdIsInProgress(xid))
1726  else if (TransactionIdDidCommit(xid))
1727  *status = XID_COMMITTED;
1728  else
1729  *status = XID_ABORTED;
1730  }
1731  LWLockRelease(XactTruncationLock);
1732  ctx->cached_xid = xid;
1733  ctx->cached_status = *status;
1734  return XID_BOUNDS_OK;
1735 }
FullTransactionId oldest_fxid
Definition: verify_heapam.c:87
bool TransactionIdIsCurrentTransactionId(TransactionId xid)
Definition: xact.c:869
bool TransactionIdIsInProgress(TransactionId xid)
Definition: procarray.c:1348
FullTransactionId next_fxid
Definition: verify_heapam.c:84
TransactionId cached_xid
bool TransactionIdDidCommit(TransactionId transactionId)
Definition: transam.c:125
void LWLockRelease(LWLock *lock)
Definition: lwlock.c:1816
static void update_cached_xid_range(HeapCheckContext *ctx)
#define FullTransactionIdPrecedesOrEquals(a, b)
Definition: transam.h:52
#define BootstrapTransactionId
Definition: transam.h:32
TransactionId oldestClogXid
Definition: transam.h:248
VariableCache ShmemVariableCache
Definition: varsup.c:34
FullTransactionId relfrozenfxid
#define FrozenTransactionId
Definition: transam.h:33
bool LWLockAcquire(LWLock *lock, LWLockMode mode)
Definition: lwlock.c:1203
static bool fxid_in_cached_range(FullTransactionId fxid, const HeapCheckContext *ctx)
XidCommitStatus cached_status
#define FullTransactionIdPrecedes(a, b)
Definition: transam.h:51
#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

◆ PG_FUNCTION_INFO_V1()

PG_FUNCTION_INFO_V1 ( verify_heapam  )

◆ report_corruption()

static void report_corruption ( HeapCheckContext ctx,
char *  msg 
)
static

Definition at line 589 of file verify_heapam.c.

References HeapCheckContext::attnum, HeapCheckContext::blkno, HeapCheckContext::is_corrupt, HeapCheckContext::offnum, report_corruption_internal(), HeapCheckContext::tupdesc, and HeapCheckContext::tupstore.

Referenced by check_tuple(), check_tuple_attribute(), check_tuple_header(), check_tuple_visibility(), and verify_heapam().

590 {
592  ctx->offnum, ctx->attnum, msg);
593  ctx->is_corrupt = true;
594 }
BlockNumber blkno
AttrNumber attnum
static void report_corruption_internal(Tuplestorestate *tupstore, TupleDesc tupdesc, BlockNumber blkno, OffsetNumber offnum, AttrNumber attnum, char *msg)
OffsetNumber offnum
Tuplestorestate * tupstore

◆ report_corruption_internal()

static void report_corruption_internal ( Tuplestorestate tupstore,
TupleDesc  tupdesc,
BlockNumber  blkno,
OffsetNumber  offnum,
AttrNumber  attnum,
char *  msg 
)
static

Definition at line 551 of file verify_heapam.c.

References CStringGetTextDatum, heap_form_tuple(), HEAPCHECK_RELATION_COLS, Int32GetDatum, Int64GetDatum(), MemSet, pfree(), tuplestore_puttuple(), and values.

Referenced by report_corruption(), and report_toast_corruption().

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 }
#define HEAPCHECK_RELATION_COLS
Definition: verify_heapam.c:31
#define MemSet(start, val, len)
Definition: c.h:1008
HeapTuple heap_form_tuple(TupleDesc tupleDescriptor, Datum *values, bool *isnull)
Definition: heaptuple.c:1020
void pfree(void *pointer)
Definition: mcxt.c:1169
void tuplestore_puttuple(Tuplestorestate *state, HeapTuple tuple)
Definition: tuplestore.c:730
Datum Int64GetDatum(int64 X)
Definition: fmgr.c:1697
uintptr_t Datum
Definition: postgres.h:411
int16 attnum
Definition: pg_attribute.h:83
static Datum values[MAXATTR]
Definition: bootstrap.c:166
#define Int32GetDatum(X)
Definition: postgres.h:523
#define CStringGetTextDatum(s)
Definition: builtins.h:82

◆ report_toast_corruption()

static void report_toast_corruption ( HeapCheckContext ctx,
ToastedAttribute ta,
char *  msg 
)
static

Definition at line 605 of file verify_heapam.c.

References ToastedAttribute::attnum, ToastedAttribute::blkno, HeapCheckContext::is_corrupt, ToastedAttribute::offnum, report_corruption_internal(), HeapCheckContext::tupdesc, and HeapCheckContext::tupstore.

Referenced by check_toast_tuple(), and check_toasted_attribute().

607 {
609  ta->offnum, ta->attnum, msg);
610  ctx->is_corrupt = true;
611 }
BlockNumber blkno
Definition: verify_heapam.c:69
OffsetNumber offnum
Definition: verify_heapam.c:70
AttrNumber attnum
Definition: verify_heapam.c:71
static void report_corruption_internal(Tuplestorestate *tupstore, TupleDesc tupdesc, BlockNumber blkno, OffsetNumber offnum, AttrNumber attnum, char *msg)
Tuplestorestate * tupstore

◆ sanity_check_relation()

static void sanity_check_relation ( Relation  rel)
static

Definition at line 531 of file verify_heapam.c.

References ereport, errcode(), errmsg(), ERROR, RelationData::rd_rel, and RelationGetRelationName.

Referenced by verify_heapam().

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 }
int errcode(int sqlerrcode)
Definition: elog.c:698
Form_pg_class rd_rel
Definition: rel.h:109
#define ERROR
Definition: elog.h:46
#define RelationGetRelationName(relation)
Definition: rel.h:511
#define ereport(elevel,...)
Definition: elog.h:157
int errmsg(const char *fmt,...)
Definition: elog.c:909

◆ update_cached_mxid_range()

static void update_cached_mxid_range ( HeapCheckContext ctx)
static

Definition at line 1593 of file verify_heapam.c.

References HeapCheckContext::next_mxact, HeapCheckContext::oldest_mxact, and ReadMultiXactIdRange().

Referenced by check_mxid_valid_in_rel(), and verify_heapam().

1594 {
1596 }
MultiXactId oldest_mxact
Definition: verify_heapam.c:96
MultiXactId next_mxact
Definition: verify_heapam.c:95
void ReadMultiXactIdRange(MultiXactId *oldest, MultiXactId *next)
Definition: multixact.c:743

◆ update_cached_xid_range()

static void update_cached_xid_range ( HeapCheckContext ctx)
static

Definition at line 1576 of file verify_heapam.c.

References FullTransactionIdFromXidAndCtx(), LW_SHARED, LWLockAcquire(), LWLockRelease(), HeapCheckContext::next_fxid, HeapCheckContext::next_xid, VariableCacheData::nextXid, HeapCheckContext::oldest_fxid, HeapCheckContext::oldest_xid, VariableCacheData::oldestXid, ShmemVariableCache, and XidFromFullTransactionId.

Referenced by get_xid_status(), and verify_heapam().

1577 {
1578  /* Make cached copies */
1579  LWLockAcquire(XidGenLock, LW_SHARED);
1582  LWLockRelease(XidGenLock);
1583 
1584  /* And compute alternate versions of the same */
1587 }
TransactionId oldest_xid
Definition: verify_heapam.c:86
FullTransactionId oldest_fxid
Definition: verify_heapam.c:87
FullTransactionId next_fxid
Definition: verify_heapam.c:84
TransactionId oldestXid
Definition: transam.h:217
FullTransactionId nextXid
Definition: transam.h:215
#define XidFromFullTransactionId(x)
Definition: transam.h:48
void LWLockRelease(LWLock *lock)
Definition: lwlock.c:1816
VariableCache ShmemVariableCache
Definition: varsup.c:34
TransactionId next_xid
Definition: verify_heapam.c:85
bool LWLockAcquire(LWLock *lock, LWLockMode mode)
Definition: lwlock.c:1203
static FullTransactionId FullTransactionIdFromXidAndCtx(TransactionId xid, const HeapCheckContext *ctx)

◆ verify_heapam()

Datum verify_heapam ( PG_FUNCTION_ARGS  )

Definition at line 212 of file verify_heapam.c.

References AccessShareLock, ReturnSetInfo::allowedModes, BAS_BULKREAD, BUFFER_LOCK_SHARE, BufferGetPage, check_toasted_attribute(), check_tuple(), ReturnSetInfo::econtext, ExprContext::ecxt_per_query_memory, ereport, errcode(), errhint(), errmsg(), ERROR, fb(), FirstOffsetNumber, FullTransactionIdFromXidAndCtx(), GetAccessStrategy(), GetTransactionSnapshot(), HeapTupleHeaderGetNatts, InvalidBuffer, InvalidTransactionId, IsA, ItemIdGetLength, ItemIdGetOffset, ItemIdGetRedirect, ItemIdIsDead, ItemIdIsRedirected, ItemIdIsUsed, lfirst, list_free_deep(), LockBuffer(), MAIN_FORKNUM, MAXALIGN, MemoryContextSwitchTo(), NIL, OffsetNumberNext, PageGetItem, PageGetItemId, PageGetMaxOffsetNumber, PG_ARGISNULL, PG_GETARG_BOOL, PG_GETARG_INT64, PG_GETARG_OID, PG_GETARG_TEXT_PP, PG_RETURN_NULL, pg_strcasecmp(), psprintf(), RBM_NORMAL, ReadBufferExtended(), relation_close(), relation_open(), RelationGetNumberOfBlocks, ReleaseBuffer(), report_corruption(), ReturnSetInfo::returnMode, sanity_check_relation(), ReturnSetInfo::setDesc, ReturnSetInfo::setResult, SFRM_Materialize, SFRM_Materialize_Random, SizeofHeapTupleHeader, skip, SKIP_PAGES_ALL_FROZEN, SKIP_PAGES_ALL_VISIBLE, SKIP_PAGES_NONE, table_close(), table_open(), text_to_cstring(), toast_close_indexes(), toast_open_indexes(), TransactionIdIsNormal, tuplestore_begin_heap(), UnlockReleaseBuffer(), update_cached_mxid_range(), update_cached_xid_range(), verify_heapam_tupdesc(), VISIBILITYMAP_ALL_FROZEN, VISIBILITYMAP_ALL_VISIBLE, visibilitymap_get_status(), work_mem, and SnapshotData::xmin.

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 }
BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype)
Definition: freelist.c:542
#define NIL
Definition: pg_list.h:65
static void check_toasted_attribute(HeapCheckContext *ctx, ToastedAttribute *ta)
#define SizeofHeapTupleHeader
Definition: htup_details.h:184
#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 ItemIdIsRedirected(itemId)
Definition: itemid.h:106
HeapTupleHeaderData * HeapTupleHeader
Definition: htup.h:23
#define ItemIdGetRedirect(itemId)
Definition: itemid.h:78
#define VISIBILITYMAP_ALL_FROZEN
Definition: visibilitymap.h:27
char * psprintf(const char *fmt,...)
Definition: psprintf.c:46
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
#define AccessShareLock
Definition: lockdefs.h:36
#define InvalidBuffer
Definition: buf.h:25
int errcode(int sqlerrcode)
Definition: elog.c:698
static void report_corruption(HeapCheckContext *ctx, char *msg)
uint32 BlockNumber
Definition: block.h:31
void ReleaseBuffer(Buffer buffer)
Definition: bufmgr.c:3784
#define PG_GETARG_BOOL(n)
Definition: fmgr.h:274
int pg_strcasecmp(const char *s1, const char *s2)
Definition: pgstrcasecmp.c:36
unsigned int Oid
Definition: postgres_ext.h:31
static int fb(int x)
Definition: preproc-init.c:92
#define ItemIdIsDead(itemId)
Definition: itemid.h:113
Snapshot GetTransactionSnapshot(void)
Definition: snapmgr.c:250
#define PageGetMaxOffsetNumber(page)
Definition: bufpage.h:357
void list_free_deep(List *list)
Definition: list.c:1405
signed int int32
Definition: c.h:429
#define PG_GETARG_TEXT_PP(n)
Definition: fmgr.h:309
uint16 OffsetNumber
Definition: off.h:24
static void update_cached_xid_range(HeapCheckContext *ctx)
#define ItemIdGetLength(itemId)
Definition: itemid.h:59
void UnlockReleaseBuffer(Buffer buffer)
Definition: bufmgr.c:3807
#define ERROR
Definition: elog.h:46
#define HeapTupleHeaderGetNatts(tup)
Definition: htup_details.h:530
Relation relation_open(Oid relationId, LOCKMODE lockmode)
Definition: relation.c:48
static void sanity_check_relation(Relation rel)
#define PG_GETARG_OID(n)
Definition: fmgr.h:275
#define FirstOffsetNumber
Definition: off.h:27
void toast_close_indexes(Relation *toastidxs, int num_indexes, LOCKMODE lock)
#define InvalidTransactionId
Definition: transam.h:31
#define ItemIdGetOffset(itemId)
Definition: itemid.h:65
TransactionId xmin
Definition: snapshot.h:157
static void update_cached_mxid_range(HeapCheckContext *ctx)
#define BufferGetPage(buffer)
Definition: bufmgr.h:169
static void check_tuple(HeapCheckContext *ctx)
#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 LockBuffer(Buffer buffer, int mode)
Definition: bufmgr.c:4023
int work_mem
Definition: globals.c:124
#define RelationGetNumberOfBlocks(reln)
Definition: bufmgr.h:213
SkipPages
Definition: verify_heapam.c:54
#define ereport(elevel,...)
Definition: elog.h:157
int allowedModes
Definition: execnodes.h:305
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 lfirst(lc)
Definition: pg_list.h:169
static TupleDesc verify_heapam_tupdesc(void)
#define OffsetNumberNext(offsetNumber)
Definition: off.h:52
#define MAXALIGN(LEN)
Definition: c.h:757
MemoryContext ecxt_per_query_memory
Definition: execnodes.h:233
#define VISIBILITYMAP_ALL_VISIBLE
Definition: visibilitymap.h:26
Tuplestorestate * setResult
Definition: execnodes.h:310
char * text_to_cstring(const text *t)
Definition: varlena.c:223
ExprContext * econtext
Definition: execnodes.h:303
TupleDesc setDesc
Definition: execnodes.h:311
int errmsg(const char *fmt,...)
Definition: elog.c:909
uint8 visibilitymap_get_status(Relation rel, BlockNumber heapBlk, Buffer *buf)
#define BUFFER_LOCK_SHARE
Definition: bufmgr.h:97
static FullTransactionId FullTransactionIdFromXidAndCtx(TransactionId xid, const HeapCheckContext *ctx)
#define TransactionIdIsNormal(xid)
Definition: transam.h:42
Relation table_open(Oid relationId, LOCKMODE lockmode)
Definition: table.c:39
#define PG_GETARG_INT64(n)
Definition: fmgr.h:283
int Buffer
Definition: buf.h:23
#define PG_RETURN_NULL()
Definition: fmgr.h:345
#define PageGetItem(page, itemId)
Definition: bufpage.h:340

◆ verify_heapam_tupdesc()

static TupleDesc verify_heapam_tupdesc ( void  )
static

Definition at line 618 of file verify_heapam.c.

References Assert, BlessTupleDesc(), CreateTemplateTupleDesc(), HEAPCHECK_RELATION_COLS, and TupleDescInitEntry().

Referenced by verify_heapam().

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 }
TupleDesc CreateTemplateTupleDesc(int natts)
Definition: tupdesc.c:45
#define HEAPCHECK_RELATION_COLS
Definition: verify_heapam.c:31
TupleDesc BlessTupleDesc(TupleDesc tupdesc)
Definition: execTuples.c:2082
void TupleDescInitEntry(TupleDesc desc, AttrNumber attributeNumber, const char *attributeName, Oid oidtypeid, int32 typmod, int attdim)
Definition: tupdesc.c:583
#define Assert(condition)
Definition: c.h:804
int16 AttrNumber
Definition: attnum.h:21