From f3ae2f783a255109655cade770ebc74e01aef34c Mon Sep 17 00:00:00 2001
From: Himanshu Upadhyaya <himanshu.upadhyaya@enterprisedb.com>
Date: Thu, 18 Aug 2022 13:20:51 +0530
Subject: [PATCH v1] HOT chain validation in verify_heapam.

---
 contrib/amcheck/verify_heapam.c | 144 ++++++++++++++++++++++++++++++++
 1 file changed, 144 insertions(+)

diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index c875f3e5a2..ae2c100de2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -399,6 +399,7 @@ verify_heapam(PG_FUNCTION_ARGS)
 	for (ctx.blkno = first_block; ctx.blkno <= last_block; ctx.blkno++)
 	{
 		OffsetNumber maxoff;
+		OffsetNumber predecessor[MaxOffsetNumber] = {0};
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -433,6 +434,9 @@ verify_heapam(PG_FUNCTION_ARGS)
 		for (ctx.offnum = FirstOffsetNumber; ctx.offnum <= maxoff;
 			 ctx.offnum = OffsetNumberNext(ctx.offnum))
 		{
+			OffsetNumber nextTupOffnum;
+			HeapTupleHeader htup;
+
 			ctx.itemid = PageGetItemId(ctx.page, ctx.offnum);
 
 			/* Skip over unused/dead line pointers */
@@ -469,6 +473,13 @@ verify_heapam(PG_FUNCTION_ARGS)
 					report_corruption(&ctx,
 									  psprintf("line pointer redirection to unused item at offset %u",
 											   (unsigned) rdoffnum));
+
+				htup = (HeapTupleHeader) PageGetItem(ctx.page, rditem);
+				if (!(HeapTupleHeaderIsHeapOnly(htup) && htup->t_infomask & HEAP_UPDATED))
+					report_corruption(&ctx,
+									  psprintf("Redirected Tuple at line pointer offset %u is not HEAP_ONLY_TUPLE or HEAP_UPDATED tuple",
+											   (unsigned) rdoffnum));
+
 				continue;
 			}
 
@@ -507,6 +518,139 @@ verify_heapam(PG_FUNCTION_ARGS)
 
 			/* Ok, ready to check this next tuple */
 			check_tuple(&ctx);
+			/*
+			 * Add line pointer offset to predecessor array.
+			 * 1) If xmax is matching with xmin of next Tuple(reaching via its t_ctid).
+			 * 2) If next tuple is in the same page.
+			 * Raise corruption if:
+			 * We have two tuples having same predecessor.
+			 *
+			 * We add offset to predecessor irrespective of transaction(t_xmin) status. We will
+			 * do validation related to transaction status(and also all other validations)
+			 * when we loop over predecessor array.
+			 */
+			nextTupOffnum = ItemPointerGetOffsetNumber(&(ctx.tuphdr)->t_ctid);
+			if (ItemPointerGetBlockNumber(&(ctx.tuphdr)->t_ctid) == ctx.blkno &&
+				nextTupOffnum != ctx.offnum)
+			{
+				TransactionId currTupXmax;
+				ItemId lp;
+
+				if (predecessor[nextTupOffnum] != 0)
+				{
+					report_corruption(&ctx,
+								psprintf("Tuple at offset %u is reachable from two or more updated tuple",
+									(unsigned) nextTupOffnum));
+					continue;
+				}
+				currTupXmax = HeapTupleHeaderGetUpdateXid(ctx.tuphdr);
+				lp   = PageGetItemId(ctx.page, nextTupOffnum);
+
+				htup = (HeapTupleHeader) PageGetItem(ctx.page, lp);
+
+				if (TransactionIdIsValid(currTupXmax) &&
+					TransactionIdEquals(HeapTupleHeaderGetXmin(htup), currTupXmax))
+				{
+					predecessor[nextTupOffnum] = ctx.offnum;
+				}
+				/* Non matching XMAX with XMIN is not a corruption*/
+			}
+
+		}
+		/* Now loop over offset and validate data in predecessor array.*/
+		for ( ctx.offnum = FirstOffsetNumber; ctx.offnum <= maxoff;
+			 ctx.offnum = OffsetNumberNext(ctx.offnum))
+		{
+			HeapTupleHeader	pred_htup, curr_htup;
+			TransactionId	pred_xmin, curr_xmin, curr_xmax;
+			ItemId		pred_lp, curr_lp;
+
+			OffsetNumber predoffset = predecessor[ctx.offnum];
+			if (!predoffset)
+			{
+				/*
+				 * Either root of chain or xmin-aborted tuple from an abandoned portion of
+				 * a HOT chain.
+				 */
+				continue;
+			}
+			/*
+			 * Validation via predecessor array:
+			 * 1) If the predecessor’s xmin is aborted or in progress, the current
+			 *    tuples xmin should be aborted or in progress respectively. Also
+			 *    Both xmin must be equal.
+			 * 2) If the predecessor’s xmin is not frozen, then current tuple’s
+			 *    shouldn’t be either.
+			 * 3) If the predecessor’s xmin is equal to the current tuple’s xmin,
+			 *    the current tuple’s cmin should be greater than predecessor’s xmin.
+			 * 4) If the current tuple is not HOT then its predecessor’s tuple must
+			 *    not be HEAP_HOT_UPDATED.
+			 * 5) If the current Tuple is HOT then its predecessor’s tuple must be
+			 *    HEAP_HOT_UPDATED and vice-versa.
+			 * 6) If xmax is 0, means it's last tuple in chain, then it must not
+			 *    be HEAP_HOT_UPDATED.
+			 * 7) If current tuple is the last tuple in HOT chain then next tuple
+			 *    should not be HOT.
+			 */
+			curr_lp = PageGetItemId(ctx.page, ctx.offnum);
+			curr_htup = (HeapTupleHeader) PageGetItem(ctx.page, curr_lp);
+			curr_xmin = HeapTupleHeaderGetXmin(curr_htup);
+			curr_xmax = HeapTupleHeaderGetUpdateXid(curr_htup);
+
+			pred_lp = PageGetItemId(ctx.page, predoffset);
+			pred_htup = (HeapTupleHeader) PageGetItem(ctx.page, pred_lp);
+			pred_xmin = HeapTupleHeaderGetXmin(pred_htup);
+
+			if ((TransactionIdDidAbort(pred_xmin)||TransactionIdIsInProgress(pred_xmin))
+				&& !TransactionIdEquals(pred_xmin, curr_xmin))
+			{
+				report_corruption(&ctx,
+							psprintf("Preceding tuple's(at offset %u) xmin is aborted or in-progress but is not equal to current tuple xmin at offset %u",
+							(unsigned) predoffset, (unsigned) ctx.offnum));
+			}
+			if (pred_xmin != FrozenTransactionId && curr_xmin == FrozenTransactionId)
+			{
+				report_corruption(&ctx,
+							psprintf("Non frozen-xmin tuple precedes current frozen tuple at offset %u",
+							(unsigned) ctx.offnum));
+			}
+			if (pred_xmin == curr_xmin &&
+				HeapTupleHeaderGetCmin(pred_htup) >=
+				HeapTupleHeaderGetCmin(curr_htup))
+			{
+				report_corruption(&ctx,
+							psprintf("Predecessor tuple was also inserted by %u transaction but cmin of predecessor's tuple does not precede current tuple.",
+							(unsigned) pred_xmin));
+			}
+			if (HeapTupleHeaderIsHeapOnly(curr_htup) &&
+				!HeapTupleHeaderIsHotUpdated(pred_htup))
+			{
+				report_corruption(&ctx,
+							psprintf("Current tuple at offset %u is HOT but its predecessor is not HEAP HOT UPDATED.",
+							(unsigned) ctx.offnum));
+			}
+			if (!HeapTupleHeaderIsHeapOnly(curr_htup) &&
+				HeapTupleHeaderIsHotUpdated(pred_htup))
+			{
+				report_corruption(&ctx,
+							psprintf("Current tuple at offset %u is not HOT but its predecessor is HEAP HOT UPDATED.",
+							(unsigned) ctx.offnum));
+			}
+			if (!TransactionIdIsValid(curr_xmax) &&
+				HeapTupleHeaderIsHotUpdated(curr_htup))
+			{
+				report_corruption(&ctx,
+							psprintf("Current tuple at offset %u is HOT but is last tuple in the HOT chain.",
+							(unsigned) ctx.offnum));
+			}
+			if (!HeapTupleHeaderIsHotUpdated(pred_htup) &&
+				HeapTupleHeaderIsHeapOnly(pred_htup) &&
+				HeapTupleHeaderIsHeapOnly(curr_htup))
+			{
+				report_corruption(&ctx,
+							psprintf("Current tuple at offset %u is HOT but it is next updated tuple of last Tuple in HOT chain.",
+							(unsigned) ctx.offnum));
+			}
 		}
 
 		/* clean up */
-- 
2.25.1

