30#include "utils/fmgroids.h"
36#define HEAPCHECK_RELATION_COLS 4
39#define VARLENA_SIZE_LIMIT 0x3FFFFFFF
183 void *callback_private_data,
184 void *per_buffer_data);
187 bool *xmin_commit_status_ok,
199 bool *xmin_commit_status_ok,
272 (
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
273 errmsg(
"relation cannot be null")));
278 (
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
279 errmsg(
"on_error_stop cannot be null")));
284 (
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
285 errmsg(
"check_toast cannot be null")));
290 (
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
291 errmsg(
"skip cannot be null")));
301 (
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
302 errmsg(
"invalid skip option"),
303 errhint(
"Valid skip options are \"all-visible\", \"all-frozen\", and \"none\".")));
307 ctx.toasted_attributes =
NIL;
333 if (!RELKIND_HAS_TABLE_AM(ctx.rel->rd_rel->relkind) &&
334 ctx.rel->rd_rel->relkind != RELKIND_SEQUENCE)
336 (
errcode(ERRCODE_WRONG_OBJECT_TYPE),
337 errmsg(
"cannot check relation \"%s\"",
345 if (ctx.rel->rd_rel->relkind != RELKIND_SEQUENCE &&
346 ctx.rel->rd_rel->relam != HEAP_TABLE_AM_OID)
348 (
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
349 errmsg(
"only heap AM is supported")));
356 if (ctx.rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED &&
360 (
errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
361 errmsg(
"cannot verify unlogged relation \"%s\" during recovery, skipping",
386 if (fb < 0 || fb >= nblocks)
388 (
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
389 errmsg(
"starting block number must be between 0 and %u",
394 last_block = nblocks - 1;
399 if (lb < 0 || lb >= nblocks)
401 (
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
402 errmsg(
"ending block number must be between 0 and %u",
408 if (ctx.rel->rd_rel->reltoastrelid && check_toast)
413 ctx.toast_rel =
table_open(ctx.rel->rd_rel->reltoastrelid,
417 &(ctx.toast_indexes),
418 &(ctx.num_toast_indexes));
419 ctx.valid_toast_index = ctx.toast_indexes[offset];
427 ctx.toast_rel = NULL;
428 ctx.toast_indexes = NULL;
429 ctx.num_toast_indexes = 0;
434 ctx.relfrozenxid = ctx.rel->rd_rel->relfrozenxid;
436 ctx.relminmxid = ctx.rel->rd_rel->relminmxid;
439 ctx.oldest_xid = ctx.relfrozenxid;
442 stream_skip_data.range.current_blocknum = first_block;
443 stream_skip_data.range.last_exclusive = last_block + 1;
444 stream_skip_data.skip_option = skip_option;
445 stream_skip_data.rel = ctx.rel;
446 stream_skip_data.vmbuffer = &vmbuffer;
458 stream_data = &stream_skip_data.range;
469 stream_data = &stream_skip_data;
509 lp_valid[ctx.offnum] =
false;
510 xmin_commit_status_ok[ctx.offnum] =
false;
529 psprintf(
"line pointer redirection to item at offset %u precedes minimum offset %u",
534 if (rdoffnum > maxoff)
537 psprintf(
"line pointer redirection to item at offset %u exceeds maximum offset %u",
553 psprintf(
"redirected line pointer points to an unused item at offset %u",
554 (
unsigned) rdoffnum));
560 psprintf(
"redirected line pointer points to a dead item at offset %u",
561 (
unsigned) rdoffnum));
567 psprintf(
"redirected line pointer points to another redirected line pointer at offset %u",
568 (
unsigned) rdoffnum));
577 lp_valid[ctx.offnum] =
true;
578 successor[ctx.offnum] = rdoffnum;
586 if (ctx.lp_off !=
MAXALIGN(ctx.lp_off))
589 psprintf(
"line pointer to page offset %u is not maximally aligned",
596 psprintf(
"line pointer length %u is less than the minimum tuple header size %u",
601 if (ctx.lp_off + ctx.lp_len > BLCKSZ)
604 psprintf(
"line pointer to page offset %u with length %u ends beyond maximum page offset %u",
612 lp_valid[ctx.offnum] =
true;
618 &xmin_commit_status_ok[ctx.offnum],
619 &xmin_commit_status[ctx.offnum]);
628 if (nextblkno == ctx.blkno && nextoffnum != ctx.offnum &&
630 successor[ctx.offnum] = nextoffnum;
681 psprintf(
"redirected line pointer points to a non-heap-only tuple at offset %u",
682 (
unsigned) nextoffnum));
689 psprintf(
"redirect line pointer points to offset %u, but offset %u also points there",
690 (
unsigned) nextoffnum, (
unsigned) predecessor[nextoffnum]));
698 predecessor[nextoffnum] = ctx.offnum;
722 psprintf(
"tuple points to new version at offset %u, but offset %u also points there",
723 (
unsigned) nextoffnum, (
unsigned) predecessor[nextoffnum]));
731 predecessor[nextoffnum] = ctx.offnum;
746 psprintf(
"non-heap-only update produced a heap-only tuple at offset %u",
747 (
unsigned) nextoffnum));
753 psprintf(
"heap-only update produced a non-heap only tuple at offset %u",
754 (
unsigned) nextoffnum));
768 if (xmin_commit_status_ok[ctx.offnum] &&
770 xmin_commit_status_ok[nextoffnum] &&
775 psprintf(
"tuple with in-progress xmin %u was updated to produce a tuple at offset %u with committed xmin %u",
776 (
unsigned) curr_xmin,
777 (
unsigned) ctx.offnum,
778 (
unsigned) next_xmin));
785 if (xmin_commit_status_ok[ctx.offnum] &&
787 xmin_commit_status_ok[nextoffnum])
791 psprintf(
"tuple with aborted xmin %u was updated to produce a tuple at offset %u with in-progress xmin %u",
792 (
unsigned) curr_xmin,
793 (
unsigned) ctx.offnum,
794 (
unsigned) next_xmin));
797 psprintf(
"tuple with aborted xmin %u was updated to produce a tuple at offset %u with committed xmin %u",
798 (
unsigned) curr_xmin,
799 (
unsigned) ctx.offnum,
800 (
unsigned) next_xmin));
812 ctx.offnum <= maxoff;
815 if (xmin_commit_status_ok[ctx.offnum] &&
831 psprintf(
"tuple is root of chain but is marked as heap-only tuple"));
842 if (ctx.toasted_attributes !=
NIL)
846 foreach(cell, ctx.toasted_attributes)
849 ctx.toasted_attributes =
NIL;
852 if (on_error_stop && ctx.is_corrupt)
862 if (ctx.toast_indexes)
882 void *callback_private_data,
883 void *per_buffer_data)
1005 unsigned expected_hoff;
1010 psprintf(
"data begins at offset %u beyond the tuple length %u",
1019 pstrdup(
"multixact should not be marked committed"));
1033 psprintf(
"tuple has been HOT updated, but xmax is 0"));
1046 psprintf(
"tuple is heap only, but not the result of an update"));
1059 psprintf(
"tuple data should begin at byte %u, but actually begins at byte %u (1 attribute, has nulls)",
1063 psprintf(
"tuple data should begin at byte %u, but actually begins at byte %u (%u attributes, has nulls)",
1065 else if (ctx->
natts == 1)
1067 psprintf(
"tuple data should begin at byte %u, but actually begins at byte %u (1 attribute, no nulls)",
1071 psprintf(
"tuple data should begin at byte %u, but actually begins at byte %u (%u attributes, no nulls)",
1124 *xmin_commit_status_ok =
false;
1134 *xmin_commit_status_ok =
true;
1135 *xmin_commit_status = xmin_status;
1139 psprintf(
"xmin %u equals or exceeds next valid transaction ID %u:%u",
1146 psprintf(
"xmin %u precedes oldest valid transaction ID %u:%u",
1153 psprintf(
"xmin %u precedes relation freeze threshold %u:%u",
1176 pstrdup(
"old-style VACUUM FULL transaction ID for moved off tuple is invalid"));
1180 psprintf(
"old-style VACUUM FULL transaction ID %u for moved off tuple equals or exceeds next valid transaction ID %u:%u",
1187 psprintf(
"old-style VACUUM FULL transaction ID %u for moved off tuple precedes relation freeze threshold %u:%u",
1194 psprintf(
"old-style VACUUM FULL transaction ID %u for moved off tuple precedes oldest valid transaction ID %u:%u",
1203 switch (xvac_status)
1207 psprintf(
"old-style VACUUM FULL transaction ID %u for moved off tuple matches our current transaction ID",
1212 psprintf(
"old-style VACUUM FULL transaction ID %u for moved off tuple appears to be in progress",
1245 pstrdup(
"old-style VACUUM FULL transaction ID for moved in tuple is invalid"));
1249 psprintf(
"old-style VACUUM FULL transaction ID %u for moved in tuple equals or exceeds next valid transaction ID %u:%u",
1256 psprintf(
"old-style VACUUM FULL transaction ID %u for moved in tuple precedes relation freeze threshold %u:%u",
1263 psprintf(
"old-style VACUUM FULL transaction ID %u for moved in tuple precedes oldest valid transaction ID %u:%u",
1272 switch (xvac_status)
1276 psprintf(
"old-style VACUUM FULL transaction ID %u for moved in tuple matches our current transaction ID",
1281 psprintf(
"old-style VACUUM FULL transaction ID %u for moved in tuple appears to be in progress",
1348 pstrdup(
"multitransaction ID is invalid"));
1352 psprintf(
"multitransaction ID %u precedes relation minimum multitransaction ID threshold %u",
1357 psprintf(
"multitransaction ID %u precedes oldest valid multitransaction ID threshold %u",
1362 psprintf(
"multitransaction ID %u equals or exceeds next valid multitransaction ID %u",
1406 pstrdup(
"update xid is invalid"));
1410 psprintf(
"update xid %u equals or exceeds next valid transaction ID %u:%u",
1417 psprintf(
"update xid %u precedes relation freeze threshold %u:%u",
1424 psprintf(
"update xid %u precedes oldest valid transaction ID %u:%u",
1433 switch (xmax_status)
1475 psprintf(
"xmax %u equals or exceeds next valid transaction ID %u:%u",
1482 psprintf(
"xmax %u precedes relation freeze threshold %u:%u",
1489 psprintf(
"xmax %u precedes oldest valid transaction ID %u:%u",
1502 switch (xmax_status)
1563 int32 expected_size;
1571 psprintf(
"toast value %u has toast chunk with null sequence number",
1575 if (chunk_seq != *expected_chunk_seq)
1579 psprintf(
"toast value %u index scan returned chunk %d when expecting chunk %d",
1581 chunk_seq, *expected_chunk_seq));
1583 *expected_chunk_seq = chunk_seq + 1;
1591 psprintf(
"toast value %u chunk %d has null data",
1611 psprintf(
"toast value %u chunk %d has invalid varlena header %0x",
1613 chunk_seq, header));
1620 if (chunk_seq > last_chunk_seq)
1623 psprintf(
"toast value %u chunk %d follows last expected chunk %d",
1625 chunk_seq, last_chunk_seq));
1632 if (chunksize != expected_size)
1634 psprintf(
"toast value %u chunk %d has size %u, but expected size %u",
1636 chunk_seq, chunksize, expected_size));
1677 psprintf(
"attribute with length %u starts at offset %u beyond total tuple length %u",
1689 if (thisatt->
attlen != -1)
1697 psprintf(
"attribute with length %u ends at offset %u beyond total tuple length %u",
1729 psprintf(
"toasted attribute has unexpected TOAST tag %u",
1743 psprintf(
"attribute with length %u ends at offset %u beyond total tuple length %u",
1778 psprintf(
"toast value %u rawsize %d exceeds limit %d",
1806 psprintf(
"toast value %u has invalid compression method id %d",
1814 psprintf(
"toast value %u is external but tuple header flag HEAP_HASEXTERNAL not set",
1823 psprintf(
"toast value %u is external but relation has no toast relation",
1864 bool found_toasttup;
1867 int32 expected_chunk_seq = 0;
1868 int32 last_chunk_seq;
1889 found_toasttup =
false;
1894 found_toasttup =
true;
1899 if (!found_toasttup)
1901 psprintf(
"toast value %u not found in toast table",
1903 else if (expected_chunk_seq <= last_chunk_seq)
1905 psprintf(
"toast value %u was expected to end at chunk %d, but ended while expecting chunk %d",
1907 last_chunk_seq, expected_chunk_seq));
1934 xmin_commit_status))
1945 psprintf(
"number of attributes %u exceeds maximum expected for table %u",
#define InvalidBlockNumber
static Datum values[MAXATTR]
BlockNumber BufferGetBlockNumber(Buffer buffer)
void ReleaseBuffer(Buffer buffer)
void UnlockReleaseBuffer(Buffer buffer)
void LockBuffer(Buffer buffer, int mode)
#define BUFFER_LOCK_SHARE
#define RelationGetNumberOfBlocks(reln)
static Page BufferGetPage(Buffer buffer)
static bool BufferIsValid(Buffer bufnum)
static Item PageGetItem(const PageData *page, const ItemIdData *itemId)
static ItemId PageGetItemId(Page page, OffsetNumber offsetNumber)
static OffsetNumber PageGetMaxOffsetNumber(const PageData *page)
#define CStringGetTextDatum(s)
TransactionId MultiXactId
#define VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr)
int errhint(const char *fmt,...)
int errcode(int sqlerrcode)
int errmsg(const char *fmt,...)
#define ereport(elevel,...)
Datum Int64GetDatum(int64 X)
#define PG_GETARG_TEXT_PP(n)
#define PG_GETARG_INT64(n)
#define PG_GETARG_BOOL(n)
BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype)
void InitMaterializedSRF(FunctionCallInfo fcinfo, bits32 flags)
SysScanDesc systable_beginscan_ordered(Relation heapRelation, Relation indexRelation, Snapshot snapshot, int nkeys, ScanKey key)
void systable_endscan_ordered(SysScanDesc sysscan)
HeapTuple systable_getnext_ordered(SysScanDesc sysscan, ScanDirection direction)
Assert(PointerIsAligned(start, uint64))
TransactionId HeapTupleGetUpdateXid(const HeapTupleHeaderData *tup)
#define TOAST_MAX_CHUNK_SIZE
HeapTuple heap_form_tuple(TupleDesc tupleDescriptor, const Datum *values, const bool *isnull)
HeapTupleHeaderData * HeapTupleHeader
#define HeapTupleHeaderGetNatts(tup)
#define SizeofHeapTupleHeader
static bool HEAP_XMAX_IS_LOCKED_ONLY(uint16 infomask)
static int BITMAPLEN(int NATTS)
static bool HeapTupleHeaderXminInvalid(const HeapTupleHeaderData *tup)
static TransactionId HeapTupleHeaderGetXvac(const HeapTupleHeaderData *tup)
static TransactionId HeapTupleHeaderGetRawXmax(const HeapTupleHeaderData *tup)
static bool HeapTupleHeaderIsHeapOnly(const HeapTupleHeaderData *tup)
#define HEAP_XMAX_IS_MULTI
#define HEAP_XMAX_COMMITTED
static TransactionId HeapTupleHeaderGetXmin(const HeapTupleHeaderData *tup)
static bool HeapTupleHeaderIsHotUpdated(const HeapTupleHeaderData *tup)
#define HEAP_XMAX_INVALID
static TransactionId HeapTupleHeaderGetUpdateXid(const HeapTupleHeaderData *tup)
static Datum fastgetattr(HeapTuple tup, int attnum, TupleDesc tupleDesc, bool *isnull)
static bool HeapTupleHeaderXminCommitted(const HeapTupleHeaderData *tup)
if(TABLE==NULL||TABLE_index==NULL)
#define ItemIdGetLength(itemId)
#define ItemIdIsNormal(itemId)
#define ItemIdGetOffset(itemId)
#define ItemIdGetRedirect(itemId)
#define ItemIdIsDead(itemId)
#define ItemIdIsUsed(itemId)
#define ItemIdIsRedirected(itemId)
static OffsetNumber ItemPointerGetOffsetNumber(const ItemPointerData *pointer)
static BlockNumber ItemPointerGetBlockNumber(const ItemPointerData *pointer)
List * lappend(List *list, void *datum)
void list_free_deep(List *list)
bool LWLockAcquire(LWLock *lock, LWLockMode mode)
void LWLockRelease(LWLock *lock)
char * pstrdup(const char *in)
void pfree(void *pointer)
void * palloc0(Size size)
#define CHECK_FOR_INTERRUPTS()
void ReadMultiXactIdRange(MultiXactId *oldest, MultiXactId *next)
bool MultiXactIdPrecedes(MultiXactId multi1, MultiXactId multi2)
bool MultiXactIdPrecedesOrEquals(MultiXactId multi1, MultiXactId multi2)
#define InvalidOffsetNumber
#define OffsetNumberNext(offsetNumber)
#define FirstOffsetNumber
static const struct exclude_list_item skip[]
int errdetail_relkind_not_supported(char relkind)
int pg_strcasecmp(const char *s1, const char *s2)
static Datum ObjectIdGetDatum(Oid X)
static Pointer DatumGetPointer(Datum X)
static Datum Int32GetDatum(int32 X)
static int32 DatumGetInt32(Datum X)
bool TransactionIdIsInProgress(TransactionId xid)
char * psprintf(const char *fmt,...)
Buffer read_stream_next_buffer(ReadStream *stream, void **per_buffer_data)
ReadStream * read_stream_begin_relation(int flags, BufferAccessStrategy strategy, Relation rel, ForkNumber forknum, ReadStreamBlockNumberCB callback, void *callback_private_data, size_t per_buffer_data_size)
void read_stream_end(ReadStream *stream)
BlockNumber block_range_read_stream_cb(ReadStream *stream, void *callback_private_data, void *per_buffer_data)
#define READ_STREAM_USE_BATCHING
BlockNumber(* ReadStreamBlockNumberCB)(ReadStream *stream, void *callback_private_data, void *per_buffer_data)
#define READ_STREAM_DEFAULT
#define READ_STREAM_SEQUENTIAL
#define RelationGetDescr(relation)
#define RelationGetRelationName(relation)
void ScanKeyInit(ScanKey entry, AttrNumber attributeNumber, StrategyNumber strategy, RegProcedure procedure, Datum argument)
Snapshot GetTransactionSnapshot(void)
void relation_close(Relation relation, LOCKMODE lockmode)
Relation relation_open(Oid relationId, LOCKMODE lockmode)
#define BTEqualStrategyNumber
BlockNumber last_exclusive
BlockNumber current_blocknum
Tuplestorestate * tupstore
bool tuple_could_be_pruned
FullTransactionId oldest_fxid
BufferAccessStrategy bstrategy
TransactionId relfrozenxid
FullTransactionId next_fxid
List * toasted_attributes
FullTransactionId relfrozenfxid
Relation valid_toast_index
XidCommitStatus cached_status
BlockRangeReadStreamPrivate range
Tuplestorestate * setResult
struct varatt_external toast_pointer
FullTransactionId nextXid
TransactionId oldestClogXid
void table_close(Relation relation, LOCKMODE lockmode)
Relation table_open(Oid relationId, LOCKMODE lockmode)
@ TOAST_INVALID_COMPRESSION_ID
@ TOAST_LZ4_COMPRESSION_ID
@ TOAST_PGLZ_COMPRESSION_ID
void toast_close_indexes(Relation *toastidxs, int num_indexes, LOCKMODE lock)
int toast_open_indexes(Relation toastrel, LOCKMODE lock, Relation **toastidxs, int *num_indexes)
Snapshot get_toast_snapshot(void)
#define TOAST_COMPRESS_METHOD(ptr)
bool TransactionIdDidCommit(TransactionId transactionId)
bool TransactionIdPrecedes(TransactionId id1, TransactionId id2)
#define FullTransactionIdIsNormal(x)
#define FrozenTransactionId
#define InvalidTransactionId
#define FullTransactionIdPrecedesOrEquals(a, b)
#define EpochFromFullTransactionId(x)
#define U64FromFullTransactionId(x)
static FullTransactionId FullTransactionIdFromU64(uint64 value)
#define TransactionIdEquals(id1, id2)
#define BootstrapTransactionId
#define XidFromFullTransactionId(x)
#define FirstNormalTransactionId
#define TransactionIdIsValid(xid)
static FullTransactionId FullTransactionIdFromEpochAndXid(uint32 epoch, TransactionId xid)
#define TransactionIdIsNormal(xid)
#define FirstNormalFullTransactionId
#define FullTransactionIdPrecedes(a, b)
static CompactAttribute * TupleDescCompactAttr(TupleDesc tupdesc, int i)
void tuplestore_puttuple(Tuplestorestate *state, HeapTuple tuple)
#define att_nominal_alignby(cur_offset, attalignby)
static bool att_isnull(int ATT, const bits8 *BITS)
#define att_addlength_pointer(cur_offset, attlen, attptr)
#define att_pointer_alignby(cur_offset, attalignby, attlen, attptr)
#define VARSIZE_SHORT(PTR)
#define VARATT_IS_EXTENDED(PTR)
#define VARATT_IS_SHORT(PTR)
#define VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer)
#define VARATT_EXTERNAL_GET_EXTSIZE(toast_pointer)
#define VARTAG_EXTERNAL(PTR)
#define VARATT_IS_EXTERNAL(PTR)
char * text_to_cstring(const text *t)
TransamVariablesData * TransamVariables
static XidBoundsViolation check_mxid_valid_in_rel(MultiXactId mxid, HeapCheckContext *ctx)
static void check_toasted_attribute(HeapCheckContext *ctx, ToastedAttribute *ta)
PG_FUNCTION_INFO_V1(verify_heapam)
#define VARLENA_SIZE_LIMIT
static XidBoundsViolation get_xid_status(TransactionId xid, HeapCheckContext *ctx, XidCommitStatus *status)
@ XID_PRECEDES_CLUSTERMIN
static void report_corruption_internal(Tuplestorestate *tupstore, TupleDesc tupdesc, BlockNumber blkno, OffsetNumber offnum, AttrNumber attnum, char *msg)
static bool check_tuple_visibility(HeapCheckContext *ctx, bool *xmin_commit_status_ok, XidCommitStatus *xmin_commit_status)
#define HEAPCHECK_RELATION_COLS
struct HeapCheckReadStreamData HeapCheckReadStreamData
struct HeapCheckContext HeapCheckContext
static void check_toast_tuple(HeapTuple toasttup, HeapCheckContext *ctx, ToastedAttribute *ta, int32 *expected_chunk_seq, uint32 extsize)
static bool fxid_in_cached_range(FullTransactionId fxid, const HeapCheckContext *ctx)
static XidBoundsViolation check_mxid_in_range(MultiXactId mxid, HeapCheckContext *ctx)
static void update_cached_mxid_range(HeapCheckContext *ctx)
static void update_cached_xid_range(HeapCheckContext *ctx)
static FullTransactionId FullTransactionIdFromXidAndCtx(TransactionId xid, const HeapCheckContext *ctx)
static void check_tuple(HeapCheckContext *ctx, bool *xmin_commit_status_ok, XidCommitStatus *xmin_commit_status)
struct ToastedAttribute ToastedAttribute
static bool check_tuple_header(HeapCheckContext *ctx)
static void report_toast_corruption(HeapCheckContext *ctx, ToastedAttribute *ta, char *msg)
static bool check_tuple_attribute(HeapCheckContext *ctx)
static void report_corruption(HeapCheckContext *ctx, char *msg)
static BlockNumber heapcheck_read_stream_next_unskippable(ReadStream *stream, void *callback_private_data, void *per_buffer_data)
Datum verify_heapam(PG_FUNCTION_ARGS)
uint8 visibilitymap_get_status(Relation rel, BlockNumber heapBlk, Buffer *vmbuf)
#define VISIBILITYMAP_ALL_FROZEN
#define VISIBILITYMAP_ALL_VISIBLE
bool TransactionIdIsCurrentTransactionId(TransactionId xid)
bool RecoveryInProgress(void)