diff --git a/contrib/bloom/blutils.c b/contrib/bloom/blutils.c
index 06077af..4ab30d6 100644
--- a/contrib/bloom/blutils.c
+++ b/contrib/bloom/blutils.c
@@ -138,6 +138,7 @@ blhandler(PG_FUNCTION_ARGS)
 	amroutine->amendscan = blendscan;
 	amroutine->ammarkpos = NULL;
 	amroutine->amrestrpos = NULL;
+	amroutine->amrecheck = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index d60ddd2..3785045 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -112,6 +112,7 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amendscan = brinendscan;
 	amroutine->ammarkpos = NULL;
 	amroutine->amrestrpos = NULL;
+	amroutine->amrecheck = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index 597056a..d4634af 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -89,6 +89,7 @@ gisthandler(PG_FUNCTION_ARGS)
 	amroutine->amendscan = gistendscan;
 	amroutine->ammarkpos = NULL;
 	amroutine->amrestrpos = NULL;
+	amroutine->amrecheck = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index a64a9b9..40fede5 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -86,6 +86,7 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->amendscan = hashendscan;
 	amroutine->ammarkpos = NULL;
 	amroutine->amrestrpos = NULL;
+	amroutine->amrecheck = hashrecheck;
 
 	PG_RETURN_POINTER(amroutine);
 }
@@ -266,6 +267,8 @@ hashgettuple(IndexScanDesc scan, ScanDirection dir)
 	OffsetNumber offnum;
 	ItemPointer current;
 	bool		res;
+	IndexTuple	itup;
+
 
 	/* Hash indexes are always lossy since we store only the hash code */
 	scan->xs_recheck = true;
@@ -303,8 +306,6 @@ hashgettuple(IndexScanDesc scan, ScanDirection dir)
 			 offnum <= maxoffnum;
 			 offnum = OffsetNumberNext(offnum))
 		{
-			IndexTuple	itup;
-
 			itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offnum));
 			if (ItemPointerEquals(&(so->hashso_heappos), &(itup->t_tid)))
 				break;
diff --git a/src/backend/access/hash/hashsearch.c b/src/backend/access/hash/hashsearch.c
index a59ad6f..46a334c 100644
--- a/src/backend/access/hash/hashsearch.c
+++ b/src/backend/access/hash/hashsearch.c
@@ -59,6 +59,8 @@ _hash_next(IndexScanDesc scan, ScanDirection dir)
 	itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offnum));
 	so->hashso_heappos = itup->t_tid;
 
+	if (scan->xs_want_itup)
+		scan->xs_itup = itup;
 	return true;
 }
 
@@ -408,6 +410,9 @@ _hash_first(IndexScanDesc scan, ScanDirection dir)
 	itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offnum));
 	so->hashso_heappos = itup->t_tid;
 
+	if (scan->xs_want_itup)
+		scan->xs_itup = itup;
+
 	return true;
 }
 
diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/hashutil.c
index c705531..dcba734 100644
--- a/src/backend/access/hash/hashutil.c
+++ b/src/backend/access/hash/hashutil.c
@@ -17,8 +17,12 @@
 #include "access/hash.h"
 #include "access/reloptions.h"
 #include "access/relscan.h"
+#include "catalog/index.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/datum.h"
 
 #define CALC_NEW_BUCKET(old_bucket, lowmask) \
 			old_bucket | (lowmask + 1)
@@ -446,3 +450,109 @@ _hash_get_newbucket_from_oldbucket(Relation rel, Bucket old_bucket,
 
 	return new_bucket;
 }
+
+/*
+ * Recheck if the heap tuple satisfies the key stored in the index tuple
+ */
+bool
+hashrecheck(Relation indexRel, IndexTuple indexTuple,
+		Relation heapRel, HeapTuple heapTuple)
+{
+	IndexInfo  *indexInfo;
+	EState	   *estate;
+	ExprContext *econtext;
+	TupleTableSlot *slot;
+	Datum		values[INDEX_MAX_KEYS];
+	bool		isnull[INDEX_MAX_KEYS];
+	Datum		values2[INDEX_MAX_KEYS];
+	bool		isnull2[INDEX_MAX_KEYS];
+	int			i;
+	bool		equal;
+	int         natts = indexRel->rd_rel->relnatts;
+	Form_pg_attribute att;
+
+	indexInfo = BuildIndexInfo(indexRel);
+
+	/*
+	 * The heap tuple must be put into a slot for FormIndexDatum.
+	 */
+	slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRel));
+
+	ExecStoreTuple(heapTuple, slot, InvalidBuffer, false);
+
+	/*
+	 * Typically the index won't have expressions, but if it does we need an
+	 * EState to evaluate them.  We need it for exclusion constraints too,
+	 * even if they are just on simple columns.
+	 */
+	if (indexInfo->ii_Expressions != NIL ||
+			indexInfo->ii_ExclusionOps != NULL)
+	{
+		estate = CreateExecutorState();
+		econtext = GetPerTupleExprContext(estate);
+		econtext->ecxt_scantuple = slot;
+	}
+	else
+		estate = NULL;
+
+	/*
+	 * Form the index values and isnull flags for the index entry that we need
+	 * to check.
+	 *
+	 * Note: if the index uses functions that are not as immutable as they are
+	 * supposed to be, this could produce an index tuple different from the
+	 * original.  The index AM can catch such errors by verifying that it
+	 * finds a matching index entry with the tuple's TID.  For exclusion
+	 * constraints we check this in check_exclusion_constraint().
+	 */
+	FormIndexDatum(indexInfo, slot, estate, values, isnull);
+
+	/*
+	 * HASH indexes compute a hash value of the key and store that in the
+	 * index. So we must first obtain the hash of the value obtained from the
+	 * heap and then do a comparison
+	 */
+	_hash_convert_tuple(indexRel, values, isnull, values2, isnull2);
+
+	equal = true;
+	for (i = 1; i <= natts; i++)
+	{
+		Datum 	indxvalue;
+		bool	indxisnull;
+
+		indxvalue = index_getattr(indexTuple, i, indexRel->rd_att, &indxisnull);
+
+		/*
+		 * If both are NULL then they are equal
+		 */
+		if (isnull2[i - 1] && indxisnull)
+			continue;
+
+		/*
+		 * If either is NULL then they are not equal
+		 */
+		if (isnull2[i - 1] || indxisnull)
+		{
+			equal = false;
+			break;
+		}
+
+		/*
+		 * Now do a raw memory comparison
+		 */
+		att = indexRel->rd_att->attrs[i - 1];
+		if (!datumIsEqual(values2[i - 1], indxvalue, att->attbyval,
+					att->attlen))
+		{
+			equal = false;
+			break;
+		}
+	}
+
+	if (estate != NULL)
+		FreeExecutorState(estate);
+
+	ExecDropSingleTupleTableSlot(slot);
+
+	return equal;
+}
diff --git a/src/backend/access/heap/README.WARM b/src/backend/access/heap/README.WARM
new file mode 100644
index 0000000..f793570
--- /dev/null
+++ b/src/backend/access/heap/README.WARM
@@ -0,0 +1,271 @@
+src/backend/access/heap/README.WARM
+
+Write Amplification Reduction Method (WARM)
+===========================================
+
+The Heap Only Tuple (HOT) feature greatly eliminated redudant index
+entries and allowed re-use of the dead space occupied by previously
+updated or deleted tuples (see src/backend/access/heap/README.HOT)
+
+One of the necessary conditions for satisfying HOT update is that the
+update must not change a column used in any of the indexes on the table.
+The condition is sometimes hard to meet, especially for complex
+workloads with several indexes on large yet frequently updated tables.
+Worse, sometimes only one or two index columns may be updated, but the
+regular non-HOT update will still insert a new index entry in every
+index on the table, irrespective of whether the key pertaining to the
+index changed or not.
+
+WARM is a technique devised to address these problems.
+
+
+Update Chains With Multiple Index Entries Pointing to the Root
+--------------------------------------------------------------
+
+When a non-HOT update is caused by an index key change, a new index
+entry must be inserted for the changed index. But if the index key
+hasn't changed for other indexes, we don't really need to insert a new
+entry. Even though the existing index entry is pointing to the old
+tuple, the new tuple is reachable via the t_ctid chain. To keep things
+simple, a WARM update requires that the heap block must have enough
+space to store the new version of the tuple. This is same as HOT
+updates.
+
+In WARM, we ensure that every index entry always points to the root of
+the WARM chain. In fact, a WARM chain looks exactly like a HOT chain
+except for the fact that there could be multiple index entries pointing
+to the root of the chain. So when new entry is inserted in an index for
+updated tuple, and if we are doing a WARM update, the new entry is made
+point to the root of the WARM chain.
+
+For example, if we have a table with two columns and two indexes on each
+of the column. When a tuple is first inserted the table, we have exactly
+one index entry pointing to the tuple from both indexes.
+
+	lp [1]
+	[1111, aaaa]
+
+	Index1's entry (1111) points to 1
+	Index2's entry (aaaa) also points to 1
+
+Now if the tuple's second column is updated and if there is room on the
+page, we perform a WARM update. To do so, Index1 does not get any new
+entry and Index2's new entry will still point to the root tuple of the
+chain.
+
+	lp [1]  [2]
+	[1111, aaaa]->[111, bbbb]
+
+	Index1's entry (1111) points to 1
+	Index2's old entry (aaaa) points to 1
+	Index2's new entry (bbbb) also points to 1
+
+"A update chain which has more than one index entries pointing to its
+root line pointer is called WARM chain and the action that creates a
+WARM chain is called WARM update."
+
+Since all indexes always point to the root of the WARM chain, even when
+there are more than one index entries, WARM chains can be pruned and
+dead tuples can be removed without a need to do corresponding index
+cleanup.
+
+While this solves the problem of pruning dead tuples from a HOT/WARM
+chain, it also opens up a new technical challenge because now we have a
+situation where a heap tuple is reachable from multiple index entries,
+each having a different index key. While MVCC still ensures that only
+valid tuples are returned, a tuple with a wrong index key may be
+returned because of wrong index entries. In the above example, tuple
+[1111, bbbb] is reachable from both keys (aaaa) as well as (bbbb). For
+this reason, tuples returned from a WARM chain must always be rechecked
+for index key-match.
+
+Recheck Index Key Againt Heap Tuple
+-----------------------------------
+
+Since every Index AM has it's own notion of index tuples, each Index AM
+must implement its own method to recheck heap tuples. For example, a
+hash index stores the hash value of the column and hence recheck routine
+for hash AM must first compute the hash value of the heap attribute and
+then compare it against the value stored in the index tuple.
+
+The patch currently implement recheck routines for hash and btree
+indexes. If the table has an index which doesn't support recheck
+routine, WARM updates are disabled on such tables.
+
+Problem With Duplicate (key, ctid) Index Entries
+------------------------------------------------
+
+The index-key recheck logic works as long as there are no duplicate
+index keys, both pointing to the same WARM chain. In that case, the same
+valid tuple will be reachable via multiple index keys, yet satisfying
+the index key checks. In the above example, if the tuple [1111, bbbb] is
+again updated to [1111, aaaa] and if we insert a new index entry (aaaa)
+pointing to the root line pointer, we will end up with the following
+structure:
+
+	lp [1]  [2]  [3]
+	[1111, aaaa]->[1111, bbbb]->[1111, aaaa]
+
+	Index1's entry (1111) points to 1
+	Index2's oldest entry (aaaa) points to 1
+	Index2's old entry (bbbb) also points to 1
+	Index2's new entry (aaaa) also points to 1
+
+We must solve this problem to ensure that the same tuple is not
+reachable via multiple index pointers. There are couple of ways to
+address this issue:
+
+1. Do not allow WARM update to a tuple from a WARM chain. This
+guarantees that there can never be duplicate index entries to the same
+root line pointer because we must have checked for old and new index
+keys while doing the first WARM update.
+
+2. Do not allow duplicate (key, ctid) index pointers. In the above
+example, since (aaaa, 1) already exists in the index, we must not insert
+a duplicate index entry.
+
+The patch currently implements 1 i.e. do not do WARM updates to a tuple
+from a WARM chain. HOT updates are fine because they do not add a new
+index entry.
+
+Even with the restriction, this is a significant improvement because the
+number of regular UPDATEs are curtailed down to half.
+
+Expression and Partial Indexes
+------------------------------
+
+Expressions may evaluate to the same value even if the underlying column
+values have changed. A simple example is an index on "lower(col)" which
+will return the same value if the new heap value only differs in the
+case sensitivity. So we can not solely rely on the heap column check to
+decide whether or not to insert a new index entry for expression
+indexes. Similarly, for partial indexes, the predicate expression must
+be evaluated to decide whether or not to cause a new index entry when
+columns referred in the predicate expressions change.
+
+(None of these things are currently implemented and we squarely disallow
+WARM update if a column from expression indexes or predicate has
+changed).
+
+
+Efficiently Finding the Root Line Pointer
+-----------------------------------------
+
+During WARM update, we must be able to find the root line pointer of the
+tuple being updated. It must be noted that the t_ctid field in the heap
+tuple header is usually used to find the next tuple in the update chain.
+But the tuple that we are updating, must be the last tuple in the update
+chain. In such cases, the c_tid field usually points the tuple itself.
+So in theory, we could use the t_ctid to store additional information in
+the last tuple of the update chain, if the information about the tuple
+being the last tuple is stored elsewhere.
+
+We now utilize another bit from t_infomask2 to explicitly identify that
+this is the last tuple in the update chain.
+
+HEAP_LATEST_TUPLE - When this bit is set, the tuple is the last tuple in
+the update chain. The OffsetNumber part of t_ctid points to the root
+line pointer of the chain when HEAP_LATEST_TUPLE flag is set.
+
+If UPDATE operation is aborted, the last tuple in the update chain
+becomes dead. The root line pointer information stored in the tuple
+which remains the last valid tuple in the chain is also lost. In such
+rare cases, the root line pointer must be found in a hard way by
+scanning the entire heap page.
+
+Tracking WARM Chains
+--------------------
+
+The old and every subsequent tuple in the chain is marked with a special
+HEAP_WARM_TUPLE flag. We use the last remaining bit in t_infomask2 to
+store this information.
+
+When a tuple is returned from a WARM chain, the caller must do
+additional checks to ensure that the tuple matches the index key. Even
+if the tuple comes precedes the WARM update in the chain, it must still
+be rechecked for the index key match (case when old tuple is returned by
+the new index key). So we must follow the update chain everytime to the
+end to see check if this is a WARM chain.
+
+When the old updated tuple is retired and the root line pointer is
+converted into a redirected line pointer, we can copy the information
+about WARM chain to the redirected line pointer by storing a special
+value in the lp_len field of the line pointer. This will handle the most
+common case where a WARM chain is replaced by a redirect line pointer
+and a single tuple in the chain.
+
+Converting WARM chains back to HOT chains (VACUUM ?)
+----------------------------------------------------
+
+The current implementation of WARM allows only one WARM update per
+chain. This simplifies the design and addresses certain issues around
+duplicate scans. But this also implies that the benefit of WARM will be
+no more than 50%, which is still significant, but if we could return
+WARM chains back to normal status, we could do far more WARM updates.
+
+A distinct property of a WARM chain is that at least one index has more
+than one live index entries pointing to the root of the chain. In other
+words, if we can remove duplicate entry from every index or conclusively
+prove that there are no duplicate index entries for the root line
+pointer, the chain can again be marked as HOT.
+
+Here is one idea:
+
+A WARM chain has two parts, separated by the tuple that caused WARM
+update. All tuples in each part has matching index keys, but certain
+index keys may not match between these two parts. Lets say we mark heap
+tuples in each part with a special Red-Blue flag. The same flag is
+replicated in the index tuples. For example, when new rows are inserted
+in a table, they are marked with Blue flag and the index entries
+associated with those rows are also marked with Blue flag. When a row is
+WARM updated, the new version is marked with Red flag and the new index
+entry created by the update is also marked with Red flag.
+
+
+Heap chain: [1] [2] [3] [4]
+			[aaaa, 1111]B -> [aaaa, 1111]B -> [bbbb, 1111]R -> [bbbb, 1111]R
+
+Index1: 	(aaaa)B points to 1 (satisfies only tuples marked with B)
+			(bbbb)R points to 1 (satisfies only tuples marked with R)
+
+Index2:		(1111)B points to 1 (satisfied bith B and R tuples)
+
+
+It's clear that for indexes with Red and Blue pointers, a heap tuple
+with Blue flag will be reachable from Blue pointer and that with Red
+flag will be reachable from Red pointer. But for indexes which did not
+create a new entry, both Blue and Red tuples will be reachable from Blue
+pointer (there is no Red pointer in such indexes). So, as a side note,
+matching Red and Blue flags is not enough from index scan perspective.
+
+During first heap scan of VACUUM, we look for tuples with
+HEAP_WARM_TUPLE set.  If all live tuples in the chain are either marked
+with Blue flag or Red flag (but no mix of Red and Blue), then the chain
+is a candidate for HOT conversion.  We remember the root line pointer
+and Red-Blue flag of the WARM chain in a separate array.
+
+If we have a Red WARM chain, then our goal is to remove Blue pointers
+and vice versa. But there is a catch. For Index2 above, there is only
+Blue pointer and that must not be removed. IOW we should remove Blue
+pointer iff a Red pointer exists. Since index vacuum may visit Red and
+Blue pointers in any order, I think we will need another index pass to
+remove dead index pointers. So in the first index pass we check which
+WARM candidates have 2 index pointers. In the second pass, we remove the
+dead pointer and reset Red flag is the surviving index pointer is Red.
+
+During the second heap scan, we fix WARM chain by clearing
+HEAP_WARM_TUPLE flag and also reset Red flag to Blue.
+
+There are some more problems around aborted vacuums. For example, if
+vacuum aborts after changing Red index flag to Blue but before removing
+the other Blue pointer, we will end up with two Blue pointers to a Red
+WARM chain. But since the HEAP_WARM_TUPLE flag on the heap tuple is
+still set, further WARM updates to the chain will be blocked. I guess we
+will need some special handling for case with multiple Blue pointers. We
+can either leave these WARM chains alone and let them die with a
+subsequent non-WARM update or must apply heap-recheck logic during index
+vacuum to find the dead pointer. Given that vacuum-aborts are not
+common, I am inclined to leave this case unhandled. We must still check
+for presence of multiple Blue pointers and ensure that we don't
+accidently remove either of the Blue pointers and not clear WARM chains
+either.
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 8e57bae..015aef1 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -1957,6 +1957,78 @@ heap_fetch(Relation relation,
 }
 
 /*
+ * Check if the HOT chain containing this tid is actually a WARM chain.
+ * Note that even if the WARM update ultimately aborted, we still must do a
+ * recheck because the failing UPDATE when have inserted created index entries
+ * which are now stale, but still referencing this chain.
+ */
+static bool
+hot_check_warm_chain(Page dp, ItemPointer tid)
+{
+	TransactionId prev_xmax = InvalidTransactionId;
+	OffsetNumber offnum;
+	HeapTupleData heapTuple;
+
+	offnum = ItemPointerGetOffsetNumber(tid);
+	heapTuple.t_self = *tid;
+	/* Scan through possible multiple members of HOT-chain */
+	for (;;)
+	{
+		ItemId		lp;
+
+		/* check for bogus TID */
+		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(dp))
+			break;
+
+		lp = PageGetItemId(dp, offnum);
+
+		/* check for unused, dead, or redirected items */
+		if (!ItemIdIsNormal(lp))
+			break;
+
+		heapTuple.t_data = (HeapTupleHeader) PageGetItem(dp, lp);
+		ItemPointerSetOffsetNumber(&heapTuple.t_self, offnum);
+
+		/*
+		 * The xmin should match the previous xmax value, else chain is
+		 * broken.
+		 */
+		if (TransactionIdIsValid(prev_xmax) &&
+			!TransactionIdEquals(prev_xmax,
+								 HeapTupleHeaderGetXmin(heapTuple.t_data)))
+			break;
+
+
+		/*
+		 * Presence of either WARM or WARM updated tuple signals possible
+		 * breakage and the caller must recheck tuple returned from this chain
+		 * for index satisfaction
+		 */
+		if (HeapTupleHeaderIsHeapWarmTuple(heapTuple.t_data))
+			return true;
+
+		/*
+		 * Check to see if HOT chain continues past this tuple; if so fetch
+		 * the next offnum and loop around.
+		 */
+		if (!HeapTupleIsHotUpdated(&heapTuple))
+			break;
+
+		/*
+		 * It can't be a HOT chain if the tuple contains root line pointer
+		 */
+		if (HeapTupleHeaderHasRootOffset(heapTuple.t_data))
+			break;
+
+		offnum = ItemPointerGetOffsetNumber(&heapTuple.t_data->t_ctid);
+		prev_xmax = HeapTupleHeaderGetUpdateXid(heapTuple.t_data);
+	}
+
+	/* All OK. No need to recheck */
+	return false;
+}
+
+/*
  *	heap_hot_search_buffer	- search HOT chain for tuple satisfying snapshot
  *
  * On entry, *tid is the TID of a tuple (either a simple tuple, or the root
@@ -1976,11 +2048,14 @@ heap_fetch(Relation relation,
  * Unlike heap_fetch, the caller must already have pin and (at least) share
  * lock on the buffer; it is still pinned/locked at exit.  Also unlike
  * heap_fetch, we do not report any pgstats count; caller may do so if wanted.
+ *
+ * recheck should be set false on entry by caller, will be set true on exit
+ * if a WARM tuple is encountered.
  */
 bool
 heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
 					   Snapshot snapshot, HeapTuple heapTuple,
-					   bool *all_dead, bool first_call)
+					   bool *all_dead, bool first_call, bool *recheck)
 {
 	Page		dp = (Page) BufferGetPage(buffer);
 	TransactionId prev_xmax = InvalidTransactionId;
@@ -2034,9 +2109,12 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
 		ItemPointerSetOffsetNumber(&heapTuple->t_self, offnum);
 
 		/*
-		 * Shouldn't see a HEAP_ONLY tuple at chain start.
+		 * Shouldn't see a HEAP_ONLY tuple at chain start, unless we are
+		 * dealing with a WARM updated tuple in which case deferred triggers
+		 * may request to fetch a WARM tuple from middle of a chain.
 		 */
-		if (at_chain_start && HeapTupleIsHeapOnly(heapTuple))
+		if (at_chain_start && HeapTupleIsHeapOnly(heapTuple) &&
+				!HeapTupleIsHeapWarmTuple(heapTuple))
 			break;
 
 		/*
@@ -2049,6 +2127,16 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
 			break;
 
 		/*
+		 * Check if there exists a WARM tuple somewhere down the chain and set
+		 * recheck to TRUE.
+		 *
+		 * XXX This is not very efficient right now, and we should look for
+		 * possible improvements here
+		 */
+		if (recheck && *recheck == false)
+			*recheck = hot_check_warm_chain(dp, &heapTuple->t_self);
+
+		/*
 		 * When first_call is true (and thus, skip is initially false) we'll
 		 * return the first tuple we find.  But on later passes, heapTuple
 		 * will initially be pointing to the tuple we returned last time.
@@ -2097,7 +2185,8 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
 		 * Check to see if HOT chain continues past this tuple; if so fetch
 		 * the next offnum and loop around.
 		 */
-		if (HeapTupleIsHotUpdated(heapTuple))
+		if (HeapTupleIsHotUpdated(heapTuple) &&
+			!HeapTupleHeaderHasRootOffset(heapTuple->t_data))
 		{
 			Assert(ItemPointerGetBlockNumber(&heapTuple->t_data->t_ctid) ==
 				   ItemPointerGetBlockNumber(tid));
@@ -2121,18 +2210,41 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
  */
 bool
 heap_hot_search(ItemPointer tid, Relation relation, Snapshot snapshot,
-				bool *all_dead)
+				bool *all_dead, bool *recheck, Buffer *cbuffer,
+				HeapTuple heapTuple)
 {
 	bool		result;
 	Buffer		buffer;
-	HeapTupleData heapTuple;
+	ItemPointerData ret_tid = *tid;
 
 	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
 	LockBuffer(buffer, BUFFER_LOCK_SHARE);
-	result = heap_hot_search_buffer(tid, relation, buffer, snapshot,
-									&heapTuple, all_dead, true);
-	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-	ReleaseBuffer(buffer);
+	result = heap_hot_search_buffer(&ret_tid, relation, buffer, snapshot,
+									heapTuple, all_dead, true, recheck);
+
+	/*
+	 * If we are returning a potential candidate tuple from this chain and the
+	 * caller has requested for "recheck" hint, keep the buffer locked and
+	 * pinned. The caller must release the lock and pin on the buffer in all
+	 * such cases
+	 */
+	if (!result || !recheck || !(*recheck))
+	{
+		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+		ReleaseBuffer(buffer);
+	}
+
+	/*
+	 * Set the caller supplied tid with the actual location of the tuple being
+	 * returned
+	 */
+	if (result)
+	{
+		*tid = ret_tid;
+		if (cbuffer)
+			*cbuffer = buffer;
+	}
+
 	return result;
 }
 
@@ -3484,13 +3596,15 @@ simple_heap_delete(Relation relation, ItemPointer tid)
 HTSU_Result
 heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 			CommandId cid, Snapshot crosscheck, bool wait,
-			HeapUpdateFailureData *hufd, LockTupleMode *lockmode)
+			HeapUpdateFailureData *hufd, LockTupleMode *lockmode,
+			Bitmapset **modified_attrsp, bool *warm_update)
 {
 	HTSU_Result result;
 	TransactionId xid = GetCurrentTransactionId();
 	Bitmapset  *hot_attrs;
 	Bitmapset  *key_attrs;
 	Bitmapset  *id_attrs;
+	Bitmapset  *exprindx_attrs;
 	Bitmapset  *interesting_attrs;
 	Bitmapset  *modified_attrs;
 	ItemId		lp;
@@ -3513,6 +3627,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	bool		have_tuple_lock = false;
 	bool		iscombo;
 	bool		use_hot_update = false;
+	bool		use_warm_update = false;
 	bool		key_intact;
 	bool		all_visible_cleared = false;
 	bool		all_visible_cleared_new = false;
@@ -3537,6 +3652,10 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
 				 errmsg("cannot update tuples during a parallel operation")));
 
+	/* Assume no-warm update */
+	if (warm_update)
+		*warm_update = false;
+
 	/*
 	 * Fetch the list of attributes to be checked for various operations.
 	 *
@@ -3558,10 +3677,13 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
 	id_attrs = RelationGetIndexAttrBitmap(relation,
 										  INDEX_ATTR_BITMAP_IDENTITY_KEY);
+	exprindx_attrs = RelationGetIndexAttrBitmap(relation,
+										  INDEX_ATTR_BITMAP_EXPR_PREDICATE);
+
 	interesting_attrs = bms_add_members(NULL, hot_attrs);
 	interesting_attrs = bms_add_members(interesting_attrs, key_attrs);
 	interesting_attrs = bms_add_members(interesting_attrs, id_attrs);
-
+	interesting_attrs = bms_add_members(interesting_attrs, exprindx_attrs);
 
 	block = ItemPointerGetBlockNumber(otid);
 	offnum = ItemPointerGetOffsetNumber(otid);
@@ -3613,6 +3735,9 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	modified_attrs = HeapDetermineModifiedColumns(relation, interesting_attrs,
 												  &oldtup, newtup);
 
+	if (modified_attrsp)
+		*modified_attrsp = bms_copy(modified_attrs);
+
 	/*
 	 * If we're not updating any "key" column, we can grab a weaker lock type.
 	 * This allows for more concurrency when we are running simultaneously
@@ -3868,6 +3993,7 @@ l2:
 		bms_free(hot_attrs);
 		bms_free(key_attrs);
 		bms_free(id_attrs);
+		bms_free(exprindx_attrs);
 		bms_free(modified_attrs);
 		bms_free(interesting_attrs);
 		return result;
@@ -4187,6 +4313,35 @@ l2:
 		 */
 		if (!bms_overlap(modified_attrs, hot_attrs))
 			use_hot_update = true;
+		else
+		{
+			/*
+			 * If no WARM updates yet on this chain, let this update be a WARM
+			 * update.
+			 *
+			 * We check for both warm and warm updated tuples since if the
+			 * previous WARM update aborted, we may still have added
+			 * another index entry for this HOT chain. In such situations, we
+			 * must not attempt a WARM update until duplicate (key, CTID) index
+			 * entry issue is sorted out
+			 *
+			 * XXX Later we'll add more checks to ensure WARM chains can
+			 * further be WARM updated. This is probably good to do first rounf
+			 * of tests of remaining functionality
+			 *
+			 * XXX Disable WARM updates on system tables. There is nothing in
+			 * principle that stops us from supporting this. But it would
+			 * require API change to propogate the changed columns back to the
+			 * caller so that CatalogUpdateIndexes() can avoid adding new
+			 * entries to indexes that are not changed by update. This will be
+			 * fixed once basic patch is tested. !!FIXME
+			 */
+			if (relation->rd_supportswarm &&
+				!bms_overlap(modified_attrs, exprindx_attrs) &&
+				!bms_is_subset(hot_attrs, modified_attrs) &&
+				!HeapTupleIsHeapWarmTuple(&oldtup))
+				use_warm_update = true;
+		}
 	}
 	else
 	{
@@ -4234,6 +4389,22 @@ l2:
 		HeapTupleSetHeapOnly(heaptup);
 		/* Mark the caller's copy too, in case different from heaptup */
 		HeapTupleSetHeapOnly(newtup);
+
+		/*
+		 * Even if we are doing a HOT update, we must carry forward the WARM
+		 * flag because we may have already inserted another index entry
+		 * pointing to our root and a third entry may create duplicates
+		 *
+		 * Note: If we ever have a mechanism to avoid duplicate <key, TID> in
+		 * indexes, we could look at relaxing this restriction and allow even
+		 * more WARM udpates
+		 */
+		if (HeapTupleIsHeapWarmTuple(&oldtup))
+		{
+			HeapTupleSetHeapWarmTuple(heaptup);
+			HeapTupleSetHeapWarmTuple(newtup);
+		}
+
 		/*
 		 * For HOT (or WARM) updated tuples, we store the offset of the root
 		 * line pointer of this chain in the ip_posid field of the new tuple.
@@ -4246,12 +4417,36 @@ l2:
 		if (HeapTupleHeaderHasRootOffset(oldtup.t_data))
 			root_offnum = HeapTupleHeaderGetRootOffset(oldtup.t_data);
 	}
+	else if (use_warm_update)
+	{
+		/* Mark the old tuple as HOT-updated */
+		HeapTupleSetHotUpdated(&oldtup);
+		HeapTupleSetHeapWarmTuple(&oldtup);
+		/* And mark the new tuple as heap-only */
+		HeapTupleSetHeapOnly(heaptup);
+		HeapTupleSetHeapWarmTuple(heaptup);
+		/* Mark the caller's copy too, in case different from heaptup */
+		HeapTupleSetHeapOnly(newtup);
+		HeapTupleSetHeapWarmTuple(newtup);
+		if (HeapTupleHeaderHasRootOffset(oldtup.t_data))
+			root_offnum = HeapTupleHeaderGetRootOffset(oldtup.t_data);
+		else
+			heap_get_root_tuple_one(page,
+					ItemPointerGetOffsetNumber(&(oldtup.t_self)),
+					&root_offnum);
+
+		/* Let the caller know we did a WARM update */
+		if (warm_update)
+			*warm_update = true;
+	}
 	else
 	{
 		/* Make sure tuples are correctly marked as not-HOT */
 		HeapTupleClearHotUpdated(&oldtup);
 		HeapTupleClearHeapOnly(heaptup);
 		HeapTupleClearHeapOnly(newtup);
+		HeapTupleClearHeapWarmTuple(heaptup);
+		HeapTupleClearHeapWarmTuple(newtup);
 		root_offnum = InvalidOffsetNumber;
 	}
 
@@ -4360,7 +4555,10 @@ l2:
 	if (have_tuple_lock)
 		UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode);
 
-	pgstat_count_heap_update(relation, use_hot_update);
+	/*
+	 * Count HOT and WARM updates separately
+	 */
+	pgstat_count_heap_update(relation, use_hot_update, use_warm_update);
 
 	/*
 	 * If heaptup is a private copy, release it.  Don't forget to copy t_self
@@ -4500,7 +4698,8 @@ HeapDetermineModifiedColumns(Relation relation, Bitmapset *interesting_cols,
  * via ereport().
  */
 void
-simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup)
+simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup,
+		bool *warm_update, Bitmapset **modified_attrs)
 {
 	HTSU_Result result;
 	HeapUpdateFailureData hufd;
@@ -4509,7 +4708,8 @@ simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup)
 	result = heap_update(relation, otid, tup,
 						 GetCurrentCommandId(true), InvalidSnapshot,
 						 true /* wait for commit */ ,
-						 &hufd, &lockmode);
+						 &hufd, &lockmode, modified_attrs,
+						 warm_update);
 	switch (result)
 	{
 		case HeapTupleSelfUpdated:
@@ -7562,6 +7762,7 @@ log_heap_update(Relation reln, Buffer oldbuf,
 	bool		need_tuple_data = RelationIsLogicallyLogged(reln);
 	bool		init;
 	int			bufflags;
+	bool		warm_update = false;
 
 	/* Caller should not call me on a non-WAL-logged relation */
 	Assert(RelationNeedsWAL(reln));
@@ -7573,6 +7774,9 @@ log_heap_update(Relation reln, Buffer oldbuf,
 	else
 		info = XLOG_HEAP_UPDATE;
 
+	if (HeapTupleIsHeapWarmTuple(newtup))
+		warm_update = true;
+
 	/*
 	 * If the old and new tuple are on the same page, we only need to log the
 	 * parts of the new tuple that were changed.  That saves on the amount of
@@ -7646,6 +7850,8 @@ log_heap_update(Relation reln, Buffer oldbuf,
 				xlrec.flags |= XLH_UPDATE_CONTAINS_OLD_KEY;
 		}
 	}
+	if (warm_update)
+		xlrec.flags |= XLH_UPDATE_WARM_UPDATE;
 
 	/* If new tuple is the single and first tuple on page... */
 	if (ItemPointerGetOffsetNumber(&(newtup->t_self)) == FirstOffsetNumber &&
@@ -8623,16 +8829,22 @@ heap_xlog_update(XLogReaderState *record, bool hot_update)
 	Size		freespace = 0;
 	XLogRedoAction oldaction;
 	XLogRedoAction newaction;
+	bool		warm_update = false;
 
 	/* initialize to keep the compiler quiet */
 	oldtup.t_data = NULL;
 	oldtup.t_len = 0;
 
+	if (xlrec->flags & XLH_UPDATE_WARM_UPDATE)
+		warm_update = true;
+
 	XLogRecGetBlockTag(record, 0, &rnode, NULL, &newblk);
 	if (XLogRecGetBlockTag(record, 1, NULL, NULL, &oldblk))
 	{
 		/* HOT updates are never done across pages */
 		Assert(!hot_update);
+		/* WARM updates are never done across pages */
+		Assert(!warm_update);
 	}
 	else
 		oldblk = newblk;
@@ -8692,6 +8904,11 @@ heap_xlog_update(XLogReaderState *record, bool hot_update)
 								   &htup->t_infomask2);
 		HeapTupleHeaderSetXmax(htup, xlrec->old_xmax);
 		HeapTupleHeaderSetCmax(htup, FirstCommandId, false);
+
+		/* Mark the old tuple has a WARM tuple */
+		if (warm_update)
+			HeapTupleHeaderSetHeapWarmTuple(htup);
+
 		/* Set forward chain link in t_ctid */
 		HeapTupleHeaderSetNextTid(htup, &newtid);
 
@@ -8827,6 +9044,10 @@ heap_xlog_update(XLogReaderState *record, bool hot_update)
 		HeapTupleHeaderSetCmin(htup, FirstCommandId);
 		HeapTupleHeaderSetXmax(htup, xlrec->new_xmax);
 
+		/* Mark the new tuple has a WARM tuple */
+		if (warm_update)
+			HeapTupleHeaderSetHeapWarmTuple(htup);
+
 		offnum = PageAddItem(page, (Item) htup, newlen, offnum, true, true);
 		if (offnum == InvalidOffsetNumber)
 			elog(PANIC, "failed to add tuple");
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index 4822af9..de559fd 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -71,10 +71,12 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/index.h"
+#include "executor/executor.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
+#include "utils/datum.h"
 #include "utils/snapmgr.h"
 #include "utils/tqual.h"
 
@@ -228,6 +230,21 @@ index_beginscan(Relation heapRelation,
 	scan->heapRelation = heapRelation;
 	scan->xs_snapshot = snapshot;
 
+	/*
+	 * If the index supports recheck, make sure that index tuple is saved
+	 * during index scans.
+	 *
+	 * XXX Ideally, we should look at all indexes on the table and check if
+	 * WARM is at all supported on the base table. If WARM is not supported
+	 * then we don't need to do any recheck. RelationGetIndexAttrBitmap() does
+	 * do that and sets rd_supportswarm after looking at all indexes. But we
+	 * don't know if the function was called earlier in the session when we're
+	 * here. We can't call it now because there exists a risk of causing
+	 * deadlock.
+	 */
+	if (indexRelation->rd_amroutine->amrecheck)
+		scan->xs_want_itup = true;
+
 	return scan;
 }
 
@@ -409,7 +426,7 @@ index_getnext_tid(IndexScanDesc scan, ScanDirection direction)
 	/*
 	 * The AM's amgettuple proc finds the next index entry matching the scan
 	 * keys, and puts the TID into scan->xs_ctup.t_self.  It should also set
-	 * scan->xs_recheck and possibly scan->xs_itup, though we pay no attention
+	 * scan->xs_tuple_recheck and possibly scan->xs_itup, though we pay no attention
 	 * to those fields here.
 	 */
 	found = scan->indexRelation->rd_amroutine->amgettuple(scan, direction);
@@ -448,7 +465,7 @@ index_getnext_tid(IndexScanDesc scan, ScanDirection direction)
  * dropped in a future index_getnext_tid, index_fetch_heap or index_endscan
  * call).
  *
- * Note: caller must check scan->xs_recheck, and perform rechecking of the
+ * Note: caller must check scan->xs_tuple_recheck, and perform rechecking of the
  * scan keys if required.  We do not do that here because we don't have
  * enough information to do it efficiently in the general case.
  * ----------------
@@ -475,6 +492,15 @@ index_fetch_heap(IndexScanDesc scan)
 		 */
 		if (prev_buf != scan->xs_cbuf)
 			heap_page_prune_opt(scan->heapRelation, scan->xs_cbuf);
+
+		/*
+		 * If we're not always re-checking, reset recheck for this tuple
+		 */
+		if (!scan->xs_recheck)
+			scan->xs_tuple_recheck = false;
+		else
+			scan->xs_tuple_recheck = true;
+
 	}
 
 	/* Obtain share-lock on the buffer so we can examine visibility */
@@ -484,32 +510,64 @@ index_fetch_heap(IndexScanDesc scan)
 											scan->xs_snapshot,
 											&scan->xs_ctup,
 											&all_dead,
-											!scan->xs_continue_hot);
+											!scan->xs_continue_hot,
+											&scan->xs_tuple_recheck);
 	LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK);
 
 	if (got_heap_tuple)
 	{
+		bool res = true;
+
+		/*
+		 * Ok we got a tuple which satisfies the snapshot, but if its part of a
+		 * WARM chain, we must do additional checks to ensure that we are
+		 * indeed returning a correct tuple. Note that if the index AM does not
+		 * implement amrecheck method, then we don't any additional checks
+		 * since WARM must have been disabled on such tables
+		 *
+		 * XXX What happens when a new index which does not support amcheck is
+		 * added to the table? Do we need to handle this case or is CREATE
+		 * INDEX and CREATE INDEX CONCURRENTLY smart enough to handle this
+		 * issue?
+		 */
+		if (scan->xs_tuple_recheck &&
+				scan->xs_itup &&
+				scan->indexRelation->rd_amroutine->amrecheck)
+		{
+			LockBuffer(scan->xs_cbuf, BUFFER_LOCK_SHARE);
+			res = scan->indexRelation->rd_amroutine->amrecheck(
+						scan->indexRelation,
+						scan->xs_itup,
+						scan->heapRelation,
+						&scan->xs_ctup);
+			LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK);
+		}
+
 		/*
 		 * Only in a non-MVCC snapshot can more than one member of the HOT
 		 * chain be visible.
 		 */
 		scan->xs_continue_hot = !IsMVCCSnapshot(scan->xs_snapshot);
 		pgstat_count_heap_fetch(scan->indexRelation);
-		return &scan->xs_ctup;
-	}
 
-	/* We've reached the end of the HOT chain. */
-	scan->xs_continue_hot = false;
+		if (res)
+			return &scan->xs_ctup;
+	}
+	else
+	{
+		/* We've reached the end of the HOT chain. */
+		scan->xs_continue_hot = false;
 
-	/*
-	 * If we scanned a whole HOT chain and found only dead tuples, tell index
-	 * AM to kill its entry for that TID (this will take effect in the next
-	 * amgettuple call, in index_getnext_tid).  We do not do this when in
-	 * recovery because it may violate MVCC to do so.  See comments in
-	 * RelationGetIndexScan().
-	 */
-	if (!scan->xactStartedInRecovery)
-		scan->kill_prior_tuple = all_dead;
+		/*
+		 * If we scanned a whole HOT chain and found only dead tuples, tell index
+		 * AM to kill its entry for that TID (this will take effect in the next
+		 * amgettuple call, in index_getnext_tid).  We do not do this when in
+		 * recovery because it may violate MVCC to do so.  See comments in
+		 * RelationGetIndexScan().
+		 */
+		if (!scan->xactStartedInRecovery)
+			scan->kill_prior_tuple = all_dead;
+	}
 
 	return NULL;
 }
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index 883d70d..6efccf7 100644
--- a/src/backend/access/nbtree/nbtinsert.c
+++ b/src/backend/access/nbtree/nbtinsert.c
@@ -19,11 +19,14 @@
 #include "access/nbtree.h"
 #include "access/transam.h"
 #include "access/xloginsert.h"
+#include "catalog/index.h"
+#include "executor/executor.h"
 #include "miscadmin.h"
+#include "nodes/execnodes.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "utils/tqual.h"
-
+#include "utils/datum.h"
 
 typedef struct
 {
@@ -249,6 +252,9 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
 	BTPageOpaque opaque;
 	Buffer		nbuf = InvalidBuffer;
 	bool		found = false;
+	Buffer		buffer;
+	HeapTupleData	heapTuple;
+	bool		recheck = false;
 
 	/* Assume unique until we find a duplicate */
 	*is_unique = true;
@@ -308,6 +314,8 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
 				curitup = (IndexTuple) PageGetItem(page, curitemid);
 				htid = curitup->t_tid;
 
+				recheck = false;
+
 				/*
 				 * If we are doing a recheck, we expect to find the tuple we
 				 * are rechecking.  It's not a duplicate, but we have to keep
@@ -325,112 +333,153 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
 				 * have just a single index entry for the entire chain.
 				 */
 				else if (heap_hot_search(&htid, heapRel, &SnapshotDirty,
-										 &all_dead))
+							&all_dead, &recheck, &buffer,
+							&heapTuple))
 				{
 					TransactionId xwait;
+					bool result = true;
 
 					/*
-					 * It is a duplicate. If we are only doing a partial
-					 * check, then don't bother checking if the tuple is being
-					 * updated in another transaction. Just return the fact
-					 * that it is a potential conflict and leave the full
-					 * check till later.
+					 * If the tuple was WARM update, we may again see our own
+					 * tuple. Since WARM updates don't create new index
+					 * entries, our own tuple is only reachable via the old
+					 * index pointer
 					 */
-					if (checkUnique == UNIQUE_CHECK_PARTIAL)
+					if (checkUnique == UNIQUE_CHECK_EXISTING &&
+							ItemPointerCompare(&htid, &itup->t_tid) == 0)
 					{
-						if (nbuf != InvalidBuffer)
-							_bt_relbuf(rel, nbuf);
-						*is_unique = false;
-						return InvalidTransactionId;
+						found = true;
+						result = false;
+						if (recheck)
+							UnlockReleaseBuffer(buffer);
 					}
-
-					/*
-					 * If this tuple is being updated by other transaction
-					 * then we have to wait for its commit/abort.
-					 */
-					xwait = (TransactionIdIsValid(SnapshotDirty.xmin)) ?
-						SnapshotDirty.xmin : SnapshotDirty.xmax;
-
-					if (TransactionIdIsValid(xwait))
+					else if (recheck)
 					{
-						if (nbuf != InvalidBuffer)
-							_bt_relbuf(rel, nbuf);
-						/* Tell _bt_doinsert to wait... */
-						*speculativeToken = SnapshotDirty.speculativeToken;
-						return xwait;
+						result = btrecheck(rel, curitup, heapRel, &heapTuple);
+						UnlockReleaseBuffer(buffer);
 					}
 
-					/*
-					 * Otherwise we have a definite conflict.  But before
-					 * complaining, look to see if the tuple we want to insert
-					 * is itself now committed dead --- if so, don't complain.
-					 * This is a waste of time in normal scenarios but we must
-					 * do it to support CREATE INDEX CONCURRENTLY.
-					 *
-					 * We must follow HOT-chains here because during
-					 * concurrent index build, we insert the root TID though
-					 * the actual tuple may be somewhere in the HOT-chain.
-					 * While following the chain we might not stop at the
-					 * exact tuple which triggered the insert, but that's OK
-					 * because if we find a live tuple anywhere in this chain,
-					 * we have a unique key conflict.  The other live tuple is
-					 * not part of this chain because it had a different index
-					 * entry.
-					 */
-					htid = itup->t_tid;
-					if (heap_hot_search(&htid, heapRel, SnapshotSelf, NULL))
-					{
-						/* Normal case --- it's still live */
-					}
-					else
+					if (result)
 					{
 						/*
-						 * It's been deleted, so no error, and no need to
-						 * continue searching
+						 * It is a duplicate. If we are only doing a partial
+						 * check, then don't bother checking if the tuple is being
+						 * updated in another transaction. Just return the fact
+						 * that it is a potential conflict and leave the full
+						 * check till later.
 						 */
-						break;
-					}
+						if (checkUnique == UNIQUE_CHECK_PARTIAL)
+						{
+							if (nbuf != InvalidBuffer)
+								_bt_relbuf(rel, nbuf);
+							*is_unique = false;
+							return InvalidTransactionId;
+						}
 
-					/*
-					 * Check for a conflict-in as we would if we were going to
-					 * write to this page.  We aren't actually going to write,
-					 * but we want a chance to report SSI conflicts that would
-					 * otherwise be masked by this unique constraint
-					 * violation.
-					 */
-					CheckForSerializableConflictIn(rel, NULL, buf);
+						/*
+						 * If this tuple is being updated by other transaction
+						 * then we have to wait for its commit/abort.
+						 */
+						xwait = (TransactionIdIsValid(SnapshotDirty.xmin)) ?
+							SnapshotDirty.xmin : SnapshotDirty.xmax;
+
+						if (TransactionIdIsValid(xwait))
+						{
+							if (nbuf != InvalidBuffer)
+								_bt_relbuf(rel, nbuf);
+							/* Tell _bt_doinsert to wait... */
+							*speculativeToken = SnapshotDirty.speculativeToken;
+							return xwait;
+						}
 
-					/*
-					 * This is a definite conflict.  Break the tuple down into
-					 * datums and report the error.  But first, make sure we
-					 * release the buffer locks we're holding ---
-					 * BuildIndexValueDescription could make catalog accesses,
-					 * which in the worst case might touch this same index and
-					 * cause deadlocks.
-					 */
-					if (nbuf != InvalidBuffer)
-						_bt_relbuf(rel, nbuf);
-					_bt_relbuf(rel, buf);
+						/*
+						 * Otherwise we have a definite conflict.  But before
+						 * complaining, look to see if the tuple we want to insert
+						 * is itself now committed dead --- if so, don't complain.
+						 * This is a waste of time in normal scenarios but we must
+						 * do it to support CREATE INDEX CONCURRENTLY.
+						 *
+						 * We must follow HOT-chains here because during
+						 * concurrent index build, we insert the root TID though
+						 * the actual tuple may be somewhere in the HOT-chain.
+						 * While following the chain we might not stop at the
+						 * exact tuple which triggered the insert, but that's OK
+						 * because if we find a live tuple anywhere in this chain,
+						 * we have a unique key conflict.  The other live tuple is
+						 * not part of this chain because it had a different index
+						 * entry.
+						 */
+						recheck = false;
+						ItemPointerCopy(&itup->t_tid, &htid);
+						if (heap_hot_search(&htid, heapRel, SnapshotSelf, NULL,
+									&recheck, &buffer, &heapTuple))
+						{
+							bool result = true;
+							if (recheck)
+							{
+								/*
+								 * Recheck if the tuple actually satisfies the
+								 * index key. Otherwise, we might be following
+								 * a wrong index pointer and mustn't entertain
+								 * this tuple
+								 */
+								result = btrecheck(rel, itup, heapRel, &heapTuple);
+								UnlockReleaseBuffer(buffer);
+							}
+							if (!result)
+								break;
+							/* Normal case --- it's still live */
+						}
+						else
+						{
+							/*
+							 * It's been deleted, so no error, and no need to
+							 * continue searching
+							 */
+							break;
+						}
 
-					{
-						Datum		values[INDEX_MAX_KEYS];
-						bool		isnull[INDEX_MAX_KEYS];
-						char	   *key_desc;
-
-						index_deform_tuple(itup, RelationGetDescr(rel),
-										   values, isnull);
-
-						key_desc = BuildIndexValueDescription(rel, values,
-															  isnull);
-
-						ereport(ERROR,
-								(errcode(ERRCODE_UNIQUE_VIOLATION),
-								 errmsg("duplicate key value violates unique constraint \"%s\"",
-										RelationGetRelationName(rel)),
-							   key_desc ? errdetail("Key %s already exists.",
-													key_desc) : 0,
-								 errtableconstraint(heapRel,
-											 RelationGetRelationName(rel))));
+						/*
+						 * Check for a conflict-in as we would if we were going to
+						 * write to this page.  We aren't actually going to write,
+						 * but we want a chance to report SSI conflicts that would
+						 * otherwise be masked by this unique constraint
+						 * violation.
+						 */
+						CheckForSerializableConflictIn(rel, NULL, buf);
+
+						/*
+						 * This is a definite conflict.  Break the tuple down into
+						 * datums and report the error.  But first, make sure we
+						 * release the buffer locks we're holding ---
+						 * BuildIndexValueDescription could make catalog accesses,
+						 * which in the worst case might touch this same index and
+						 * cause deadlocks.
+						 */
+						if (nbuf != InvalidBuffer)
+							_bt_relbuf(rel, nbuf);
+						_bt_relbuf(rel, buf);
+
+						{
+							Datum		values[INDEX_MAX_KEYS];
+							bool		isnull[INDEX_MAX_KEYS];
+							char	   *key_desc;
+
+							index_deform_tuple(itup, RelationGetDescr(rel),
+									values, isnull);
+
+							key_desc = BuildIndexValueDescription(rel, values,
+									isnull);
+
+							ereport(ERROR,
+									(errcode(ERRCODE_UNIQUE_VIOLATION),
+									 errmsg("duplicate key value violates unique constraint \"%s\"",
+										 RelationGetRelationName(rel)),
+									 key_desc ? errdetail("Key %s already exists.",
+										 key_desc) : 0,
+									 errtableconstraint(heapRel,
+										 RelationGetRelationName(rel))));
+						}
 					}
 				}
 				else if (all_dead)
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index 1bb1acf..cb5a796 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -23,6 +23,7 @@
 #include "access/xlog.h"
 #include "catalog/index.h"
 #include "commands/vacuum.h"
+#include "executor/nodeIndexscan.h"
 #include "storage/indexfsm.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
@@ -118,6 +119,7 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amendscan = btendscan;
 	amroutine->ammarkpos = btmarkpos;
 	amroutine->amrestrpos = btrestrpos;
+	amroutine->amrecheck = btrecheck;
 
 	PG_RETURN_POINTER(amroutine);
 }
@@ -298,8 +300,9 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	bool		res;
 
-	/* btree indexes are never lossy */
+	/* btree indexes are never lossy, except for WARM tuples */
 	scan->xs_recheck = false;
+	scan->xs_tuple_recheck = false;
 
 	/*
 	 * If we have any array keys, initialize them during first call for a
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index da0f330..9becaeb 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -20,11 +20,15 @@
 #include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/relscan.h"
+#include "catalog/index.h"
+#include "executor/executor.h"
 #include "miscadmin.h"
+#include "nodes/execnodes.h"
 #include "utils/array.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
+#include "utils/datum.h"
 
 
 typedef struct BTSortArrayContext
@@ -2065,3 +2069,103 @@ btproperty(Oid index_oid, int attno,
 			return false;		/* punt to generic code */
 	}
 }
+
+/*
+ * Check if the index tuple's key matches the one computed from the given heap
+ * tuple's attribute
+ */
+bool
+btrecheck(Relation indexRel, IndexTuple indexTuple,
+		Relation heapRel, HeapTuple heapTuple)
+{
+	IndexInfo  *indexInfo;
+	EState	   *estate;
+	ExprContext *econtext;
+	TupleTableSlot *slot;
+	Datum		values[INDEX_MAX_KEYS];
+	bool		isnull[INDEX_MAX_KEYS];
+	int			i;
+	bool		equal;
+	int         natts = indexRel->rd_rel->relnatts;
+	Form_pg_attribute att;
+
+	/* Get IndexInfo for this index */
+	indexInfo = BuildIndexInfo(indexRel);
+
+	/*
+	 * The heap tuple must be put into a slot for FormIndexDatum.
+	 */
+	slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRel));
+
+	ExecStoreTuple(heapTuple, slot, InvalidBuffer, false);
+
+	/*
+	 * Typically the index won't have expressions, but if it does we need an
+	 * EState to evaluate them.  We need it for exclusion constraints too,
+	 * even if they are just on simple columns.
+	 */
+	if (indexInfo->ii_Expressions != NIL ||
+			indexInfo->ii_ExclusionOps != NULL)
+	{
+		estate = CreateExecutorState();
+		econtext = GetPerTupleExprContext(estate);
+		econtext->ecxt_scantuple = slot;
+	}
+	else
+		estate = NULL;
+
+	/*
+	 * Form the index values and isnull flags for the index entry that we need
+	 * to check.
+	 *
+	 * Note: if the index uses functions that are not as immutable as they are
+	 * supposed to be, this could produce an index tuple different from the
+	 * original.  The index AM can catch such errors by verifying that it
+	 * finds a matching index entry with the tuple's TID.  For exclusion
+	 * constraints we check this in check_exclusion_constraint().
+	 */
+	FormIndexDatum(indexInfo, slot, estate, values, isnull);
+
+	equal = true;
+	for (i = 1; i <= natts; i++)
+	{
+		Datum 	indxvalue;
+		bool	indxisnull;
+
+		indxvalue = index_getattr(indexTuple, i, indexRel->rd_att, &indxisnull);
+
+		/*
+		 * If both are NULL, then they are equal
+		 */
+		if (isnull[i - 1] && indxisnull)
+			continue;
+
+		/*
+		 * If just one is NULL, then they are not equal
+		 */
+		if (isnull[i - 1] || indxisnull)
+		{
+			equal = false;
+			break;
+		}
+
+		/*
+		 * Now just do a raw memory comparison. If the index tuple was formed
+		 * using this heap tuple, the computed index values must match
+		 */
+		att = indexRel->rd_att->attrs[i - 1];
+		if (!datumIsEqual(values[i - 1], indxvalue, att->attbyval,
+					att->attlen))
+		{
+			equal = false;
+			break;
+		}
+	}
+
+	if (estate != NULL)
+		FreeExecutorState(estate);
+
+	ExecDropSingleTupleTableSlot(slot);
+
+	return equal;
+}
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index ca4b0bd..b0d2952 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -68,6 +68,7 @@ spghandler(PG_FUNCTION_ARGS)
 	amroutine->amendscan = spgendscan;
 	amroutine->ammarkpos = NULL;
 	amroutine->amrestrpos = NULL;
+	amroutine->amrecheck = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index a96bf69..eed5b0b 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -1230,6 +1230,9 @@ SetDefaultACL(InternalDefaultACL *iacls)
 	}
 	else
 	{
+		bool warm_update;
+		Bitmapset *modified_attrs;
+
 		/* Prepare to insert or update pg_default_acl entry */
 		MemSet(values, 0, sizeof(values));
 		MemSet(nulls, false, sizeof(nulls));
@@ -1245,6 +1248,8 @@ SetDefaultACL(InternalDefaultACL *iacls)
 
 			newtuple = heap_form_tuple(RelationGetDescr(rel), values, nulls);
 			simple_heap_insert(rel, newtuple);
+			warm_update = false;
+			modified_attrs = NULL;
 		}
 		else
 		{
@@ -1254,11 +1259,12 @@ SetDefaultACL(InternalDefaultACL *iacls)
 
 			newtuple = heap_modify_tuple(tuple, RelationGetDescr(rel),
 										 values, nulls, replaces);
-			simple_heap_update(rel, &newtuple->t_self, newtuple);
+			simple_heap_update(rel, &newtuple->t_self, newtuple, &warm_update,
+					&modified_attrs);
 		}
 
 		/* keep the catalog indexes up to date */
-		CatalogUpdateIndexes(rel, newtuple);
+		CatalogUpdateIndexes(rel, newtuple, warm_update, modified_attrs);
 
 		/* these dependencies don't change in an update */
 		if (isNew)
@@ -1686,13 +1692,17 @@ ExecGrant_Attribute(InternalGrant *istmt, Oid relOid, const char *relname,
 
 	if (need_update)
 	{
+		bool warm_update;
+		Bitmapset *modified_attrs;
+
 		newtuple = heap_modify_tuple(attr_tuple, RelationGetDescr(attRelation),
 									 values, nulls, replaces);
 
-		simple_heap_update(attRelation, &newtuple->t_self, newtuple);
+		simple_heap_update(attRelation, &newtuple->t_self, newtuple,
+				&warm_update, &modified_attrs);
 
 		/* keep the catalog indexes up to date */
-		CatalogUpdateIndexes(attRelation, newtuple);
+		CatalogUpdateIndexes(attRelation, newtuple, warm_update, modified_attrs);
 
 		/* Update initial privileges for extensions */
 		recordExtensionInitPriv(relOid, RelationRelationId, attnum,
@@ -1899,6 +1909,8 @@ ExecGrant_Relation(InternalGrant *istmt)
 			int			nnewmembers;
 			Oid		   *newmembers;
 			AclObjectKind aclkind;
+			bool		warm_update;
+			Bitmapset	*modified_attrs;
 
 			/* Determine ID to do the grant as, and available grant options */
 			select_best_grantor(GetUserId(), this_privileges,
@@ -1955,10 +1967,12 @@ ExecGrant_Relation(InternalGrant *istmt)
 			newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation),
 										 values, nulls, replaces);
 
-			simple_heap_update(relation, &newtuple->t_self, newtuple);
+			simple_heap_update(relation, &newtuple->t_self, newtuple,
+					&warm_update, &modified_attrs);
 
 			/* keep the catalog indexes up to date */
-			CatalogUpdateIndexes(relation, newtuple);
+			CatalogUpdateIndexes(relation, newtuple, warm_update,
+					modified_attrs);
 
 			/* Update initial privileges for extensions */
 			recordExtensionInitPriv(relOid, RelationRelationId, 0, new_acl);
@@ -2079,6 +2093,8 @@ ExecGrant_Database(InternalGrant *istmt)
 		Oid		   *oldmembers;
 		Oid		   *newmembers;
 		HeapTuple	tuple;
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
 
 		tuple = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(datId));
 		if (!HeapTupleIsValid(tuple))
@@ -2148,10 +2164,11 @@ ExecGrant_Database(InternalGrant *istmt)
 		newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values,
 									 nulls, replaces);
 
-		simple_heap_update(relation, &newtuple->t_self, newtuple);
+		simple_heap_update(relation, &newtuple->t_self, newtuple, &warm_update,
+				&modified_attrs);
 
 		/* keep the catalog indexes up to date */
-		CatalogUpdateIndexes(relation, newtuple);
+		CatalogUpdateIndexes(relation, newtuple, warm_update, modified_attrs);
 
 		/* Update the shared dependency ACL info */
 		updateAclDependencies(DatabaseRelationId, HeapTupleGetOid(tuple), 0,
@@ -2202,6 +2219,8 @@ ExecGrant_Fdw(InternalGrant *istmt)
 		int			nnewmembers;
 		Oid		   *oldmembers;
 		Oid		   *newmembers;
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
 
 		tuple = SearchSysCache1(FOREIGNDATAWRAPPEROID,
 								ObjectIdGetDatum(fdwid));
@@ -2273,10 +2292,11 @@ ExecGrant_Fdw(InternalGrant *istmt)
 		newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values,
 									 nulls, replaces);
 
-		simple_heap_update(relation, &newtuple->t_self, newtuple);
+		simple_heap_update(relation, &newtuple->t_self, newtuple, &warm_update,
+				&modified_attrs);
 
 		/* keep the catalog indexes up to date */
-		CatalogUpdateIndexes(relation, newtuple);
+		CatalogUpdateIndexes(relation, newtuple, warm_update, modified_attrs);
 
 		/* Update initial privileges for extensions */
 		recordExtensionInitPriv(fdwid, ForeignDataWrapperRelationId, 0,
@@ -2332,6 +2352,8 @@ ExecGrant_ForeignServer(InternalGrant *istmt)
 		int			nnewmembers;
 		Oid		   *oldmembers;
 		Oid		   *newmembers;
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
 
 		tuple = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(srvid));
 		if (!HeapTupleIsValid(tuple))
@@ -2402,10 +2424,11 @@ ExecGrant_ForeignServer(InternalGrant *istmt)
 		newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values,
 									 nulls, replaces);
 
-		simple_heap_update(relation, &newtuple->t_self, newtuple);
+		simple_heap_update(relation, &newtuple->t_self, newtuple, &warm_update,
+				&modified_attrs);
 
 		/* keep the catalog indexes up to date */
-		CatalogUpdateIndexes(relation, newtuple);
+		CatalogUpdateIndexes(relation, newtuple, warm_update, modified_attrs);
 
 		/* Update initial privileges for extensions */
 		recordExtensionInitPriv(srvid, ForeignServerRelationId, 0, new_acl);
@@ -2460,6 +2483,8 @@ ExecGrant_Function(InternalGrant *istmt)
 		int			nnewmembers;
 		Oid		   *oldmembers;
 		Oid		   *newmembers;
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
 
 		tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcId));
 		if (!HeapTupleIsValid(tuple))
@@ -2529,10 +2554,11 @@ ExecGrant_Function(InternalGrant *istmt)
 		newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values,
 									 nulls, replaces);
 
-		simple_heap_update(relation, &newtuple->t_self, newtuple);
+		simple_heap_update(relation, &newtuple->t_self, newtuple, &warm_update,
+				&modified_attrs);
 
 		/* keep the catalog indexes up to date */
-		CatalogUpdateIndexes(relation, newtuple);
+		CatalogUpdateIndexes(relation, newtuple, warm_update, modified_attrs);
 
 		/* Update initial privileges for extensions */
 		recordExtensionInitPriv(funcId, ProcedureRelationId, 0, new_acl);
@@ -2586,6 +2612,8 @@ ExecGrant_Language(InternalGrant *istmt)
 		int			nnewmembers;
 		Oid		   *oldmembers;
 		Oid		   *newmembers;
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
 
 		tuple = SearchSysCache1(LANGOID, ObjectIdGetDatum(langId));
 		if (!HeapTupleIsValid(tuple))
@@ -2663,10 +2691,11 @@ ExecGrant_Language(InternalGrant *istmt)
 		newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values,
 									 nulls, replaces);
 
-		simple_heap_update(relation, &newtuple->t_self, newtuple);
+		simple_heap_update(relation, &newtuple->t_self, newtuple, &warm_update,
+				&modified_attrs);
 
 		/* keep the catalog indexes up to date */
-		CatalogUpdateIndexes(relation, newtuple);
+		CatalogUpdateIndexes(relation, newtuple, warm_update, modified_attrs);
 
 		/* Update initial privileges for extensions */
 		recordExtensionInitPriv(langId, LanguageRelationId, 0, new_acl);
@@ -2724,6 +2753,8 @@ ExecGrant_Largeobject(InternalGrant *istmt)
 		ScanKeyData entry[1];
 		SysScanDesc scan;
 		HeapTuple	tuple;
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
 
 		/* There's no syscache for pg_largeobject_metadata */
 		ScanKeyInit(&entry[0],
@@ -2805,10 +2836,11 @@ ExecGrant_Largeobject(InternalGrant *istmt)
 		newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation),
 									 values, nulls, replaces);
 
-		simple_heap_update(relation, &newtuple->t_self, newtuple);
+		simple_heap_update(relation, &newtuple->t_self, newtuple, &warm_update,
+				&modified_attrs);
 
 		/* keep the catalog indexes up to date */
-		CatalogUpdateIndexes(relation, newtuple);
+		CatalogUpdateIndexes(relation, newtuple, warm_update, modified_attrs);
 
 		/* Update initial privileges for extensions */
 		recordExtensionInitPriv(loid, LargeObjectRelationId, 0, new_acl);
@@ -2863,6 +2895,8 @@ ExecGrant_Namespace(InternalGrant *istmt)
 		int			nnewmembers;
 		Oid		   *oldmembers;
 		Oid		   *newmembers;
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
 
 		tuple = SearchSysCache1(NAMESPACEOID, ObjectIdGetDatum(nspid));
 		if (!HeapTupleIsValid(tuple))
@@ -2933,10 +2967,11 @@ ExecGrant_Namespace(InternalGrant *istmt)
 		newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values,
 									 nulls, replaces);
 
-		simple_heap_update(relation, &newtuple->t_self, newtuple);
+		simple_heap_update(relation, &newtuple->t_self, newtuple, &warm_update,
+				&modified_attrs);
 
 		/* keep the catalog indexes up to date */
-		CatalogUpdateIndexes(relation, newtuple);
+		CatalogUpdateIndexes(relation, newtuple, warm_update, modified_attrs);
 
 		/* Update initial privileges for extensions */
 		recordExtensionInitPriv(nspid, NamespaceRelationId, 0, new_acl);
@@ -2990,6 +3025,9 @@ ExecGrant_Tablespace(InternalGrant *istmt)
 		Oid		   *oldmembers;
 		Oid		   *newmembers;
 		HeapTuple	tuple;
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
+
 
 		/* Search syscache for pg_tablespace */
 		tuple = SearchSysCache1(TABLESPACEOID, ObjectIdGetDatum(tblId));
@@ -3060,10 +3098,11 @@ ExecGrant_Tablespace(InternalGrant *istmt)
 		newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values,
 									 nulls, replaces);
 
-		simple_heap_update(relation, &newtuple->t_self, newtuple);
+		simple_heap_update(relation, &newtuple->t_self, newtuple, &warm_update,
+				&modified_attrs);
 
 		/* keep the catalog indexes up to date */
-		CatalogUpdateIndexes(relation, newtuple);
+		CatalogUpdateIndexes(relation, newtuple, warm_update, modified_attrs);
 
 		/* Update the shared dependency ACL info */
 		updateAclDependencies(TableSpaceRelationId, tblId, 0,
@@ -3113,6 +3152,8 @@ ExecGrant_Type(InternalGrant *istmt)
 		Oid		   *oldmembers;
 		Oid		   *newmembers;
 		HeapTuple	tuple;
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
 
 		/* Search syscache for pg_type */
 		tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typId));
@@ -3197,10 +3238,11 @@ ExecGrant_Type(InternalGrant *istmt)
 		newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values,
 									 nulls, replaces);
 
-		simple_heap_update(relation, &newtuple->t_self, newtuple);
+		simple_heap_update(relation, &newtuple->t_self, newtuple, &warm_update,
+				&modified_attrs);
 
 		/* keep the catalog indexes up to date */
-		CatalogUpdateIndexes(relation, newtuple);
+		CatalogUpdateIndexes(relation, newtuple, warm_update, modified_attrs);
 
 		/* Update initial privileges for extensions */
 		recordExtensionInitPriv(typId, TypeRelationId, 0, new_acl);
@@ -5354,6 +5396,9 @@ recordExtensionInitPriv(Oid objoid, Oid classoid, int objsubid, Acl *new_acl)
 		/* If we have a new ACL to set, then update the row with it. */
 		if (new_acl)
 		{
+			bool	warm_update;
+			Bitmapset	*modified_attrs;
+
 			MemSet(values, 0, sizeof(values));
 			MemSet(nulls, false, sizeof(nulls));
 			MemSet(replace, false, sizeof(replace));
@@ -5364,10 +5409,12 @@ recordExtensionInitPriv(Oid objoid, Oid classoid, int objsubid, Acl *new_acl)
 			oldtuple = heap_modify_tuple(oldtuple, RelationGetDescr(relation),
 										 values, nulls, replace);
 
-			simple_heap_update(relation, &oldtuple->t_self, oldtuple);
+			simple_heap_update(relation, &oldtuple->t_self, oldtuple,
+					&warm_update, &modified_attrs);
 
 			/* keep the catalog indexes up to date */
-			CatalogUpdateIndexes(relation, oldtuple);
+			CatalogUpdateIndexes(relation, oldtuple, warm_update,
+					modified_attrs);
 		}
 		else
 			/* new_acl is NULL, so delete the entry we found. */
@@ -5396,7 +5443,7 @@ recordExtensionInitPriv(Oid objoid, Oid classoid, int objsubid, Acl *new_acl)
 		simple_heap_insert(relation, tuple);
 
 		/* keep the catalog indexes up to date */
-		CatalogUpdateIndexes(relation, tuple);
+		CatalogUpdateIndexes(relation, tuple, false, NULL);
 	}
 
 	systable_endscan(scan);
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index bfc54a8..8a8cdee 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -644,9 +644,9 @@ InsertPgAttributeTuple(Relation pg_attribute_rel,
 	simple_heap_insert(pg_attribute_rel, tup);
 
 	if (indstate != NULL)
-		CatalogIndexInsert(indstate, tup);
+		CatalogIndexInsert(indstate, tup, false, NULL);
 	else
-		CatalogUpdateIndexes(pg_attribute_rel, tup);
+		CatalogUpdateIndexes(pg_attribute_rel, tup, false, NULL);
 
 	heap_freetuple(tup);
 }
@@ -837,7 +837,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	/* finally insert the new tuple, update the indexes, and clean up */
 	simple_heap_insert(pg_class_desc, tup);
 
-	CatalogUpdateIndexes(pg_class_desc, tup);
+	CatalogUpdateIndexes(pg_class_desc, tup, false, NULL);
 
 	heap_freetuple(tup);
 }
@@ -1581,6 +1581,9 @@ RemoveAttributeById(Oid relid, AttrNumber attnum)
 	}
 	else
 	{
+		bool	warm_update;
+		Bitmapset	*modified_attrs;
+
 		/* Dropping user attributes is lots harder */
 
 		/* Mark the attribute as dropped */
@@ -1610,10 +1613,11 @@ RemoveAttributeById(Oid relid, AttrNumber attnum)
 				 "........pg.dropped.%d........", attnum);
 		namestrcpy(&(attStruct->attname), newattname);
 
-		simple_heap_update(attr_rel, &tuple->t_self, tuple);
+		simple_heap_update(attr_rel, &tuple->t_self, tuple, &warm_update,
+				&modified_attrs);
 
 		/* keep the system catalog indexes current */
-		CatalogUpdateIndexes(attr_rel, tuple);
+		CatalogUpdateIndexes(attr_rel, tuple, warm_update, modified_attrs);
 	}
 
 	/*
@@ -1701,6 +1705,8 @@ RemoveAttrDefaultById(Oid attrdefId)
 	HeapTuple	tuple;
 	Oid			myrelid;
 	AttrNumber	myattnum;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/* Grab an appropriate lock on the pg_attrdef relation */
 	attrdef_rel = heap_open(AttrDefaultRelationId, RowExclusiveLock);
@@ -1742,10 +1748,11 @@ RemoveAttrDefaultById(Oid attrdefId)
 
 	((Form_pg_attribute) GETSTRUCT(tuple))->atthasdef = false;
 
-	simple_heap_update(attr_rel, &tuple->t_self, tuple);
+	simple_heap_update(attr_rel, &tuple->t_self, tuple, &warm_update,
+			&modified_attrs);
 
 	/* keep the system catalog indexes current */
-	CatalogUpdateIndexes(attr_rel, tuple);
+	CatalogUpdateIndexes(attr_rel, tuple, warm_update, modified_attrs);
 
 	/*
 	 * Our update of the pg_attribute row will force a relcache rebuild, so
@@ -1945,7 +1952,7 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
 	tuple = heap_form_tuple(adrel->rd_att, values, nulls);
 	attrdefOid = simple_heap_insert(adrel, tuple);
 
-	CatalogUpdateIndexes(adrel, tuple);
+	CatalogUpdateIndexes(adrel, tuple, false, NULL);
 
 	defobject.classId = AttrDefaultRelationId;
 	defobject.objectId = attrdefOid;
@@ -1974,10 +1981,14 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
 	attStruct = (Form_pg_attribute) GETSTRUCT(atttup);
 	if (!attStruct->atthasdef)
 	{
+		bool	warm_update;
+		Bitmapset	*modified_attrs;
+
 		attStruct->atthasdef = true;
-		simple_heap_update(attrrel, &atttup->t_self, atttup);
+		simple_heap_update(attrrel, &atttup->t_self, atttup, &warm_update,
+				&modified_attrs);
 		/* keep catalog indexes current */
-		CatalogUpdateIndexes(attrrel, atttup);
+		CatalogUpdateIndexes(attrrel, atttup, warm_update, modified_attrs);
 	}
 	heap_close(attrrel, RowExclusiveLock);
 	heap_freetuple(atttup);
@@ -2479,6 +2490,9 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
 
 		if (con->conrelid == RelationGetRelid(rel))
 		{
+			bool	warm_update;
+			Bitmapset	*modified_attrs;
+
 			/* Found it.  Conflicts if not identical check constraint */
 			if (con->contype == CONSTRAINT_CHECK)
 			{
@@ -2572,8 +2586,9 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
 				Assert(is_local);
 				con->connoinherit = true;
 			}
-			simple_heap_update(conDesc, &tup->t_self, tup);
-			CatalogUpdateIndexes(conDesc, tup);
+			simple_heap_update(conDesc, &tup->t_self, tup, &warm_update,
+					&modified_attrs);
+			CatalogUpdateIndexes(conDesc, tup, warm_update, modified_attrs);
 			break;
 		}
 	}
@@ -2611,12 +2626,16 @@ SetRelationNumChecks(Relation rel, int numchecks)
 
 	if (relStruct->relchecks != numchecks)
 	{
+		bool	warm_update;
+		Bitmapset	*modified_attrs;
+
 		relStruct->relchecks = numchecks;
 
-		simple_heap_update(relrel, &reltup->t_self, reltup);
+		simple_heap_update(relrel, &reltup->t_self, reltup, &warm_update,
+				&modified_attrs);
 
 		/* keep catalog indexes current */
-		CatalogUpdateIndexes(relrel, reltup);
+		CatalogUpdateIndexes(relrel, reltup, warm_update, modified_attrs);
 	}
 	else
 	{
@@ -3159,7 +3178,7 @@ StorePartitionKey(Relation rel,
 	simple_heap_insert(pg_partitioned_table, tuple);
 
 	/* Update the indexes on pg_partitioned_table */
-	CatalogUpdateIndexes(pg_partitioned_table, tuple);
+	CatalogUpdateIndexes(pg_partitioned_table, tuple, false, NULL);
 	heap_close(pg_partitioned_table, RowExclusiveLock);
 
 	/* Mark this relation as dependent on a few things as follows */
@@ -3243,6 +3262,8 @@ StorePartitionBound(Relation rel, Relation parent, Node *bound)
 	Datum	new_val[Natts_pg_class];
 	bool	new_null[Natts_pg_class],
 			new_repl[Natts_pg_class];
+	bool	warm_update;
+	Bitmapset	*modified_attrs;
 
 	/* Update pg_class tuple */
 	classRel = heap_open(RelationRelationId, RowExclusiveLock);
@@ -3276,8 +3297,9 @@ StorePartitionBound(Relation rel, Relation parent, Node *bound)
 								 new_val, new_null, new_repl);
 	/* Also set the flag */
 	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
-	simple_heap_update(classRel, &newtuple->t_self, newtuple);
-	CatalogUpdateIndexes(classRel, newtuple);
+	simple_heap_update(classRel, &newtuple->t_self, newtuple, &warm_update,
+			&modified_attrs);
+	CatalogUpdateIndexes(classRel, newtuple, warm_update, modified_attrs);
 	heap_freetuple(newtuple);
 	heap_close(classRel, RowExclusiveLock);
 
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 26cbc0e..04ea34f 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
+#include "optimizer/var.h"
 #include "parser/parser.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
@@ -652,7 +653,7 @@ UpdateIndexRelation(Oid indexoid,
 	simple_heap_insert(pg_index, tuple);
 
 	/* update the indexes on pg_index */
-	CatalogUpdateIndexes(pg_index, tuple);
+	CatalogUpdateIndexes(pg_index, tuple, false, NULL);
 
 	/*
 	 * close the relation and free the tuple
@@ -1324,8 +1325,13 @@ index_constraint_create(Relation heapRelation,
 
 		if (dirty)
 		{
-			simple_heap_update(pg_index, &indexTuple->t_self, indexTuple);
-			CatalogUpdateIndexes(pg_index, indexTuple);
+			bool	warm_update;
+			Bitmapset	*modified_attrs;
+
+			simple_heap_update(pg_index, &indexTuple->t_self, indexTuple,
+					&warm_update, &modified_attrs);
+			CatalogUpdateIndexes(pg_index, indexTuple, warm_update,
+					modified_attrs);
 
 			InvokeObjectPostAlterHookArg(IndexRelationId, indexRelationId, 0,
 										 InvalidOid, is_internal);
@@ -1691,6 +1697,20 @@ BuildIndexInfo(Relation index)
 	ii->ii_Concurrent = false;
 	ii->ii_BrokenHotChain = false;
 
+	/* build a bitmap of all table attributes referred by this index */
+	for (i = 0; i < ii->ii_NumIndexAttrs; i++)
+	{
+		AttrNumber attr = ii->ii_KeyAttrNumbers[i];
+		ii->ii_indxattrs = bms_add_member(ii->ii_indxattrs, attr -
+				FirstLowInvalidHeapAttributeNumber);
+	}
+
+	/* Collect all attributes used in expressions, too */
+	pull_varattnos((Node *) ii->ii_Expressions, 1, &ii->ii_indxattrs);
+
+	/* Collect all attributes in the index predicate, too */
+	pull_varattnos((Node *) ii->ii_Predicate, 1, &ii->ii_indxattrs);
+
 	return ii;
 }
 
@@ -2090,6 +2110,8 @@ index_build(Relation heapRelation,
 		Relation	pg_index;
 		HeapTuple	indexTuple;
 		Form_pg_index indexForm;
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
 
 		pg_index = heap_open(IndexRelationId, RowExclusiveLock);
 
@@ -2103,8 +2125,9 @@ index_build(Relation heapRelation,
 		Assert(!indexForm->indcheckxmin);
 
 		indexForm->indcheckxmin = true;
-		simple_heap_update(pg_index, &indexTuple->t_self, indexTuple);
-		CatalogUpdateIndexes(pg_index, indexTuple);
+		simple_heap_update(pg_index, &indexTuple->t_self, indexTuple,
+				&warm_update, &modified_attrs);
+		CatalogUpdateIndexes(pg_index, indexTuple, warm_update, modified_attrs);
 
 		heap_freetuple(indexTuple);
 		heap_close(pg_index, RowExclusiveLock);
@@ -3441,6 +3464,9 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 			(indexForm->indcheckxmin && !indexInfo->ii_BrokenHotChain) ||
 			early_pruning_enabled)
 		{
+			bool	warm_update;
+			Bitmapset	*modified_attrs;
+
 			if (!indexInfo->ii_BrokenHotChain && !early_pruning_enabled)
 				indexForm->indcheckxmin = false;
 			else if (index_bad || early_pruning_enabled)
@@ -3448,8 +3474,10 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 			indexForm->indisvalid = true;
 			indexForm->indisready = true;
 			indexForm->indislive = true;
-			simple_heap_update(pg_index, &indexTuple->t_self, indexTuple);
-			CatalogUpdateIndexes(pg_index, indexTuple);
+			simple_heap_update(pg_index, &indexTuple->t_self, indexTuple,
+					&warm_update, &modified_attrs);
+			CatalogUpdateIndexes(pg_index, indexTuple, warm_update,
+					modified_attrs);
 
 			/*
 			 * Invalidate the relcache for the table, so that after we commit
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index 1915ca3..5046fd1 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -67,9 +67,13 @@ CatalogCloseIndexes(CatalogIndexState indstate)
  * This should be called for each inserted or updated catalog tuple.
  *
  * This is effectively a cut-down version of ExecInsertIndexTuples.
+ *
+ * See comments at CatalogUpdateIndexes for details about warm_update and
+ * modified_attrs
  */
 void
-CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple)
+CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple,
+		bool warm_update, Bitmapset *modified_attrs)
 {
 	int			i;
 	int			numIndexes;
@@ -79,12 +83,27 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple)
 	IndexInfo **indexInfoArray;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
+	ItemPointerData	root_tid;
 
-	/* HOT update does not require index inserts */
-	if (HeapTupleIsHeapOnly(heapTuple))
+	/* 
+	 * HOT update does not require index inserts, but WARM may need for some
+	 * indexes
+	 */
+	if (HeapTupleIsHeapOnly(heapTuple) && !warm_update)
 		return;
 
 	/*
+	 * If we've done a WARM update, then we must index the TID of the root line
+	 * pointer and not the actual TID of the new tuple.
+	 */
+	if (warm_update)
+		ItemPointerSet(&root_tid,
+				ItemPointerGetBlockNumber(&(heapTuple->t_self)),
+				HeapTupleHeaderGetRootOffset(heapTuple->t_data));
+	else
+		ItemPointerCopy(&heapTuple->t_self, &root_tid);
+
+	/*
 	 * Get information from the state structure.  Fall out if nothing to do.
 	 */
 	numIndexes = indstate->ri_NumIndices;
@@ -112,6 +131,17 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple)
 			continue;
 
 		/*
+		 * If we've done WARM update, then we must not insert a new index tuple
+		 * if none of the index keys have changed. This is not just an
+		 * optimization, but a requirement for WARM to work correctly.
+		 */
+		if (warm_update)
+		{
+			if (!bms_overlap(modified_attrs, indexInfo->ii_indxattrs))
+				continue;
+		}
+
+		/*
 		 * Expressional and partial indexes on system catalogs are not
 		 * supported, nor exclusion constraints, nor deferred uniqueness
 		 */
@@ -136,7 +166,7 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple)
 		index_insert(relationDescs[i],	/* index relation */
 					 values,	/* array of index Datums */
 					 isnull,	/* is-null flags */
-					 &(heapTuple->t_self),		/* tid of heap tuple */
+					 &root_tid,
 					 heapRelation,
 					 relationDescs[i]->rd_index->indisunique ?
 					 UNIQUE_CHECK_YES : UNIQUE_CHECK_NO);
@@ -152,13 +182,21 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple)
  * to insert or update a single tuple in a system catalog.  Avoid using it for
  * multiple tuples, since opening the indexes and building the index info
  * structures is moderately expensive.
+ *
+ * warm_update is passed as true if the indexes are being updated as a result
+ * of an update operation on the underlying system table and that update was a
+ * WARM update.
+ *
+ * modified_attrs contains a bitmap of attributes modified by the update
+ * operation.
  */
 void
-CatalogUpdateIndexes(Relation heapRel, HeapTuple heapTuple)
+CatalogUpdateIndexes(Relation heapRel, HeapTuple heapTuple,
+		bool warm_update, Bitmapset *modified_attrs)
 {
 	CatalogIndexState indstate;
 
 	indstate = CatalogOpenIndexes(heapRel);
-	CatalogIndexInsert(indstate, heapTuple);
+	CatalogIndexInsert(indstate, heapTuple, warm_update, modified_attrs);
 	CatalogCloseIndexes(indstate);
 }
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 3a4e22f..4642614 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -676,7 +676,7 @@ AggregateCreate(const char *aggName,
 	tup = heap_form_tuple(tupDesc, values, nulls);
 	simple_heap_insert(aggdesc, tup);
 
-	CatalogUpdateIndexes(aggdesc, tup);
+	CatalogUpdateIndexes(aggdesc, tup, false, NULL);
 
 	heap_close(aggdesc, RowExclusiveLock);
 
diff --git a/src/backend/catalog/pg_collation.c b/src/backend/catalog/pg_collation.c
index 694c0f6..d143a4a 100644
--- a/src/backend/catalog/pg_collation.c
+++ b/src/backend/catalog/pg_collation.c
@@ -138,7 +138,7 @@ CollationCreate(const char *collname, Oid collnamespace,
 	Assert(OidIsValid(oid));
 
 	/* update the index if any */
-	CatalogUpdateIndexes(rel, tup);
+	CatalogUpdateIndexes(rel, tup, false, NULL);
 
 	/* set up dependencies for the new collation */
 	myself.classId = CollationRelationId;
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index b5a0ce9..6757d9c 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -229,7 +229,7 @@ CreateConstraintEntry(const char *constraintName,
 	conOid = simple_heap_insert(conDesc, tup);
 
 	/* update catalog indexes */
-	CatalogUpdateIndexes(conDesc, tup);
+	CatalogUpdateIndexes(conDesc, tup, false, NULL);
 
 	conobject.classId = ConstraintRelationId;
 	conobject.objectId = conOid;
@@ -570,6 +570,8 @@ RemoveConstraintById(Oid conId)
 			Relation	pgrel;
 			HeapTuple	relTup;
 			Form_pg_class classForm;
+			bool		warm_update;
+			Bitmapset	*modified_attrs;
 
 			pgrel = heap_open(RelationRelationId, RowExclusiveLock);
 			relTup = SearchSysCacheCopy1(RELOID,
@@ -584,9 +586,10 @@ RemoveConstraintById(Oid conId)
 					 RelationGetRelationName(rel));
 			classForm->relchecks--;
 
-			simple_heap_update(pgrel, &relTup->t_self, relTup);
+			simple_heap_update(pgrel, &relTup->t_self, relTup, &warm_update,
+					&modified_attrs);
 
-			CatalogUpdateIndexes(pgrel, relTup);
+			CatalogUpdateIndexes(pgrel, relTup, warm_update, modified_attrs);
 
 			heap_freetuple(relTup);
 
@@ -632,6 +635,8 @@ RenameConstraintById(Oid conId, const char *newname)
 	Relation	conDesc;
 	HeapTuple	tuple;
 	Form_pg_constraint con;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	conDesc = heap_open(ConstraintRelationId, RowExclusiveLock);
 
@@ -666,10 +671,11 @@ RenameConstraintById(Oid conId, const char *newname)
 	/* OK, do the rename --- tuple is a copy, so OK to scribble on it */
 	namestrcpy(&(con->conname), newname);
 
-	simple_heap_update(conDesc, &tuple->t_self, tuple);
+	simple_heap_update(conDesc, &tuple->t_self, tuple, &warm_update,
+			&modified_attrs);
 
 	/* update the system catalog indexes */
-	CatalogUpdateIndexes(conDesc, tuple);
+	CatalogUpdateIndexes(conDesc, tuple, warm_update, modified_attrs);
 
 	InvokeObjectPostAlterHook(ConstraintRelationId, conId, 0);
 
@@ -731,13 +737,17 @@ AlterConstraintNamespaces(Oid ownerId, Oid oldNspId,
 		/* Don't update if the object is already part of the namespace */
 		if (conform->connamespace == oldNspId && oldNspId != newNspId)
 		{
+			bool	warm_update;
+			Bitmapset	*modified_attrs;
+
 			tup = heap_copytuple(tup);
 			conform = (Form_pg_constraint) GETSTRUCT(tup);
 
 			conform->connamespace = newNspId;
 
-			simple_heap_update(conRel, &tup->t_self, tup);
-			CatalogUpdateIndexes(conRel, tup);
+			simple_heap_update(conRel, &tup->t_self, tup, &warm_update,
+					&modified_attrs);
+			CatalogUpdateIndexes(conRel, tup, warm_update, modified_attrs);
 
 			/*
 			 * Note: currently, the constraint will not have its own
diff --git a/src/backend/catalog/pg_conversion.c b/src/backend/catalog/pg_conversion.c
index adaf7b8..99da1f3 100644
--- a/src/backend/catalog/pg_conversion.c
+++ b/src/backend/catalog/pg_conversion.c
@@ -108,7 +108,7 @@ ConversionCreate(const char *conname, Oid connamespace,
 	simple_heap_insert(rel, tup);
 
 	/* update the index if any */
-	CatalogUpdateIndexes(rel, tup);
+	CatalogUpdateIndexes(rel, tup, false, NULL);
 
 	myself.classId = ConversionRelationId;
 	myself.objectId = HeapTupleGetOid(tup);
diff --git a/src/backend/catalog/pg_db_role_setting.c b/src/backend/catalog/pg_db_role_setting.c
index 117cc8d..f592419 100644
--- a/src/backend/catalog/pg_db_role_setting.c
+++ b/src/backend/catalog/pg_db_role_setting.c
@@ -78,6 +78,8 @@ AlterSetting(Oid databaseid, Oid roleid, VariableSetStmt *setstmt)
 				bool		repl_null[Natts_pg_db_role_setting];
 				bool		repl_repl[Natts_pg_db_role_setting];
 				HeapTuple	newtuple;
+				bool		warm_update;
+				Bitmapset	*modified_attrs;
 
 				memset(repl_repl, false, sizeof(repl_repl));
 
@@ -88,10 +90,11 @@ AlterSetting(Oid databaseid, Oid roleid, VariableSetStmt *setstmt)
 
 				newtuple = heap_modify_tuple(tuple, RelationGetDescr(rel),
 											 repl_val, repl_null, repl_repl);
-				simple_heap_update(rel, &tuple->t_self, newtuple);
+				simple_heap_update(rel, &tuple->t_self, newtuple, &warm_update,
+						&modified_attrs);
 
 				/* Update indexes */
-				CatalogUpdateIndexes(rel, newtuple);
+				CatalogUpdateIndexes(rel, newtuple, warm_update, modified_attrs);
 			}
 			else
 				simple_heap_delete(rel, &tuple->t_self);
@@ -106,6 +109,8 @@ AlterSetting(Oid databaseid, Oid roleid, VariableSetStmt *setstmt)
 		Datum		datum;
 		bool		isnull;
 		ArrayType  *a;
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
 
 		memset(repl_repl, false, sizeof(repl_repl));
 		repl_repl[Anum_pg_db_role_setting_setconfig - 1] = true;
@@ -129,10 +134,11 @@ AlterSetting(Oid databaseid, Oid roleid, VariableSetStmt *setstmt)
 
 			newtuple = heap_modify_tuple(tuple, RelationGetDescr(rel),
 										 repl_val, repl_null, repl_repl);
-			simple_heap_update(rel, &tuple->t_self, newtuple);
+			simple_heap_update(rel, &tuple->t_self, newtuple, &warm_update,
+					&modified_attrs);
 
 			/* Update indexes */
-			CatalogUpdateIndexes(rel, newtuple);
+			CatalogUpdateIndexes(rel, newtuple, warm_update, modified_attrs);
 		}
 		else
 			simple_heap_delete(rel, &tuple->t_self);
@@ -158,7 +164,7 @@ AlterSetting(Oid databaseid, Oid roleid, VariableSetStmt *setstmt)
 		simple_heap_insert(rel, newtuple);
 
 		/* Update indexes */
-		CatalogUpdateIndexes(rel, newtuple);
+		CatalogUpdateIndexes(rel, newtuple, false, NULL);
 	}
 
 	InvokeObjectPostAlterHookArg(DbRoleSettingRelationId,
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index b71fa1b..cae00ad 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -113,7 +113,7 @@ recordMultipleDependencies(const ObjectAddress *depender,
 			if (indstate == NULL)
 				indstate = CatalogOpenIndexes(dependDesc);
 
-			CatalogIndexInsert(indstate, tup);
+			CatalogIndexInsert(indstate, tup, false, NULL);
 
 			heap_freetuple(tup);
 		}
@@ -356,14 +356,18 @@ changeDependencyFor(Oid classId, Oid objectId,
 				simple_heap_delete(depRel, &tup->t_self);
 			else
 			{
+				bool		warm_update;
+				Bitmapset	*modified_attrs;
+
 				/* make a modifiable copy */
 				tup = heap_copytuple(tup);
 				depform = (Form_pg_depend) GETSTRUCT(tup);
 
 				depform->refobjid = newRefObjectId;
 
-				simple_heap_update(depRel, &tup->t_self, tup);
-				CatalogUpdateIndexes(depRel, tup);
+				simple_heap_update(depRel, &tup->t_self, tup, &warm_update,
+						&modified_attrs);
+				CatalogUpdateIndexes(depRel, tup, warm_update, modified_attrs);
 
 				heap_freetuple(tup);
 			}
diff --git a/src/backend/catalog/pg_enum.c b/src/backend/catalog/pg_enum.c
index 089a9a0..4bf90a4 100644
--- a/src/backend/catalog/pg_enum.c
+++ b/src/backend/catalog/pg_enum.c
@@ -126,7 +126,7 @@ EnumValuesCreate(Oid enumTypeOid, List *vals)
 		HeapTupleSetOid(tup, oids[elemno]);
 
 		simple_heap_insert(pg_enum, tup);
-		CatalogUpdateIndexes(pg_enum, tup);
+		CatalogUpdateIndexes(pg_enum, tup, false, NULL);
 		heap_freetuple(tup);
 
 		elemno++;
@@ -459,7 +459,7 @@ restart:
 	enum_tup = heap_form_tuple(RelationGetDescr(pg_enum), values, nulls);
 	HeapTupleSetOid(enum_tup, newOid);
 	simple_heap_insert(pg_enum, enum_tup);
-	CatalogUpdateIndexes(pg_enum, enum_tup);
+	CatalogUpdateIndexes(pg_enum, enum_tup, false, NULL);
 	heap_freetuple(enum_tup);
 
 	heap_close(pg_enum, RowExclusiveLock);
@@ -483,6 +483,8 @@ RenameEnumLabel(Oid enumTypeOid,
 	HeapTuple	old_tup;
 	bool		found_new;
 	int			i;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/* check length of new label is ok */
 	if (strlen(newVal) > (NAMEDATALEN - 1))
@@ -543,8 +545,9 @@ RenameEnumLabel(Oid enumTypeOid,
 
 	/* Update the pg_enum entry */
 	namestrcpy(&en->enumlabel, newVal);
-	simple_heap_update(pg_enum, &enum_tup->t_self, enum_tup);
-	CatalogUpdateIndexes(pg_enum, enum_tup);
+	simple_heap_update(pg_enum, &enum_tup->t_self, enum_tup, &warm_update,
+			&modified_attrs);
+	CatalogUpdateIndexes(pg_enum, enum_tup, warm_update, modified_attrs);
 	heap_freetuple(enum_tup);
 
 	heap_close(pg_enum, RowExclusiveLock);
@@ -588,6 +591,8 @@ RenumberEnumType(Relation pg_enum, HeapTuple *existing, int nelems)
 		HeapTuple	newtup;
 		Form_pg_enum en;
 		float4		newsortorder;
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
 
 		newtup = heap_copytuple(existing[i]);
 		en = (Form_pg_enum) GETSTRUCT(newtup);
@@ -597,9 +602,10 @@ RenumberEnumType(Relation pg_enum, HeapTuple *existing, int nelems)
 		{
 			en->enumsortorder = newsortorder;
 
-			simple_heap_update(pg_enum, &newtup->t_self, newtup);
+			simple_heap_update(pg_enum, &newtup->t_self, newtup, &warm_update,
+					&modified_attrs);
 
-			CatalogUpdateIndexes(pg_enum, newtup);
+			CatalogUpdateIndexes(pg_enum, newtup, warm_update, modified_attrs);
 		}
 
 		heap_freetuple(newtup);
diff --git a/src/backend/catalog/pg_largeobject.c b/src/backend/catalog/pg_largeobject.c
index 24edf6a..7ece246 100644
--- a/src/backend/catalog/pg_largeobject.c
+++ b/src/backend/catalog/pg_largeobject.c
@@ -66,7 +66,7 @@ LargeObjectCreate(Oid loid)
 	loid_new = simple_heap_insert(pg_lo_meta, ntup);
 	Assert(!OidIsValid(loid) || loid == loid_new);
 
-	CatalogUpdateIndexes(pg_lo_meta, ntup);
+	CatalogUpdateIndexes(pg_lo_meta, ntup, false, NULL);
 
 	heap_freetuple(ntup);
 
diff --git a/src/backend/catalog/pg_namespace.c b/src/backend/catalog/pg_namespace.c
index f048ad4..6b31e7e 100644
--- a/src/backend/catalog/pg_namespace.c
+++ b/src/backend/catalog/pg_namespace.c
@@ -79,7 +79,7 @@ NamespaceCreate(const char *nspName, Oid ownerId, bool isTemp)
 	nspoid = simple_heap_insert(nspdesc, tup);
 	Assert(OidIsValid(nspoid));
 
-	CatalogUpdateIndexes(nspdesc, tup);
+	CatalogUpdateIndexes(nspdesc, tup, false, NULL);
 
 	heap_close(nspdesc, RowExclusiveLock);
 
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index 556f9fe..77bbd97 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -264,7 +264,7 @@ OperatorShellMake(const char *operatorName,
 	 */
 	operatorObjectId = simple_heap_insert(pg_operator_desc, tup);
 
-	CatalogUpdateIndexes(pg_operator_desc, tup);
+	CatalogUpdateIndexes(pg_operator_desc, tup, false, NULL);
 
 	/* Add dependencies for the entry */
 	makeOperatorDependencies(tup, false);
@@ -350,6 +350,8 @@ OperatorCreate(const char *operatorName,
 	NameData	oname;
 	int			i;
 	ObjectAddress address;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/*
 	 * Sanity checks
@@ -526,7 +528,8 @@ OperatorCreate(const char *operatorName,
 								nulls,
 								replaces);
 
-		simple_heap_update(pg_operator_desc, &tup->t_self, tup);
+		simple_heap_update(pg_operator_desc, &tup->t_self, tup, &warm_update,
+				&modified_attrs);
 	}
 	else
 	{
@@ -536,10 +539,12 @@ OperatorCreate(const char *operatorName,
 							  values, nulls);
 
 		operatorObjectId = simple_heap_insert(pg_operator_desc, tup);
+		warm_update = false;
+		modified_attrs = NULL;
 	}
 
 	/* Must update the indexes in either case */
-	CatalogUpdateIndexes(pg_operator_desc, tup);
+	CatalogUpdateIndexes(pg_operator_desc, tup, warm_update, modified_attrs);
 
 	/* Add dependencies for the entry */
 	address = makeOperatorDependencies(tup, isUpdate);
@@ -695,8 +700,12 @@ OperatorUpd(Oid baseId, Oid commId, Oid negId, bool isDelete)
 		/* If any columns were found to need modification, update tuple. */
 		if (update_commutator)
 		{
-			simple_heap_update(pg_operator_desc, &tup->t_self, tup);
-			CatalogUpdateIndexes(pg_operator_desc, tup);
+			bool		warm_update;
+			Bitmapset	*modified_attrs;
+			simple_heap_update(pg_operator_desc, &tup->t_self, tup,
+					&warm_update, &modified_attrs);
+			CatalogUpdateIndexes(pg_operator_desc, tup, warm_update,
+					modified_attrs);
 
 			/*
 			 * Do CCI to make the updated tuple visible.  We must do this in
@@ -741,8 +750,13 @@ OperatorUpd(Oid baseId, Oid commId, Oid negId, bool isDelete)
 		/* If any columns were found to need modification, update tuple. */
 		if (update_negator)
 		{
-			simple_heap_update(pg_operator_desc, &tup->t_self, tup);
-			CatalogUpdateIndexes(pg_operator_desc, tup);
+			bool		warm_update;
+			Bitmapset	*modified_attrs;
+
+			simple_heap_update(pg_operator_desc, &tup->t_self, tup,
+					&warm_update, &modified_attrs);
+			CatalogUpdateIndexes(pg_operator_desc, tup, warm_update,
+					modified_attrs);
 
 			/*
 			 * In the deletion case, do CCI to make the updated tuple visible.
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 7ae192a..0f7027a 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -118,6 +118,8 @@ ProcedureCreate(const char *procedureName,
 				referenced;
 	int			i;
 	Oid			trfid;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/*
 	 * sanity checks
@@ -573,7 +575,8 @@ ProcedureCreate(const char *procedureName,
 
 		/* Okay, do it... */
 		tup = heap_modify_tuple(oldtup, tupDesc, values, nulls, replaces);
-		simple_heap_update(rel, &tup->t_self, tup);
+		simple_heap_update(rel, &tup->t_self, tup, &warm_update,
+				&modified_attrs);
 
 		ReleaseSysCache(oldtup);
 		is_update = true;
@@ -593,10 +596,12 @@ ProcedureCreate(const char *procedureName,
 		tup = heap_form_tuple(tupDesc, values, nulls);
 		simple_heap_insert(rel, tup);
 		is_update = false;
+		warm_update = false;
+		modified_attrs = NULL;
 	}
 
 	/* Need to update indexes for either the insert or update case */
-	CatalogUpdateIndexes(rel, tup);
+	CatalogUpdateIndexes(rel, tup, warm_update, modified_attrs);
 
 	retval = HeapTupleGetOid(tup);
 
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 576b7fa..b93f7c3 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -149,7 +149,7 @@ publication_add_relation(Oid pubid, Relation targetrel,
 
 	/* Insert tuple into catalog. */
 	prrelid = simple_heap_insert(rel, tup);
-	CatalogUpdateIndexes(rel, tup);
+	CatalogUpdateIndexes(rel, tup, false, NULL);
 	heap_freetuple(tup);
 
 	ObjectAddressSet(myself, PublicationRelRelationId, prrelid);
diff --git a/src/backend/catalog/pg_range.c b/src/backend/catalog/pg_range.c
index d3a4c26..7d4cc5d 100644
--- a/src/backend/catalog/pg_range.c
+++ b/src/backend/catalog/pg_range.c
@@ -59,7 +59,7 @@ RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation,
 	tup = heap_form_tuple(RelationGetDescr(pg_range), values, nulls);
 
 	simple_heap_insert(pg_range, tup);
-	CatalogUpdateIndexes(pg_range, tup);
+	CatalogUpdateIndexes(pg_range, tup, false, NULL);
 	heap_freetuple(tup);
 
 	/* record type's dependencies on range-related items */
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index 60ed957..3019b4e 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -253,6 +253,8 @@ shdepChangeDep(Relation sdepRel,
 	}
 	else if (oldtup)
 	{
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
 		/* Need to update existing entry */
 		Form_pg_shdepend shForm = (Form_pg_shdepend) GETSTRUCT(oldtup);
 
@@ -260,10 +262,11 @@ shdepChangeDep(Relation sdepRel,
 		shForm->refclassid = refclassid;
 		shForm->refobjid = refobjid;
 
-		simple_heap_update(sdepRel, &oldtup->t_self, oldtup);
+		simple_heap_update(sdepRel, &oldtup->t_self, oldtup, &warm_update,
+				&modified_attrs);
 
 		/* keep indexes current */
-		CatalogUpdateIndexes(sdepRel, oldtup);
+		CatalogUpdateIndexes(sdepRel, oldtup, warm_update, modified_attrs);
 	}
 	else
 	{
@@ -290,7 +293,7 @@ shdepChangeDep(Relation sdepRel,
 		simple_heap_insert(sdepRel, oldtup);
 
 		/* keep indexes current */
-		CatalogUpdateIndexes(sdepRel, oldtup);
+		CatalogUpdateIndexes(sdepRel, oldtup, false, NULL);
 	}
 
 	if (oldtup)
@@ -762,7 +765,7 @@ copyTemplateDependencies(Oid templateDbId, Oid newDbId)
 		simple_heap_insert(sdepRel, newtup);
 
 		/* Keep indexes current */
-		CatalogIndexInsert(indstate, newtup);
+		CatalogIndexInsert(indstate, newtup, false, NULL);
 
 		heap_freetuple(newtup);
 	}
@@ -885,7 +888,7 @@ shdepAddDependency(Relation sdepRel,
 	simple_heap_insert(sdepRel, tup);
 
 	/* keep indexes current */
-	CatalogUpdateIndexes(sdepRel, tup);
+	CatalogUpdateIndexes(sdepRel, tup, false, NULL);
 
 	/* clean up */
 	heap_freetuple(tup);
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index 6d9a324..5857045 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -144,7 +144,7 @@ TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId)
 	 */
 	typoid = simple_heap_insert(pg_type_desc, tup);
 
-	CatalogUpdateIndexes(pg_type_desc, tup);
+	CatalogUpdateIndexes(pg_type_desc, tup, false, NULL);
 
 	/*
 	 * Create dependencies.  We can/must skip this in bootstrap mode.
@@ -237,6 +237,8 @@ TypeCreate(Oid newTypeOid,
 	int			i;
 	Acl		   *typacl = NULL;
 	ObjectAddress address;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/*
 	 * We assume that the caller validated the arguments individually, but did
@@ -430,7 +432,8 @@ TypeCreate(Oid newTypeOid,
 								nulls,
 								replaces);
 
-		simple_heap_update(pg_type_desc, &tup->t_self, tup);
+		simple_heap_update(pg_type_desc, &tup->t_self, tup, &warm_update,
+				&modified_attrs);
 
 		typeObjectId = HeapTupleGetOid(tup);
 
@@ -459,10 +462,12 @@ TypeCreate(Oid newTypeOid,
 		/* else allow system to assign oid */
 
 		typeObjectId = simple_heap_insert(pg_type_desc, tup);
+		warm_update = false;
+		modified_attrs = NULL;
 	}
 
 	/* Update indexes */
-	CatalogUpdateIndexes(pg_type_desc, tup);
+	CatalogUpdateIndexes(pg_type_desc, tup, warm_update, modified_attrs);
 
 	/*
 	 * Create dependencies.  We can/must skip this in bootstrap mode.
@@ -700,6 +705,8 @@ RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace)
 	HeapTuple	tuple;
 	Form_pg_type typ;
 	Oid			arrayOid;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	pg_type_desc = heap_open(TypeRelationId, RowExclusiveLock);
 
@@ -724,10 +731,11 @@ RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace)
 	/* OK, do the rename --- tuple is a copy, so OK to scribble on it */
 	namestrcpy(&(typ->typname), newTypeName);
 
-	simple_heap_update(pg_type_desc, &tuple->t_self, tuple);
+	simple_heap_update(pg_type_desc, &tuple->t_self, tuple, &warm_update,
+			&modified_attrs);
 
 	/* update the system catalog indexes */
-	CatalogUpdateIndexes(pg_type_desc, tuple);
+	CatalogUpdateIndexes(pg_type_desc, tuple, warm_update, modified_attrs);
 
 	InvokeObjectPostAlterHook(TypeRelationId, typeOid, 0);
 
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 4dfedf8..27bc137 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -487,6 +487,7 @@ CREATE VIEW pg_stat_all_tables AS
             pg_stat_get_tuples_updated(C.oid) AS n_tup_upd,
             pg_stat_get_tuples_deleted(C.oid) AS n_tup_del,
             pg_stat_get_tuples_hot_updated(C.oid) AS n_tup_hot_upd,
+            pg_stat_get_tuples_warm_updated(C.oid) AS n_tup_warm_upd,
             pg_stat_get_live_tuples(C.oid) AS n_live_tup,
             pg_stat_get_dead_tuples(C.oid) AS n_dead_tup,
             pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze,
@@ -517,7 +518,8 @@ CREATE VIEW pg_stat_xact_all_tables AS
             pg_stat_get_xact_tuples_inserted(C.oid) AS n_tup_ins,
             pg_stat_get_xact_tuples_updated(C.oid) AS n_tup_upd,
             pg_stat_get_xact_tuples_deleted(C.oid) AS n_tup_del,
-            pg_stat_get_xact_tuples_hot_updated(C.oid) AS n_tup_hot_upd
+            pg_stat_get_xact_tuples_hot_updated(C.oid) AS n_tup_hot_upd,
+            pg_stat_get_xact_tuples_warm_updated(C.oid) AS n_tup_warm_upd
     FROM pg_class C LEFT JOIN
          pg_index I ON C.oid = I.indrelid
          LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index ee4a182..b48f785 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -349,11 +349,15 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 
 	if (!IsBootstrapProcessingMode())
 	{
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
+
 		/* normal case, use a transactional update */
-		simple_heap_update(class_rel, &reltup->t_self, reltup);
+		simple_heap_update(class_rel, &reltup->t_self, reltup, &warm_update,
+				&modified_attrs);
 
 		/* Keep catalog indexes current */
-		CatalogUpdateIndexes(class_rel, reltup);
+		CatalogUpdateIndexes(class_rel, reltup, warm_update, modified_attrs);
 	}
 	else
 	{
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 768fcc8..5c03207 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -172,6 +172,8 @@ AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name)
 	bool	   *nulls;
 	bool	   *replaces;
 	NameData	nameattrdata;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	oldtup = SearchSysCache1(oidCacheId, ObjectIdGetDatum(objectId));
 	if (!HeapTupleIsValid(oldtup))
@@ -284,8 +286,9 @@ AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name)
 							   values, nulls, replaces);
 
 	/* Perform actual update */
-	simple_heap_update(rel, &oldtup->t_self, newtup);
-	CatalogUpdateIndexes(rel, newtup);
+	simple_heap_update(rel, &oldtup->t_self, newtup, &warm_update,
+			&modified_attrs);
+	CatalogUpdateIndexes(rel, newtup, warm_update, modified_attrs);
 
 	InvokeObjectPostAlterHook(classId, objectId, 0);
 
@@ -617,6 +620,8 @@ AlterObjectNamespace_internal(Relation rel, Oid objid, Oid nspOid)
 	Datum	   *values;
 	bool	   *nulls;
 	bool	   *replaces;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	tup = SearchSysCacheCopy1(oidCacheId, ObjectIdGetDatum(objid));
 	if (!HeapTupleIsValid(tup)) /* should not happen */
@@ -722,8 +727,8 @@ AlterObjectNamespace_internal(Relation rel, Oid objid, Oid nspOid)
 							   values, nulls, replaces);
 
 	/* Perform actual update */
-	simple_heap_update(rel, &tup->t_self, newtup);
-	CatalogUpdateIndexes(rel, newtup);
+	simple_heap_update(rel, &tup->t_self, newtup, &warm_update, &modified_attrs);
+	CatalogUpdateIndexes(rel, newtup, warm_update, modified_attrs);
 
 	/* Release memory */
 	pfree(values);
@@ -880,6 +885,8 @@ AlterObjectOwner_internal(Relation rel, Oid objectId, Oid new_ownerId)
 		Datum	   *values;
 		bool	   *nulls;
 		bool	   *replaces;
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
 
 		/* Superusers can bypass permission checks */
 		if (!superuser())
@@ -954,8 +961,9 @@ AlterObjectOwner_internal(Relation rel, Oid objectId, Oid new_ownerId)
 								   values, nulls, replaces);
 
 		/* Perform actual update */
-		simple_heap_update(rel, &newtup->t_self, newtup);
-		CatalogUpdateIndexes(rel, newtup);
+		simple_heap_update(rel, &newtup->t_self, newtup, &warm_update,
+				&modified_attrs);
+		CatalogUpdateIndexes(rel, newtup, warm_update, modified_attrs);
 
 		/* Update owner dependency reference */
 		if (classId == LargeObjectMetadataRelationId)
diff --git a/src/backend/commands/amcmds.c b/src/backend/commands/amcmds.c
index 29061b8..2f33b2c 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -88,7 +88,7 @@ CreateAccessMethod(CreateAmStmt *stmt)
 	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
 
 	amoid = simple_heap_insert(rel, tup);
-	CatalogUpdateIndexes(rel, tup);
+	CatalogUpdateIndexes(rel, tup, false, NULL);
 	heap_freetuple(tup);
 
 	myself.classId = AccessMethodRelationId;
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index e3e1a53..b9a9ede 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -1498,6 +1498,8 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 		Datum		values[Natts_pg_statistic];
 		bool		nulls[Natts_pg_statistic];
 		bool		replaces[Natts_pg_statistic];
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
 
 		/* Ignore attr if we weren't able to collect stats */
 		if (!stats->stats_valid)
@@ -1589,17 +1591,20 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 									 nulls,
 									 replaces);
 			ReleaseSysCache(oldtup);
-			simple_heap_update(sd, &stup->t_self, stup);
+			simple_heap_update(sd, &stup->t_self, stup, &warm_update,
+					&modified_attrs);
 		}
 		else
 		{
 			/* No, insert new tuple */
 			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
 			simple_heap_insert(sd, stup);
+			warm_update = false;
+			modified_attrs = NULL;
 		}
 
 		/* update indexes too */
-		CatalogUpdateIndexes(sd, stup);
+		CatalogUpdateIndexes(sd, stup, warm_update, modified_attrs);
 
 		heap_freetuple(stup);
 	}
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index f9309fc..03ed871 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -522,18 +522,28 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
 		 */
 		if (indexForm->indisclustered)
 		{
+			bool		warm_update;
+			Bitmapset	*modified_attrs;
+
 			indexForm->indisclustered = false;
-			simple_heap_update(pg_index, &indexTuple->t_self, indexTuple);
-			CatalogUpdateIndexes(pg_index, indexTuple);
+			simple_heap_update(pg_index, &indexTuple->t_self, indexTuple,
+					&warm_update, &modified_attrs);
+			CatalogUpdateIndexes(pg_index, indexTuple, warm_update,
+					modified_attrs);
 		}
 		else if (thisIndexOid == indexOid)
 		{
+			bool		warm_update;
+			Bitmapset	*modified_attrs;
+
 			/* this was checked earlier, but let's be real sure */
 			if (!IndexIsValid(indexForm))
 				elog(ERROR, "cannot cluster on invalid index %u", indexOid);
 			indexForm->indisclustered = true;
-			simple_heap_update(pg_index, &indexTuple->t_self, indexTuple);
-			CatalogUpdateIndexes(pg_index, indexTuple);
+			simple_heap_update(pg_index, &indexTuple->t_self, indexTuple,
+					&warm_update, &modified_attrs);
+			CatalogUpdateIndexes(pg_index, indexTuple, warm_update,
+					modified_attrs);
 		}
 
 		InvokeObjectPostAlterHookArg(IndexRelationId, thisIndexOid, 0,
@@ -1287,13 +1297,18 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
 	 */
 	if (!target_is_pg_class)
 	{
-		simple_heap_update(relRelation, &reltup1->t_self, reltup1);
-		simple_heap_update(relRelation, &reltup2->t_self, reltup2);
+		bool warm_update1 = false, warm_update2 = false;
+		Bitmapset *modified_attrs1, *modified_attrs2;
+
+		simple_heap_update(relRelation, &reltup1->t_self, reltup1,
+				&warm_update1, &modified_attrs1);
+		simple_heap_update(relRelation, &reltup2->t_self, reltup2,
+				&warm_update2, &modified_attrs2);
 
 		/* Keep system catalogs current */
 		indstate = CatalogOpenIndexes(relRelation);
-		CatalogIndexInsert(indstate, reltup1);
-		CatalogIndexInsert(indstate, reltup2);
+		CatalogIndexInsert(indstate, reltup1, warm_update1, modified_attrs1);
+		CatalogIndexInsert(indstate, reltup2, warm_update2, modified_attrs2);
 		CatalogCloseIndexes(indstate);
 	}
 	else
@@ -1547,6 +1562,8 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 		Relation	relRelation;
 		HeapTuple	reltup;
 		Form_pg_class relform;
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
 
 		relRelation = heap_open(RelationRelationId, RowExclusiveLock);
 
@@ -1558,8 +1575,9 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 		relform->relfrozenxid = frozenXid;
 		relform->relminmxid = cutoffMulti;
 
-		simple_heap_update(relRelation, &reltup->t_self, reltup);
-		CatalogUpdateIndexes(relRelation, reltup);
+		simple_heap_update(relRelation, &reltup->t_self, reltup, &warm_update,
+				&modified_attrs);
+		CatalogUpdateIndexes(relRelation, reltup, warm_update, modified_attrs);
 
 		heap_close(relRelation, RowExclusiveLock);
 	}
diff --git a/src/backend/commands/comment.c b/src/backend/commands/comment.c
index ada0b03..60b3631 100644
--- a/src/backend/commands/comment.c
+++ b/src/backend/commands/comment.c
@@ -150,6 +150,8 @@ CreateComments(Oid oid, Oid classoid, int32 subid, char *comment)
 	bool		nulls[Natts_pg_description];
 	bool		replaces[Natts_pg_description];
 	int			i;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/* Reduce empty-string to NULL case */
 	if (comment != NULL && strlen(comment) == 0)
@@ -199,7 +201,8 @@ CreateComments(Oid oid, Oid classoid, int32 subid, char *comment)
 		{
 			newtuple = heap_modify_tuple(oldtuple, RelationGetDescr(description), values,
 										 nulls, replaces);
-			simple_heap_update(description, &oldtuple->t_self, newtuple);
+			simple_heap_update(description, &oldtuple->t_self, newtuple,
+					&warm_update, &modified_attrs);
 		}
 
 		break;					/* Assume there can be only one match */
@@ -214,12 +217,13 @@ CreateComments(Oid oid, Oid classoid, int32 subid, char *comment)
 		newtuple = heap_form_tuple(RelationGetDescr(description),
 								   values, nulls);
 		simple_heap_insert(description, newtuple);
+		warm_update = false;
 	}
 
 	/* Update indexes, if necessary */
 	if (newtuple != NULL)
 	{
-		CatalogUpdateIndexes(description, newtuple);
+		CatalogUpdateIndexes(description, newtuple, warm_update, modified_attrs);
 		heap_freetuple(newtuple);
 	}
 
@@ -249,6 +253,8 @@ CreateSharedComments(Oid oid, Oid classoid, char *comment)
 	bool		nulls[Natts_pg_shdescription];
 	bool		replaces[Natts_pg_shdescription];
 	int			i;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/* Reduce empty-string to NULL case */
 	if (comment != NULL && strlen(comment) == 0)
@@ -293,7 +299,8 @@ CreateSharedComments(Oid oid, Oid classoid, char *comment)
 		{
 			newtuple = heap_modify_tuple(oldtuple, RelationGetDescr(shdescription),
 										 values, nulls, replaces);
-			simple_heap_update(shdescription, &oldtuple->t_self, newtuple);
+			simple_heap_update(shdescription, &oldtuple->t_self, newtuple,
+					&warm_update, &modified_attrs);
 		}
 
 		break;					/* Assume there can be only one match */
@@ -308,12 +315,14 @@ CreateSharedComments(Oid oid, Oid classoid, char *comment)
 		newtuple = heap_form_tuple(RelationGetDescr(shdescription),
 								   values, nulls);
 		simple_heap_insert(shdescription, newtuple);
+		warm_update = false;
 	}
 
 	/* Update indexes, if necessary */
 	if (newtuple != NULL)
 	{
-		CatalogUpdateIndexes(shdescription, newtuple);
+		CatalogUpdateIndexes(shdescription, newtuple, warm_update,
+				modified_attrs);
 		heap_freetuple(newtuple);
 	}
 
diff --git a/src/backend/commands/constraint.c b/src/backend/commands/constraint.c
index 77cf8ce..faef5b4 100644
--- a/src/backend/commands/constraint.c
+++ b/src/backend/commands/constraint.c
@@ -40,6 +40,7 @@ unique_key_recheck(PG_FUNCTION_ARGS)
 	TriggerData *trigdata = (TriggerData *) fcinfo->context;
 	const char *funcname = "unique_key_recheck";
 	HeapTuple	new_row;
+	HeapTupleData heapTuple;
 	ItemPointerData tmptid;
 	Relation	indexRel;
 	IndexInfo  *indexInfo;
@@ -102,7 +103,8 @@ unique_key_recheck(PG_FUNCTION_ARGS)
 	 * removed.
 	 */
 	tmptid = new_row->t_self;
-	if (!heap_hot_search(&tmptid, trigdata->tg_relation, SnapshotSelf, NULL))
+	if (!heap_hot_search(&tmptid, trigdata->tg_relation, SnapshotSelf, NULL,
+				NULL, NULL, &heapTuple))
 	{
 		/*
 		 * All rows in the HOT chain are dead, so skip the check.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index c05e14e..55b955a 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2669,6 +2669,8 @@ CopyFrom(CopyState cstate)
 					if (resultRelInfo->ri_NumIndices > 0)
 						recheckIndexes = ExecInsertIndexTuples(slot,
 															&(tuple->t_self),
+															&(tuple->t_self),
+															NULL,
 															   estate,
 															   false,
 															   NULL,
@@ -2823,6 +2825,7 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
 			ExecStoreTuple(bufferedTuples[i], myslot, InvalidBuffer, false);
 			recheckIndexes =
 				ExecInsertIndexTuples(myslot, &(bufferedTuples[i]->t_self),
+									  &(bufferedTuples[i]->t_self), NULL,
 									  estate, false, NULL, NIL);
 			ExecARInsertTriggers(estate, resultRelInfo,
 								 bufferedTuples[i],
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 6ad8fd7..db9f9fc 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -549,7 +549,7 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 	simple_heap_insert(pg_database_rel, tuple);
 
 	/* Update indexes */
-	CatalogUpdateIndexes(pg_database_rel, tuple);
+	CatalogUpdateIndexes(pg_database_rel, tuple, false, NULL);
 
 	/*
 	 * Now generate additional catalog entries associated with the new DB
@@ -978,6 +978,8 @@ RenameDatabase(const char *oldname, const char *newname)
 	int			notherbackends;
 	int			npreparedxacts;
 	ObjectAddress address;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/*
 	 * Look up the target database's OID, and get exclusive lock on it. We
@@ -1040,8 +1042,9 @@ RenameDatabase(const char *oldname, const char *newname)
 	if (!HeapTupleIsValid(newtup))
 		elog(ERROR, "cache lookup failed for database %u", db_id);
 	namestrcpy(&(((Form_pg_database) GETSTRUCT(newtup))->datname), newname);
-	simple_heap_update(rel, &newtup->t_self, newtup);
-	CatalogUpdateIndexes(rel, newtup);
+	simple_heap_update(rel, &newtup->t_self, newtup, &warm_update,
+			&modified_attrs);
+	CatalogUpdateIndexes(rel, newtup, warm_update, modified_attrs);
 
 	InvokeObjectPostAlterHook(DatabaseRelationId, db_id, 0);
 
@@ -1081,6 +1084,8 @@ movedb(const char *dbname, const char *tblspcname)
 	DIR		   *dstdir;
 	struct dirent *xlde;
 	movedb_failure_params fparms;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/*
 	 * Look up the target database's OID, and get exclusive lock on it. We
@@ -1296,10 +1301,11 @@ movedb(const char *dbname, const char *tblspcname)
 		newtuple = heap_modify_tuple(oldtuple, RelationGetDescr(pgdbrel),
 									 new_record,
 									 new_record_nulls, new_record_repl);
-		simple_heap_update(pgdbrel, &oldtuple->t_self, newtuple);
+		simple_heap_update(pgdbrel, &oldtuple->t_self, newtuple, &warm_update,
+				&modified_attrs);
 
 		/* Update indexes */
-		CatalogUpdateIndexes(pgdbrel, newtuple);
+		CatalogUpdateIndexes(pgdbrel, newtuple, warm_update, modified_attrs);
 
 		InvokeObjectPostAlterHook(DatabaseRelationId,
 								  HeapTupleGetOid(newtuple), 0);
@@ -1413,6 +1419,8 @@ AlterDatabase(ParseState *pstate, AlterDatabaseStmt *stmt, bool isTopLevel)
 	Datum		new_record[Natts_pg_database];
 	bool		new_record_nulls[Natts_pg_database];
 	bool		new_record_repl[Natts_pg_database];
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/* Extract options from the statement node tree */
 	foreach(option, stmt->options)
@@ -1554,10 +1562,11 @@ AlterDatabase(ParseState *pstate, AlterDatabaseStmt *stmt, bool isTopLevel)
 
 	newtuple = heap_modify_tuple(tuple, RelationGetDescr(rel), new_record,
 								 new_record_nulls, new_record_repl);
-	simple_heap_update(rel, &tuple->t_self, newtuple);
+	simple_heap_update(rel, &tuple->t_self, newtuple, &warm_update,
+			&modified_attrs);
 
 	/* Update indexes */
-	CatalogUpdateIndexes(rel, newtuple);
+	CatalogUpdateIndexes(rel, newtuple, warm_update, modified_attrs);
 
 	InvokeObjectPostAlterHook(DatabaseRelationId,
 							  HeapTupleGetOid(newtuple), 0);
@@ -1610,6 +1619,8 @@ AlterDatabaseOwner(const char *dbname, Oid newOwnerId)
 	SysScanDesc scan;
 	Form_pg_database datForm;
 	ObjectAddress address;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/*
 	 * Get the old tuple.  We don't need a lock on the database per se,
@@ -1692,8 +1703,9 @@ AlterDatabaseOwner(const char *dbname, Oid newOwnerId)
 		}
 
 		newtuple = heap_modify_tuple(tuple, RelationGetDescr(rel), repl_val, repl_null, repl_repl);
-		simple_heap_update(rel, &newtuple->t_self, newtuple);
-		CatalogUpdateIndexes(rel, newtuple);
+		simple_heap_update(rel, &newtuple->t_self, newtuple, &warm_update,
+				&modified_attrs);
+		CatalogUpdateIndexes(rel, newtuple, warm_update, modified_attrs);
 
 		heap_freetuple(newtuple);
 
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 8125537..63cdff8 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -406,7 +406,7 @@ insert_event_trigger_tuple(char *trigname, char *eventname, Oid evtOwner,
 	/* Insert heap tuple. */
 	tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
 	trigoid = simple_heap_insert(tgrel, tuple);
-	CatalogUpdateIndexes(tgrel, tuple);
+	CatalogUpdateIndexes(tgrel, tuple, false, NULL);
 	heap_freetuple(tuple);
 
 	/* Depend on owner. */
@@ -503,6 +503,8 @@ AlterEventTrigger(AlterEventTrigStmt *stmt)
 	Oid			trigoid;
 	Form_pg_event_trigger evtForm;
 	char		tgenabled = stmt->tgenabled;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	tgrel = heap_open(EventTriggerRelationId, RowExclusiveLock);
 
@@ -524,8 +526,9 @@ AlterEventTrigger(AlterEventTrigStmt *stmt)
 	evtForm = (Form_pg_event_trigger) GETSTRUCT(tup);
 	evtForm->evtenabled = tgenabled;
 
-	simple_heap_update(tgrel, &tup->t_self, tup);
-	CatalogUpdateIndexes(tgrel, tup);
+	simple_heap_update(tgrel, &tup->t_self, tup, &warm_update,
+			&modified_attrs);
+	CatalogUpdateIndexes(tgrel, tup, warm_update, modified_attrs);
 
 	InvokeObjectPostAlterHook(EventTriggerRelationId,
 							  trigoid, 0);
@@ -602,6 +605,8 @@ static void
 AlterEventTriggerOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
 {
 	Form_pg_event_trigger form;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	form = (Form_pg_event_trigger) GETSTRUCT(tup);
 
@@ -621,8 +626,8 @@ AlterEventTriggerOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
 			 errhint("The owner of an event trigger must be a superuser.")));
 
 	form->evtowner = newOwnerId;
-	simple_heap_update(rel, &tup->t_self, tup);
-	CatalogUpdateIndexes(rel, tup);
+	simple_heap_update(rel, &tup->t_self, tup, &warm_update, &modified_attrs);
+	CatalogUpdateIndexes(rel, tup, warm_update, modified_attrs);
 
 	/* Update owner dependency reference */
 	changeDependencyOnOwner(EventTriggerRelationId,
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 554fdc4..01e6a54 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1773,7 +1773,7 @@ InsertExtensionTuple(const char *extName, Oid extOwner,
 	tuple = heap_form_tuple(rel->rd_att, values, nulls);
 
 	extensionOid = simple_heap_insert(rel, tuple);
-	CatalogUpdateIndexes(rel, tuple);
+	CatalogUpdateIndexes(rel, tuple, false, NULL);
 
 	heap_freetuple(tuple);
 	heap_close(rel, RowExclusiveLock);
@@ -2332,6 +2332,8 @@ pg_extension_config_dump(PG_FUNCTION_ARGS)
 	bool		repl_null[Natts_pg_extension];
 	bool		repl_repl[Natts_pg_extension];
 	ArrayType  *a;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/*
 	 * We only allow this to be called from an extension's SQL script. We
@@ -2484,8 +2486,9 @@ pg_extension_config_dump(PG_FUNCTION_ARGS)
 	extTup = heap_modify_tuple(extTup, RelationGetDescr(extRel),
 							   repl_val, repl_null, repl_repl);
 
-	simple_heap_update(extRel, &extTup->t_self, extTup);
-	CatalogUpdateIndexes(extRel, extTup);
+	simple_heap_update(extRel, &extTup->t_self, extTup, &warm_update,
+			&modified_attrs);
+	CatalogUpdateIndexes(extRel, extTup, warm_update, modified_attrs);
 
 	systable_endscan(extScan);
 
@@ -2516,6 +2519,8 @@ extension_config_remove(Oid extensionoid, Oid tableoid)
 	bool		repl_null[Natts_pg_extension];
 	bool		repl_repl[Natts_pg_extension];
 	ArrayType  *a;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/* Find the pg_extension tuple */
 	extRel = heap_open(ExtensionRelationId, RowExclusiveLock);
@@ -2662,8 +2667,9 @@ extension_config_remove(Oid extensionoid, Oid tableoid)
 	extTup = heap_modify_tuple(extTup, RelationGetDescr(extRel),
 							   repl_val, repl_null, repl_repl);
 
-	simple_heap_update(extRel, &extTup->t_self, extTup);
-	CatalogUpdateIndexes(extRel, extTup);
+	simple_heap_update(extRel, &extTup->t_self, extTup, &warm_update,
+			&modified_attrs);
+	CatalogUpdateIndexes(extRel, extTup, warm_update, modified_attrs);
 
 	systable_endscan(extScan);
 
@@ -2691,6 +2697,8 @@ AlterExtensionNamespace(List *names, const char *newschema, Oid *oldschema)
 	HeapTuple	depTup;
 	ObjectAddresses *objsMoved;
 	ObjectAddress extAddr;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	if (list_length(names) != 1)
 		ereport(ERROR,
@@ -2843,8 +2851,9 @@ AlterExtensionNamespace(List *names, const char *newschema, Oid *oldschema)
 	/* Now adjust pg_extension.extnamespace */
 	extForm->extnamespace = nspOid;
 
-	simple_heap_update(extRel, &extTup->t_self, extTup);
-	CatalogUpdateIndexes(extRel, extTup);
+	simple_heap_update(extRel, &extTup->t_self, extTup, &warm_update,
+			&modified_attrs);
+	CatalogUpdateIndexes(extRel, extTup, warm_update, modified_attrs);
 
 	heap_close(extRel, RowExclusiveLock);
 
@@ -3042,6 +3051,8 @@ ApplyExtensionUpdates(Oid extensionOid,
 		bool		repl[Natts_pg_extension];
 		ObjectAddress myself;
 		ListCell   *lc;
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
 
 		/*
 		 * Fetch parameters for specific version (pcontrol is not changed)
@@ -3090,8 +3101,9 @@ ApplyExtensionUpdates(Oid extensionOid,
 		extTup = heap_modify_tuple(extTup, RelationGetDescr(extRel),
 								   values, nulls, repl);
 
-		simple_heap_update(extRel, &extTup->t_self, extTup);
-		CatalogUpdateIndexes(extRel, extTup);
+		simple_heap_update(extRel, &extTup->t_self, extTup, &warm_update,
+				&modified_attrs);
+		CatalogUpdateIndexes(extRel, extTup, warm_update, modified_attrs);
 
 		systable_endscan(extScan);
 
diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c
index 476a023..d76ccda 100644
--- a/src/backend/commands/foreigncmds.c
+++ b/src/backend/commands/foreigncmds.c
@@ -234,6 +234,9 @@ AlterForeignDataWrapperOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerI
 
 	if (form->fdwowner != newOwnerId)
 	{
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
+
 		memset(repl_null, false, sizeof(repl_null));
 		memset(repl_repl, false, sizeof(repl_repl));
 
@@ -256,8 +259,9 @@ AlterForeignDataWrapperOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerI
 		tup = heap_modify_tuple(tup, RelationGetDescr(rel), repl_val, repl_null,
 								repl_repl);
 
-		simple_heap_update(rel, &tup->t_self, tup);
-		CatalogUpdateIndexes(rel, tup);
+		simple_heap_update(rel, &tup->t_self, tup, &warm_update,
+				&modified_attrs);
+		CatalogUpdateIndexes(rel, tup, warm_update, modified_attrs);
 
 		/* Update owner dependency reference */
 		changeDependencyOnOwner(ForeignDataWrapperRelationId,
@@ -349,6 +353,9 @@ AlterForeignServerOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
 
 	if (form->srvowner != newOwnerId)
 	{
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
+
 		/* Superusers can always do it */
 		if (!superuser())
 		{
@@ -397,8 +404,9 @@ AlterForeignServerOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
 		tup = heap_modify_tuple(tup, RelationGetDescr(rel), repl_val, repl_null,
 								repl_repl);
 
-		simple_heap_update(rel, &tup->t_self, tup);
-		CatalogUpdateIndexes(rel, tup);
+		simple_heap_update(rel, &tup->t_self, tup, &warm_update,
+				&modified_attrs);
+		CatalogUpdateIndexes(rel, tup, warm_update, modified_attrs);
 
 		/* Update owner dependency reference */
 		changeDependencyOnOwner(ForeignServerRelationId, HeapTupleGetOid(tup),
@@ -630,7 +638,7 @@ CreateForeignDataWrapper(CreateFdwStmt *stmt)
 	tuple = heap_form_tuple(rel->rd_att, values, nulls);
 
 	fdwId = simple_heap_insert(rel, tuple);
-	CatalogUpdateIndexes(rel, tuple);
+	CatalogUpdateIndexes(rel, tuple, false, NULL);
 
 	heap_freetuple(tuple);
 
@@ -689,6 +697,8 @@ AlterForeignDataWrapper(AlterFdwStmt *stmt)
 	Oid			fdwhandler;
 	Oid			fdwvalidator;
 	ObjectAddress myself;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock);
 
@@ -786,8 +796,8 @@ AlterForeignDataWrapper(AlterFdwStmt *stmt)
 	tp = heap_modify_tuple(tp, RelationGetDescr(rel),
 						   repl_val, repl_null, repl_repl);
 
-	simple_heap_update(rel, &tp->t_self, tp);
-	CatalogUpdateIndexes(rel, tp);
+	simple_heap_update(rel, &tp->t_self, tp, &warm_update, &modified_attrs);
+	CatalogUpdateIndexes(rel, tp, warm_update, modified_attrs);
 
 	heap_freetuple(tp);
 
@@ -943,7 +953,7 @@ CreateForeignServer(CreateForeignServerStmt *stmt)
 
 	srvId = simple_heap_insert(rel, tuple);
 
-	CatalogUpdateIndexes(rel, tuple);
+	CatalogUpdateIndexes(rel, tuple, false, NULL);
 
 	heap_freetuple(tuple);
 
@@ -985,6 +995,8 @@ AlterForeignServer(AlterForeignServerStmt *stmt)
 	Oid			srvId;
 	Form_pg_foreign_server srvForm;
 	ObjectAddress address;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	rel = heap_open(ForeignServerRelationId, RowExclusiveLock);
 
@@ -1056,8 +1068,8 @@ AlterForeignServer(AlterForeignServerStmt *stmt)
 	tp = heap_modify_tuple(tp, RelationGetDescr(rel),
 						   repl_val, repl_null, repl_repl);
 
-	simple_heap_update(rel, &tp->t_self, tp);
-	CatalogUpdateIndexes(rel, tp);
+	simple_heap_update(rel, &tp->t_self, tp, &warm_update, &modified_attrs);
+	CatalogUpdateIndexes(rel, tp, warm_update, modified_attrs);
 
 	InvokeObjectPostAlterHook(ForeignServerRelationId, srvId, 0);
 
@@ -1192,7 +1204,7 @@ CreateUserMapping(CreateUserMappingStmt *stmt)
 
 	umId = simple_heap_insert(rel, tuple);
 
-	CatalogUpdateIndexes(rel, tuple);
+	CatalogUpdateIndexes(rel, tuple, false, NULL);
 
 	heap_freetuple(tuple);
 
@@ -1240,6 +1252,8 @@ AlterUserMapping(AlterUserMappingStmt *stmt)
 	ForeignServer *srv;
 	ObjectAddress address;
 	RoleSpec   *role = (RoleSpec *) stmt->user;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	rel = heap_open(UserMappingRelationId, RowExclusiveLock);
 
@@ -1307,8 +1321,8 @@ AlterUserMapping(AlterUserMappingStmt *stmt)
 	tp = heap_modify_tuple(tp, RelationGetDescr(rel),
 						   repl_val, repl_null, repl_repl);
 
-	simple_heap_update(rel, &tp->t_self, tp);
-	CatalogUpdateIndexes(rel, tp);
+	simple_heap_update(rel, &tp->t_self, tp, &warm_update, &modified_attrs);
+	CatalogUpdateIndexes(rel, tp, warm_update, modified_attrs);
 
 	ObjectAddressSet(address, UserMappingRelationId, umId);
 
@@ -1485,7 +1499,7 @@ CreateForeignTable(CreateForeignTableStmt *stmt, Oid relid)
 	tuple = heap_form_tuple(ftrel->rd_att, values, nulls);
 
 	simple_heap_insert(ftrel, tuple);
-	CatalogUpdateIndexes(ftrel, tuple);
+	CatalogUpdateIndexes(ftrel, tuple, false, NULL);
 
 	heap_freetuple(tuple);
 
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 22aecb2..8a48bdc 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -1181,6 +1181,8 @@ AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt)
 	DefElem    *rows_item = NULL;
 	DefElem    *parallel_item = NULL;
 	ObjectAddress address;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	rel = heap_open(ProcedureRelationId, RowExclusiveLock);
 
@@ -1295,8 +1297,8 @@ AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt)
 		procForm->proparallel = interpret_func_parallel(parallel_item);
 
 	/* Do the update */
-	simple_heap_update(rel, &tup->t_self, tup);
-	CatalogUpdateIndexes(rel, tup);
+	simple_heap_update(rel, &tup->t_self, tup, &warm_update, &modified_attrs);
+	CatalogUpdateIndexes(rel, tup, warm_update, modified_attrs);
 
 	InvokeObjectPostAlterHook(ProcedureRelationId, funcOid, 0);
 
@@ -1321,6 +1323,8 @@ SetFunctionReturnType(Oid funcOid, Oid newRetType)
 	Relation	pg_proc_rel;
 	HeapTuple	tup;
 	Form_pg_proc procForm;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	pg_proc_rel = heap_open(ProcedureRelationId, RowExclusiveLock);
 
@@ -1336,9 +1340,10 @@ SetFunctionReturnType(Oid funcOid, Oid newRetType)
 	procForm->prorettype = newRetType;
 
 	/* update the catalog and its indexes */
-	simple_heap_update(pg_proc_rel, &tup->t_self, tup);
+	simple_heap_update(pg_proc_rel, &tup->t_self, tup, &warm_update,
+			&modified_attrs);
 
-	CatalogUpdateIndexes(pg_proc_rel, tup);
+	CatalogUpdateIndexes(pg_proc_rel, tup, warm_update, modified_attrs);
 
 	heap_close(pg_proc_rel, RowExclusiveLock);
 }
@@ -1355,6 +1360,8 @@ SetFunctionArgType(Oid funcOid, int argIndex, Oid newArgType)
 	Relation	pg_proc_rel;
 	HeapTuple	tup;
 	Form_pg_proc procForm;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	pg_proc_rel = heap_open(ProcedureRelationId, RowExclusiveLock);
 
@@ -1371,9 +1378,10 @@ SetFunctionArgType(Oid funcOid, int argIndex, Oid newArgType)
 	procForm->proargtypes.values[argIndex] = newArgType;
 
 	/* update the catalog and its indexes */
-	simple_heap_update(pg_proc_rel, &tup->t_self, tup);
+	simple_heap_update(pg_proc_rel, &tup->t_self, tup, &warm_update,
+			&modified_attrs);
 
-	CatalogUpdateIndexes(pg_proc_rel, tup);
+	CatalogUpdateIndexes(pg_proc_rel, tup, warm_update, modified_attrs);
 
 	heap_close(pg_proc_rel, RowExclusiveLock);
 }
@@ -1661,7 +1669,7 @@ CreateCast(CreateCastStmt *stmt)
 
 	castid = simple_heap_insert(relation, tuple);
 
-	CatalogUpdateIndexes(relation, tuple);
+	CatalogUpdateIndexes(relation, tuple, false, NULL);
 
 	/* make dependency entries */
 	myself.classId = CastRelationId;
@@ -1806,6 +1814,8 @@ CreateTransform(CreateTransformStmt *stmt)
 	ObjectAddress myself,
 				referenced;
 	bool		is_replace;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/*
 	 * Get the type
@@ -1924,7 +1934,8 @@ CreateTransform(CreateTransformStmt *stmt)
 		replaces[Anum_pg_transform_trftosql - 1] = true;
 
 		newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values, nulls, replaces);
-		simple_heap_update(relation, &newtuple->t_self, newtuple);
+		simple_heap_update(relation, &newtuple->t_self, newtuple, &warm_update,
+				&modified_attrs);
 
 		transformid = HeapTupleGetOid(tuple);
 		ReleaseSysCache(tuple);
@@ -1935,9 +1946,11 @@ CreateTransform(CreateTransformStmt *stmt)
 		newtuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
 		transformid = simple_heap_insert(relation, newtuple);
 		is_replace = false;
+		warm_update = false;
+		modified_attrs = NULL;
 	}
 
-	CatalogUpdateIndexes(relation, newtuple);
+	CatalogUpdateIndexes(relation, newtuple, warm_update, modified_attrs);
 
 	if (is_replace)
 		deleteDependencyRecordsFor(TransformRelationId, transformid, true);
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 6b5a9b6..c22173d 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -83,6 +83,8 @@ SetMatViewPopulatedState(Relation relation, bool newstate)
 {
 	Relation	pgrel;
 	HeapTuple	tuple;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	Assert(relation->rd_rel->relkind == RELKIND_MATVIEW);
 
@@ -100,9 +102,10 @@ SetMatViewPopulatedState(Relation relation, bool newstate)
 
 	((Form_pg_class) GETSTRUCT(tuple))->relispopulated = newstate;
 
-	simple_heap_update(pgrel, &tuple->t_self, tuple);
+	simple_heap_update(pgrel, &tuple->t_self, tuple, &warm_update,
+			&modified_attrs);
 
-	CatalogUpdateIndexes(pgrel, tuple);
+	CatalogUpdateIndexes(pgrel, tuple, warm_update, modified_attrs);
 
 	heap_freetuple(tuple);
 	heap_close(pgrel, RowExclusiveLock);
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index 7cfcc6d..609d92b 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -280,7 +280,7 @@ CreateOpFamily(char *amname, char *opfname, Oid namespaceoid, Oid amoid)
 
 	opfamilyoid = simple_heap_insert(rel, tup);
 
-	CatalogUpdateIndexes(rel, tup);
+	CatalogUpdateIndexes(rel, tup, false, NULL);
 
 	heap_freetuple(tup);
 
@@ -657,7 +657,7 @@ DefineOpClass(CreateOpClassStmt *stmt)
 
 	opclassoid = simple_heap_insert(rel, tup);
 
-	CatalogUpdateIndexes(rel, tup);
+	CatalogUpdateIndexes(rel, tup, false, NULL);
 
 	heap_freetuple(tup);
 
@@ -1332,7 +1332,7 @@ storeOperators(List *opfamilyname, Oid amoid,
 
 		entryoid = simple_heap_insert(rel, tup);
 
-		CatalogUpdateIndexes(rel, tup);
+		CatalogUpdateIndexes(rel, tup, false, NULL);
 
 		heap_freetuple(tup);
 
@@ -1443,7 +1443,7 @@ storeProcedures(List *opfamilyname, Oid amoid,
 
 		entryoid = simple_heap_insert(rel, tup);
 
-		CatalogUpdateIndexes(rel, tup);
+		CatalogUpdateIndexes(rel, tup, false, NULL);
 
 		heap_freetuple(tup);
 
diff --git a/src/backend/commands/operatorcmds.c b/src/backend/commands/operatorcmds.c
index a273376..e93a71a 100644
--- a/src/backend/commands/operatorcmds.c
+++ b/src/backend/commands/operatorcmds.c
@@ -400,6 +400,8 @@ AlterOperator(AlterOperatorStmt *stmt)
 	List	   *joinName = NIL; /* optional join sel. procedure */
 	bool		updateJoin = false;
 	Oid			joinOid;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/* Look up the operator */
 	oprId = LookupOperNameTypeNames(NULL, stmt->opername,
@@ -518,8 +520,9 @@ AlterOperator(AlterOperatorStmt *stmt)
 	tup = heap_modify_tuple(tup, RelationGetDescr(catalog),
 							values, nulls, replaces);
 
-	simple_heap_update(catalog, &tup->t_self, tup);
-	CatalogUpdateIndexes(catalog, tup);
+	simple_heap_update(catalog, &tup->t_self, tup, &warm_update,
+			&modified_attrs);
+	CatalogUpdateIndexes(catalog, tup, warm_update, modified_attrs);
 
 	address = makeOperatorDependencies(tup, true);
 
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index 5d9d3a6..a080fc0 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -536,6 +536,8 @@ RemoveRoleFromObjectPolicy(Oid roleid, Oid classid, Oid policy_id)
 		HeapTuple	new_tuple;
 		ObjectAddress target;
 		ObjectAddress myself;
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
 
 		/* zero-clear */
 		memset(values, 0, sizeof(values));
@@ -614,10 +616,12 @@ RemoveRoleFromObjectPolicy(Oid roleid, Oid classid, Oid policy_id)
 		new_tuple = heap_modify_tuple(tuple,
 									  RelationGetDescr(pg_policy_rel),
 									  values, isnull, replaces);
-		simple_heap_update(pg_policy_rel, &new_tuple->t_self, new_tuple);
+		simple_heap_update(pg_policy_rel, &new_tuple->t_self, new_tuple,
+				&warm_update, &modified_attrs);
 
 		/* Update Catalog Indexes */
-		CatalogUpdateIndexes(pg_policy_rel, new_tuple);
+		CatalogUpdateIndexes(pg_policy_rel, new_tuple, warm_update,
+				modified_attrs);
 
 		/* Remove all old dependencies. */
 		deleteDependencyRecordsFor(PolicyRelationId, policy_id, false);
@@ -826,7 +830,7 @@ CreatePolicy(CreatePolicyStmt *stmt)
 	policy_id = simple_heap_insert(pg_policy_rel, policy_tuple);
 
 	/* Update Indexes */
-	CatalogUpdateIndexes(pg_policy_rel, policy_tuple);
+	CatalogUpdateIndexes(pg_policy_rel, policy_tuple, false, NULL);
 
 	/* Record Dependencies */
 	target.classId = RelationRelationId;
@@ -906,6 +910,8 @@ AlterPolicy(AlterPolicyStmt *stmt)
 	char		polcmd;
 	bool		polcmd_isnull;
 	int			i;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/* Parse role_ids */
 	if (stmt->roles != NULL)
@@ -1150,10 +1156,12 @@ AlterPolicy(AlterPolicyStmt *stmt)
 	new_tuple = heap_modify_tuple(policy_tuple,
 								  RelationGetDescr(pg_policy_rel),
 								  values, isnull, replaces);
-	simple_heap_update(pg_policy_rel, &new_tuple->t_self, new_tuple);
+	simple_heap_update(pg_policy_rel, &new_tuple->t_self, new_tuple,
+			&warm_update, &modified_attrs);
 
 	/* Update Catalog Indexes */
-	CatalogUpdateIndexes(pg_policy_rel, new_tuple);
+	CatalogUpdateIndexes(pg_policy_rel, new_tuple, warm_update,
+			modified_attrs);
 
 	/* Update Dependencies. */
 	deleteDependencyRecordsFor(PolicyRelationId, policy_id, false);
@@ -1217,6 +1225,8 @@ rename_policy(RenameStmt *stmt)
 	SysScanDesc sscan;
 	HeapTuple	policy_tuple;
 	ObjectAddress address;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/* Get id of table.  Also handles permissions checks. */
 	table_id = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
@@ -1287,10 +1297,12 @@ rename_policy(RenameStmt *stmt)
 	namestrcpy(&((Form_pg_policy) GETSTRUCT(policy_tuple))->polname,
 			   stmt->newname);
 
-	simple_heap_update(pg_policy_rel, &policy_tuple->t_self, policy_tuple);
+	simple_heap_update(pg_policy_rel, &policy_tuple->t_self, policy_tuple,
+			&warm_update, &modified_attrs);
 
 	/* keep system catalog indexes current */
-	CatalogUpdateIndexes(pg_policy_rel, policy_tuple);
+	CatalogUpdateIndexes(pg_policy_rel, policy_tuple, warm_update,
+			modified_attrs);
 
 	InvokeObjectPostAlterHook(PolicyRelationId,
 							  HeapTupleGetOid(policy_tuple), 0);
diff --git a/src/backend/commands/proclang.c b/src/backend/commands/proclang.c
index b684f41..aae5ef6 100644
--- a/src/backend/commands/proclang.c
+++ b/src/backend/commands/proclang.c
@@ -336,6 +336,8 @@ create_proc_lang(const char *languageName, bool replace,
 	bool		is_update;
 	ObjectAddress myself,
 				referenced;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	rel = heap_open(LanguageRelationId, RowExclusiveLock);
 	tupDesc = RelationGetDescr(rel);
@@ -378,7 +380,8 @@ create_proc_lang(const char *languageName, bool replace,
 
 		/* Okay, do it... */
 		tup = heap_modify_tuple(oldtup, tupDesc, values, nulls, replaces);
-		simple_heap_update(rel, &tup->t_self, tup);
+		simple_heap_update(rel, &tup->t_self, tup, &warm_update,
+				&modified_attrs);
 
 		ReleaseSysCache(oldtup);
 		is_update = true;
@@ -389,10 +392,12 @@ create_proc_lang(const char *languageName, bool replace,
 		tup = heap_form_tuple(tupDesc, values, nulls);
 		simple_heap_insert(rel, tup);
 		is_update = false;
+		warm_update = false;
+		modified_attrs = NULL;
 	}
 
 	/* Need to update indexes for either the insert or update case */
-	CatalogUpdateIndexes(rel, tup);
+	CatalogUpdateIndexes(rel, tup, warm_update, modified_attrs);
 
 	/*
 	 * Create dependencies for the new language.  If we are updating an
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 63dcc10..4980b36 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -215,7 +215,7 @@ CreatePublication(CreatePublicationStmt *stmt)
 
 	/* Insert tuple into catalog. */
 	puboid = simple_heap_insert(rel, tup);
-	CatalogUpdateIndexes(rel, tup);
+	CatalogUpdateIndexes(rel, tup, false, NULL);
 	heap_freetuple(tup);
 
 	recordDependencyOnOwner(PublicationRelationId, puboid, GetUserId());
@@ -260,6 +260,8 @@ AlterPublicationOptions(AlterPublicationStmt *stmt, Relation rel,
 	bool		publish_update;
 	bool		publish_delete;
 	ObjectAddress		obj;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	parse_publication_options(stmt->options,
 							  &publish_insert_given, &publish_insert,
@@ -294,8 +296,8 @@ AlterPublicationOptions(AlterPublicationStmt *stmt, Relation rel,
 							replaces);
 
 	/* Update the catalog. */
-	simple_heap_update(rel, &tup->t_self, tup);
-	CatalogUpdateIndexes(rel, tup);
+	simple_heap_update(rel, &tup->t_self, tup, &warm_update, &modified_attrs);
+	CatalogUpdateIndexes(rel, tup, warm_update, modified_attrs);
 
 	CommandCounterIncrement();
 
@@ -666,6 +668,8 @@ PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
 AlterPublicationOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
 {
 	Form_pg_publication form;
+	bool				warm_update;
+	Bitmapset			*modified_attrs;
 
 	form = (Form_pg_publication) GETSTRUCT(tup);
 
@@ -685,8 +689,8 @@ AlterPublicationOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
 				 errhint("The owner of a publication must be a superuser.")));
 
 	form->pubowner = newOwnerId;
-	simple_heap_update(rel, &tup->t_self, tup);
-	CatalogUpdateIndexes(rel, tup);
+	simple_heap_update(rel, &tup->t_self, tup, &warm_update, &modified_attrs);
+	CatalogUpdateIndexes(rel, tup, warm_update, modified_attrs);
 
 	/* Update owner dependency reference */
 	changeDependencyOnOwner(PublicationRelationId,
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index c3b37b2..d89e093 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -245,6 +245,8 @@ RenameSchema(const char *oldname, const char *newname)
 	Relation	rel;
 	AclResult	aclresult;
 	ObjectAddress address;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	rel = heap_open(NamespaceRelationId, RowExclusiveLock);
 
@@ -281,8 +283,8 @@ RenameSchema(const char *oldname, const char *newname)
 
 	/* rename */
 	namestrcpy(&(((Form_pg_namespace) GETSTRUCT(tup))->nspname), newname);
-	simple_heap_update(rel, &tup->t_self, tup);
-	CatalogUpdateIndexes(rel, tup);
+	simple_heap_update(rel, &tup->t_self, tup, &warm_update, &modified_attrs);
+	CatalogUpdateIndexes(rel, tup, warm_update, modified_attrs);
 
 	InvokeObjectPostAlterHook(NamespaceRelationId, HeapTupleGetOid(tup), 0);
 
@@ -370,6 +372,8 @@ AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerId)
 		bool		isNull;
 		HeapTuple	newtuple;
 		AclResult	aclresult;
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
 
 		/* Otherwise, must be owner of the existing object */
 		if (!pg_namespace_ownercheck(HeapTupleGetOid(tup), GetUserId()))
@@ -417,8 +421,9 @@ AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerId)
 
 		newtuple = heap_modify_tuple(tup, RelationGetDescr(rel), repl_val, repl_null, repl_repl);
 
-		simple_heap_update(rel, &newtuple->t_self, newtuple);
-		CatalogUpdateIndexes(rel, newtuple);
+		simple_heap_update(rel, &newtuple->t_self, newtuple, &warm_update,
+				&modified_attrs); CatalogUpdateIndexes(rel, newtuple,
+					warm_update, modified_attrs);
 
 		heap_freetuple(newtuple);
 
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 324f2e7..30d7af8 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -260,6 +260,8 @@ SetSharedSecurityLabel(const ObjectAddress *object,
 	Datum		values[Natts_pg_shseclabel];
 	bool		nulls[Natts_pg_shseclabel];
 	bool		replaces[Natts_pg_shseclabel];
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/* Prepare to form or update a tuple, if necessary. */
 	memset(nulls, false, sizeof(nulls));
@@ -299,7 +301,8 @@ SetSharedSecurityLabel(const ObjectAddress *object,
 			replaces[Anum_pg_shseclabel_label - 1] = true;
 			newtup = heap_modify_tuple(oldtup, RelationGetDescr(pg_shseclabel),
 									   values, nulls, replaces);
-			simple_heap_update(pg_shseclabel, &oldtup->t_self, newtup);
+			simple_heap_update(pg_shseclabel, &oldtup->t_self, newtup,
+					&warm_update, &modified_attrs);
 		}
 	}
 	systable_endscan(scan);
@@ -310,12 +313,13 @@ SetSharedSecurityLabel(const ObjectAddress *object,
 		newtup = heap_form_tuple(RelationGetDescr(pg_shseclabel),
 								 values, nulls);
 		simple_heap_insert(pg_shseclabel, newtup);
+		warm_update = false;
 	}
 
 	/* Update indexes, if necessary */
 	if (newtup != NULL)
 	{
-		CatalogUpdateIndexes(pg_shseclabel, newtup);
+		CatalogUpdateIndexes(pg_shseclabel, newtup, warm_update, modified_attrs);
 		heap_freetuple(newtup);
 	}
 
@@ -339,6 +343,8 @@ SetSecurityLabel(const ObjectAddress *object,
 	Datum		values[Natts_pg_seclabel];
 	bool		nulls[Natts_pg_seclabel];
 	bool		replaces[Natts_pg_seclabel];
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/* Shared objects have their own security label catalog. */
 	if (IsSharedRelation(object->classId))
@@ -390,7 +396,8 @@ SetSecurityLabel(const ObjectAddress *object,
 			replaces[Anum_pg_seclabel_label - 1] = true;
 			newtup = heap_modify_tuple(oldtup, RelationGetDescr(pg_seclabel),
 									   values, nulls, replaces);
-			simple_heap_update(pg_seclabel, &oldtup->t_self, newtup);
+			simple_heap_update(pg_seclabel, &oldtup->t_self, newtup,
+					&warm_update, &modified_attrs);
 		}
 	}
 	systable_endscan(scan);
@@ -401,12 +408,13 @@ SetSecurityLabel(const ObjectAddress *object,
 		newtup = heap_form_tuple(RelationGetDescr(pg_seclabel),
 								 values, nulls);
 		simple_heap_insert(pg_seclabel, newtup);
+		warm_update = false;
 	}
 
 	/* Update indexes, if necessary */
 	if (newtup != NULL)
 	{
-		CatalogUpdateIndexes(pg_seclabel, newtup);
+		CatalogUpdateIndexes(pg_seclabel, newtup, warm_update, modified_attrs);
 		heap_freetuple(newtup);
 	}
 
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 0c673f5..8d4d9a4 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -237,7 +237,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	tuple = heap_form_tuple(tupDesc, pgs_values, pgs_nulls);
 	simple_heap_insert(rel, tuple);
-	CatalogUpdateIndexes(rel, tuple);
+	CatalogUpdateIndexes(rel, tuple, false, NULL);
 
 	heap_freetuple(tuple);
 	heap_close(rel, RowExclusiveLock);
@@ -419,6 +419,8 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	ObjectAddress address;
 	Relation	rel;
 	HeapTuple	tuple;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/* Open and lock sequence. */
 	relid = RangeVarGetRelid(stmt->sequence, AccessShareLock, stmt->missing_ok);
@@ -504,8 +506,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	relation_close(seqrel, NoLock);
 
-	simple_heap_update(rel, &tuple->t_self, tuple);
-	CatalogUpdateIndexes(rel, tuple);
+	simple_heap_update(rel, &tuple->t_self, tuple, &warm_update,
+			&modified_attrs);
+	CatalogUpdateIndexes(rel, tuple, warm_update, modified_attrs);
 	heap_close(rel, RowExclusiveLock);
 
 	return address;
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 2b6d322..ccabde1 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -277,7 +277,7 @@ CreateSubscription(CreateSubscriptionStmt *stmt)
 
 	/* Insert tuple into catalog. */
 	subid = simple_heap_insert(rel, tup);
-	CatalogUpdateIndexes(rel, tup);
+	CatalogUpdateIndexes(rel, tup, false, NULL);
 	heap_freetuple(tup);
 
 	recordDependencyOnOwner(SubscriptionRelationId, subid, owner);
@@ -339,6 +339,8 @@ AlterSubscription(AlterSubscriptionStmt *stmt)
 	char	   *conninfo;
 	char	   *slot_name;
 	List	   *publications;
+	bool		warm_update;
+	Bitmapset  *modified_attrs;
 
 	rel = heap_open(SubscriptionRelationId, RowExclusiveLock);
 
@@ -397,8 +399,8 @@ AlterSubscription(AlterSubscriptionStmt *stmt)
 							replaces);
 
 	/* Update the catalog. */
-	simple_heap_update(rel, &tup->t_self, tup);
-	CatalogUpdateIndexes(rel, tup);
+	simple_heap_update(rel, &tup->t_self, tup, &warm_update, &modified_attrs);
+	CatalogUpdateIndexes(rel, tup, warm_update, modified_attrs);
 
 	ObjectAddressSet(myself, SubscriptionRelationId, subid);
 
@@ -558,6 +560,8 @@ static void
 AlterSubscriptionOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
 {
 	Form_pg_subscription form;
+	bool				 warm_update;
+	Bitmapset  			*modified_attrs;
 
 	form = (Form_pg_subscription) GETSTRUCT(tup);
 
@@ -577,8 +581,8 @@ AlterSubscriptionOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
 			 errhint("The owner of an subscription must be a superuser.")));
 
 	form->subowner = newOwnerId;
-	simple_heap_update(rel, &tup->t_self, tup);
-	CatalogUpdateIndexes(rel, tup);
+	simple_heap_update(rel, &tup->t_self, tup, &warm_update, &modified_attrs);
+	CatalogUpdateIndexes(rel, tup, warm_update, modified_attrs);
 
 	/* Update owner dependency reference */
 	changeDependencyOnOwner(SubscriptionRelationId,
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c4b0011..2d8d419 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2310,7 +2310,7 @@ StoreCatalogInheritance1(Oid relationId, Oid parentOid,
 
 	simple_heap_insert(inhRelation, tuple);
 
-	CatalogUpdateIndexes(inhRelation, tuple);
+	CatalogUpdateIndexes(inhRelation, tuple, false, NULL);
 
 	heap_freetuple(tuple);
 
@@ -2397,11 +2397,16 @@ SetRelationHasSubclass(Oid relationId, bool relhassubclass)
 
 	if (classtuple->relhassubclass != relhassubclass)
 	{
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
+
 		classtuple->relhassubclass = relhassubclass;
-		simple_heap_update(relationRelation, &tuple->t_self, tuple);
+		simple_heap_update(relationRelation, &tuple->t_self, tuple,
+				&warm_update, &modified_attrs);
 
 		/* keep the catalog indexes up to date */
-		CatalogUpdateIndexes(relationRelation, tuple);
+		CatalogUpdateIndexes(relationRelation, tuple, warm_update,
+				modified_attrs);
 	}
 	else
 	{
@@ -2477,6 +2482,8 @@ renameatt_internal(Oid myrelid,
 	HeapTuple	atttup;
 	Form_pg_attribute attform;
 	AttrNumber	attnum;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/*
 	 * Grab an exclusive lock on the target table, which we will NOT release
@@ -2592,10 +2599,11 @@ renameatt_internal(Oid myrelid,
 	/* apply the update */
 	namestrcpy(&(attform->attname), newattname);
 
-	simple_heap_update(attrelation, &atttup->t_self, atttup);
+	simple_heap_update(attrelation, &atttup->t_self, atttup, &warm_update,
+			&modified_attrs);
 
 	/* keep system catalog indexes current */
-	CatalogUpdateIndexes(attrelation, atttup);
+	CatalogUpdateIndexes(attrelation, atttup, warm_update, modified_attrs);
 
 	InvokeObjectPostAlterHook(RelationRelationId, myrelid, attnum);
 
@@ -2871,6 +2879,8 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal)
 	HeapTuple	reltup;
 	Form_pg_class relform;
 	Oid			namespaceId;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/*
 	 * Grab an exclusive lock on the target table, index, sequence, view,
@@ -2902,10 +2912,11 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal)
 	 */
 	namestrcpy(&(relform->relname), newrelname);
 
-	simple_heap_update(relrelation, &reltup->t_self, reltup);
+	simple_heap_update(relrelation, &reltup->t_self, reltup, &warm_update,
+			&modified_attrs);
 
 	/* keep the system catalog indexes current */
-	CatalogUpdateIndexes(relrelation, reltup);
+	CatalogUpdateIndexes(relrelation, reltup, warm_update, modified_attrs);
 
 	InvokeObjectPostAlterHookArg(RelationRelationId, myrelid, 0,
 								 InvalidOid, is_internal);
@@ -5039,6 +5050,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	ListCell   *child;
 	AclResult	aclresult;
 	ObjectAddress address;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -5069,6 +5082,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			Oid			ctypeId;
 			int32		ctypmod;
 			Oid			ccollid;
+			bool		warm_update;
+			Bitmapset	*modified_attrs;
 
 			/* Child column must match on type, typmod, and collation */
 			typenameTypeIdAndMod(NULL, colDef->typeName, &ctypeId, &ctypmod);
@@ -5097,8 +5112,9 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
 			/* Bump the existing child att's inhcount */
 			childatt->attinhcount++;
-			simple_heap_update(attrdesc, &tuple->t_self, tuple);
-			CatalogUpdateIndexes(attrdesc, tuple);
+			simple_heap_update(attrdesc, &tuple->t_self, tuple, &warm_update,
+					&modified_attrs);
+			CatalogUpdateIndexes(attrdesc, tuple, warm_update, modified_attrs);
 
 			heap_freetuple(tuple);
 
@@ -5191,10 +5207,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	else
 		((Form_pg_class) GETSTRUCT(reltup))->relnatts = newattnum;
 
-	simple_heap_update(pgclass, &reltup->t_self, reltup);
+	simple_heap_update(pgclass, &reltup->t_self, reltup, &warm_update,
+			&modified_attrs);
 
 	/* keep catalog indexes current */
-	CatalogUpdateIndexes(pgclass, reltup);
+	CatalogUpdateIndexes(pgclass, reltup, warm_update, modified_attrs);
 
 	heap_freetuple(reltup);
 
@@ -5628,12 +5645,16 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 	 */
 	if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
 	{
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
+
 		((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = FALSE;
 
-		simple_heap_update(attr_rel, &tuple->t_self, tuple);
+		simple_heap_update(attr_rel, &tuple->t_self, tuple, &warm_update,
+				&modified_attrs);
 
 		/* keep the system catalog indexes current */
-		CatalogUpdateIndexes(attr_rel, tuple);
+		CatalogUpdateIndexes(attr_rel, tuple, warm_update, modified_attrs);
 
 		ObjectAddressSubSet(address, RelationRelationId,
 							RelationGetRelid(rel), attnum);
@@ -5706,12 +5727,16 @@ ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
 	 */
 	if (!((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
 	{
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
+
 		((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = TRUE;
 
-		simple_heap_update(attr_rel, &tuple->t_self, tuple);
+		simple_heap_update(attr_rel, &tuple->t_self, tuple, &warm_update,
+				&modified_attrs);
 
 		/* keep the system catalog indexes current */
-		CatalogUpdateIndexes(attr_rel, tuple);
+		CatalogUpdateIndexes(attr_rel, tuple, warm_update, modified_attrs);
 
 		/* Tell Phase 3 it needs to test the constraint */
 		tab->new_notnull = true;
@@ -5833,6 +5858,8 @@ ATExecSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 	Form_pg_attribute attrtuple;
 	AttrNumber	attnum;
 	ObjectAddress address;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	Assert(IsA(newValue, Integer));
 	newtarget = intVal(newValue);
@@ -5876,10 +5903,11 @@ ATExecSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 
 	attrtuple->attstattarget = newtarget;
 
-	simple_heap_update(attrelation, &tuple->t_self, tuple);
+	simple_heap_update(attrelation, &tuple->t_self, tuple, &warm_update,
+			&modified_attrs);
 
 	/* keep system catalog indexes current */
-	CatalogUpdateIndexes(attrelation, tuple);
+	CatalogUpdateIndexes(attrelation, tuple, warm_update, modified_attrs);
 
 	InvokeObjectPostAlterHook(RelationRelationId,
 							  RelationGetRelid(rel),
@@ -5912,6 +5940,8 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 	Datum		repl_val[Natts_pg_attribute];
 	bool		repl_null[Natts_pg_attribute];
 	bool		repl_repl[Natts_pg_attribute];
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	attrelation = heap_open(AttributeRelationId, RowExclusiveLock);
 
@@ -5953,8 +5983,9 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 								 repl_val, repl_null, repl_repl);
 
 	/* Update system catalog. */
-	simple_heap_update(attrelation, &newtuple->t_self, newtuple);
-	CatalogUpdateIndexes(attrelation, newtuple);
+	simple_heap_update(attrelation, &newtuple->t_self, newtuple, &warm_update,
+			&modified_attrs);
+	CatalogUpdateIndexes(attrelation, newtuple, warm_update, modified_attrs);
 
 	InvokeObjectPostAlterHook(RelationRelationId,
 							  RelationGetRelid(rel),
@@ -5986,6 +6017,8 @@ ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE loc
 	Form_pg_attribute attrtuple;
 	AttrNumber	attnum;
 	ObjectAddress address;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	Assert(IsA(newValue, String));
 	storagemode = strVal(newValue);
@@ -6037,10 +6070,11 @@ ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE loc
 				 errmsg("column data type %s can only have storage PLAIN",
 						format_type_be(attrtuple->atttypid))));
 
-	simple_heap_update(attrelation, &tuple->t_self, tuple);
+	simple_heap_update(attrelation, &tuple->t_self, tuple, &warm_update,
+			&modified_attrs);
 
 	/* keep system catalog indexes current */
-	CatalogUpdateIndexes(attrelation, tuple);
+	CatalogUpdateIndexes(attrelation, tuple, warm_update, modified_attrs);
 
 	InvokeObjectPostAlterHook(RelationRelationId,
 							  RelationGetRelid(rel),
@@ -6275,13 +6309,18 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				}
 				else
 				{
+					bool		warm_update;
+					Bitmapset	*modified_attrs;
+
 					/* Child column must survive my deletion */
 					childatt->attinhcount--;
 
-					simple_heap_update(attr_rel, &tuple->t_self, tuple);
+					simple_heap_update(attr_rel, &tuple->t_self, tuple,
+							&warm_update, &modified_attrs);
 
 					/* keep the system catalog indexes current */
-					CatalogUpdateIndexes(attr_rel, tuple);
+					CatalogUpdateIndexes(attr_rel, tuple, warm_update,
+							modified_attrs);
 
 					/* Make update visible */
 					CommandCounterIncrement();
@@ -6289,6 +6328,9 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 			}
 			else
 			{
+				bool		warm_update;
+				Bitmapset	*modified_attrs;
+
 				/*
 				 * If we were told to drop ONLY in this table (no recursion),
 				 * we need to mark the inheritors' attributes as locally
@@ -6297,10 +6339,12 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				childatt->attinhcount--;
 				childatt->attislocal = true;
 
-				simple_heap_update(attr_rel, &tuple->t_self, tuple);
+				simple_heap_update(attr_rel, &tuple->t_self, tuple,
+						&warm_update, &modified_attrs);
 
 				/* keep the system catalog indexes current */
-				CatalogUpdateIndexes(attr_rel, tuple);
+				CatalogUpdateIndexes(attr_rel, tuple, warm_update,
+						modified_attrs);
 
 				/* Make update visible */
 				CommandCounterIncrement();
@@ -6333,6 +6377,8 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 		Relation	class_rel;
 		Form_pg_class tuple_class;
 		AlteredTableInfo *tab;
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
 
 		class_rel = heap_open(RelationRelationId, RowExclusiveLock);
 
@@ -6344,10 +6390,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 		tuple_class = (Form_pg_class) GETSTRUCT(tuple);
 
 		tuple_class->relhasoids = false;
-		simple_heap_update(class_rel, &tuple->t_self, tuple);
+		simple_heap_update(class_rel, &tuple->t_self, tuple, &warm_update,
+				&modified_attrs);
 
 		/* Keep the catalog indexes up to date */
-		CatalogUpdateIndexes(class_rel, tuple);
+		CatalogUpdateIndexes(class_rel, tuple, warm_update, modified_attrs);
 
 		heap_close(class_rel, RowExclusiveLock);
 
@@ -7189,6 +7236,8 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
 		SysScanDesc tgscan;
 		Relation	tgrel;
 		ListCell   *lc;
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
 
 		/*
 		 * Now update the catalog, while we have the door open.
@@ -7197,8 +7246,9 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
 		copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
 		copy_con->condeferrable = cmdcon->deferrable;
 		copy_con->condeferred = cmdcon->initdeferred;
-		simple_heap_update(conrel, &copyTuple->t_self, copyTuple);
-		CatalogUpdateIndexes(conrel, copyTuple);
+		simple_heap_update(conrel, &copyTuple->t_self, copyTuple, &warm_update,
+				&modified_attrs);
+		CatalogUpdateIndexes(conrel, copyTuple, warm_update, modified_attrs);
 
 		InvokeObjectPostAlterHook(ConstraintRelationId,
 								  HeapTupleGetOid(contuple), 0);
@@ -7223,6 +7273,8 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
 		{
 			Form_pg_trigger tgform = (Form_pg_trigger) GETSTRUCT(tgtuple);
 			Form_pg_trigger copy_tg;
+			bool		warm_update;
+			Bitmapset	*modified_attrs;
 
 			/*
 			 * Remember OIDs of other relation(s) involved in FK constraint.
@@ -7251,8 +7303,9 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
 
 			copy_tg->tgdeferrable = cmdcon->deferrable;
 			copy_tg->tginitdeferred = cmdcon->initdeferred;
-			simple_heap_update(tgrel, &copyTuple->t_self, copyTuple);
-			CatalogUpdateIndexes(tgrel, copyTuple);
+			simple_heap_update(tgrel, &copyTuple->t_self, copyTuple,
+					&warm_update, &modified_attrs);
+			CatalogUpdateIndexes(tgrel, copyTuple, warm_update, modified_attrs);
 
 			InvokeObjectPostAlterHook(TriggerRelationId,
 									  HeapTupleGetOid(tgtuple), 0);
@@ -7351,6 +7404,8 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 	{
 		HeapTuple	copyTuple;
 		Form_pg_constraint copy_con;
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
 
 		if (con->contype == CONSTRAINT_FOREIGN)
 		{
@@ -7438,8 +7493,9 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 		copyTuple = heap_copytuple(tuple);
 		copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
 		copy_con->convalidated = true;
-		simple_heap_update(conrel, &copyTuple->t_self, copyTuple);
-		CatalogUpdateIndexes(conrel, copyTuple);
+		simple_heap_update(conrel, &copyTuple->t_self, copyTuple, &warm_update,
+				&modified_attrs);
+		CatalogUpdateIndexes(conrel, copyTuple, warm_update, modified_attrs);
 
 		InvokeObjectPostAlterHook(ConstraintRelationId,
 								  HeapTupleGetOid(tuple), 0);
@@ -8339,10 +8395,14 @@ ATExecDropConstraint(Relation rel, const char *constrName,
 			}
 			else
 			{
+				bool		warm_update;
+				Bitmapset	*modified_attrs;
+
 				/* Child constraint must survive my deletion */
 				con->coninhcount--;
-				simple_heap_update(conrel, &copy_tuple->t_self, copy_tuple);
-				CatalogUpdateIndexes(conrel, copy_tuple);
+				simple_heap_update(conrel, &copy_tuple->t_self, copy_tuple,
+						&warm_update, &modified_attrs);
+				CatalogUpdateIndexes(conrel, copy_tuple, warm_update, modified_attrs);
 
 				/* Make update visible */
 				CommandCounterIncrement();
@@ -8350,6 +8410,9 @@ ATExecDropConstraint(Relation rel, const char *constrName,
 		}
 		else
 		{
+			bool		warm_update;
+			Bitmapset	*modified_attrs;
+
 			/*
 			 * If we were told to drop ONLY in this table (no recursion), we
 			 * need to mark the inheritors' constraints as locally defined
@@ -8358,8 +8421,10 @@ ATExecDropConstraint(Relation rel, const char *constrName,
 			con->coninhcount--;
 			con->conislocal = true;
 
-			simple_heap_update(conrel, &copy_tuple->t_self, copy_tuple);
-			CatalogUpdateIndexes(conrel, copy_tuple);
+			simple_heap_update(conrel, &copy_tuple->t_self, copy_tuple,
+					&warm_update, &modified_attrs);
+			CatalogUpdateIndexes(conrel, copy_tuple, warm_update,
+					modified_attrs);
 
 			/* Make update visible */
 			CommandCounterIncrement();
@@ -8675,6 +8740,8 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 	SysScanDesc scan;
 	HeapTuple	depTup;
 	ObjectAddress address;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	attrelation = heap_open(AttributeRelationId, RowExclusiveLock);
 
@@ -9005,10 +9072,11 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 
 	ReleaseSysCache(typeTuple);
 
-	simple_heap_update(attrelation, &heapTup->t_self, heapTup);
+	simple_heap_update(attrelation, &heapTup->t_self, heapTup, &warm_update,
+			&modified_attrs);
 
 	/* keep system catalog indexes current */
-	CatalogUpdateIndexes(attrelation, heapTup);
+	CatalogUpdateIndexes(attrelation, heapTup, warm_update, modified_attrs);
 
 	heap_close(attrelation, RowExclusiveLock);
 
@@ -9079,6 +9147,8 @@ ATExecAlterColumnGenericOptions(Relation rel,
 	Form_pg_attribute atttableform;
 	AttrNumber	attnum;
 	ObjectAddress address;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	if (options == NIL)
 		return InvalidObjectAddress;
@@ -9146,8 +9216,9 @@ ATExecAlterColumnGenericOptions(Relation rel,
 	newtuple = heap_modify_tuple(tuple, RelationGetDescr(attrel),
 								 repl_val, repl_null, repl_repl);
 
-	simple_heap_update(attrel, &newtuple->t_self, newtuple);
-	CatalogUpdateIndexes(attrel, newtuple);
+	simple_heap_update(attrel, &newtuple->t_self, newtuple, &warm_update,
+			&modified_attrs);
+	CatalogUpdateIndexes(attrel, newtuple, warm_update, modified_attrs);
 
 	InvokeObjectPostAlterHook(RelationRelationId,
 							  RelationGetRelid(rel),
@@ -9617,6 +9688,8 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 		Datum		aclDatum;
 		bool		isNull;
 		HeapTuple	newtuple;
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
 
 		/* skip permission checks when recursing to index or toast table */
 		if (!recursing)
@@ -9667,8 +9740,9 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 
 		newtuple = heap_modify_tuple(tuple, RelationGetDescr(class_rel), repl_val, repl_null, repl_repl);
 
-		simple_heap_update(class_rel, &newtuple->t_self, newtuple);
-		CatalogUpdateIndexes(class_rel, newtuple);
+		simple_heap_update(class_rel, &newtuple->t_self, newtuple,
+				&warm_update, &modified_attrs);
+		CatalogUpdateIndexes(class_rel, newtuple, warm_update, modified_attrs);
 
 		heap_freetuple(newtuple);
 
@@ -9770,6 +9844,8 @@ change_owner_fix_column_acls(Oid relationOid, Oid oldOwnerId, Oid newOwnerId)
 		Datum		aclDatum;
 		bool		isNull;
 		HeapTuple	newtuple;
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
 
 		/* Ignore dropped columns */
 		if (att->attisdropped)
@@ -9795,8 +9871,10 @@ change_owner_fix_column_acls(Oid relationOid, Oid oldOwnerId, Oid newOwnerId)
 									 RelationGetDescr(attRelation),
 									 repl_val, repl_null, repl_repl);
 
-		simple_heap_update(attRelation, &newtuple->t_self, newtuple);
-		CatalogUpdateIndexes(attRelation, newtuple);
+		simple_heap_update(attRelation, &newtuple->t_self, newtuple,
+				&warm_update, &modified_attrs);
+		CatalogUpdateIndexes(attRelation, newtuple, warm_update,
+				modified_attrs);
 
 		heap_freetuple(newtuple);
 	}
@@ -9966,6 +10044,8 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	bool		repl_null[Natts_pg_class];
 	bool		repl_repl[Natts_pg_class];
 	static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	if (defList == NIL && operation != AT_ReplaceRelOptions)
 		return;					/* nothing to do */
@@ -10073,9 +10153,10 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	newtuple = heap_modify_tuple(tuple, RelationGetDescr(pgclass),
 								 repl_val, repl_null, repl_repl);
 
-	simple_heap_update(pgclass, &newtuple->t_self, newtuple);
+	simple_heap_update(pgclass, &newtuple->t_self, newtuple, &warm_update,
+			&modified_attrs);
 
-	CatalogUpdateIndexes(pgclass, newtuple);
+	CatalogUpdateIndexes(pgclass, newtuple, warm_update, modified_attrs);
 
 	InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0);
 
@@ -10088,6 +10169,8 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	{
 		Relation	toastrel;
 		Oid			toastid = rel->rd_rel->reltoastrelid;
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
 
 		toastrel = heap_open(toastid, lockmode);
 
@@ -10132,9 +10215,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 		newtuple = heap_modify_tuple(tuple, RelationGetDescr(pgclass),
 									 repl_val, repl_null, repl_repl);
 
-		simple_heap_update(pgclass, &newtuple->t_self, newtuple);
+		simple_heap_update(pgclass, &newtuple->t_self, newtuple, &warm_update, &modified_attrs);
 
-		CatalogUpdateIndexes(pgclass, newtuple);
+		CatalogUpdateIndexes(pgclass, newtuple, warm_update, modified_attrs);
 
 		InvokeObjectPostAlterHookArg(RelationRelationId,
 									 RelationGetRelid(toastrel), 0,
@@ -10169,6 +10252,8 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	ForkNumber	forkNum;
 	List	   *reltoastidxids = NIL;
 	ListCell   *lc;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/*
 	 * Need lock here in case we are recursing to toast table or index
@@ -10295,8 +10380,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 	/* update the pg_class row */
 	rd_rel->reltablespace = (newTableSpace == MyDatabaseTableSpace) ? InvalidOid : newTableSpace;
 	rd_rel->relfilenode = newrelfilenode;
-	simple_heap_update(pg_class, &tuple->t_self, tuple);
-	CatalogUpdateIndexes(pg_class, tuple);
+	simple_heap_update(pg_class, &tuple->t_self, tuple, &warm_update,
+			&modified_attrs);
+	CatalogUpdateIndexes(pg_class, tuple, warm_update, modified_attrs);
 
 	InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0);
 
@@ -10901,6 +10987,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 										  attributeName);
 		if (HeapTupleIsValid(tuple))
 		{
+			bool		warm_update;
+			Bitmapset	*modified_attrs;
+
 			/* Check they are same type, typmod, and collation */
 			Form_pg_attribute childatt = (Form_pg_attribute) GETSTRUCT(tuple);
 
@@ -10946,8 +11035,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 				childatt->attislocal = false;
 			}
 
-			simple_heap_update(attrrel, &tuple->t_self, tuple);
-			CatalogUpdateIndexes(attrrel, tuple);
+			simple_heap_update(attrrel, &tuple->t_self, tuple, &warm_update,
+					&modified_attrs);
+			CatalogUpdateIndexes(attrrel, tuple, warm_update, modified_attrs);
 			heap_freetuple(tuple);
 		}
 		else
@@ -10976,6 +11066,8 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 		if (HeapTupleIsValid(tuple))
 		{
 			Form_pg_attribute childatt = (Form_pg_attribute) GETSTRUCT(tuple);
+			bool		warm_update;
+			Bitmapset	*modified_attrs;
 
 			/* See comments above; these changes should be the same */
 			childatt->attinhcount++;
@@ -10986,8 +11078,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 				childatt->attislocal = false;
 			}
 
-			simple_heap_update(attrrel, &tuple->t_self, tuple);
-			CatalogUpdateIndexes(attrrel, tuple);
+			simple_heap_update(attrrel, &tuple->t_self, tuple, &warm_update,
+					&modified_attrs);
+			CatalogUpdateIndexes(attrrel, tuple, warm_update, modified_attrs);
 			heap_freetuple(tuple);
 		}
 		else
@@ -11071,6 +11164,8 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 		{
 			Form_pg_constraint child_con = (Form_pg_constraint) GETSTRUCT(child_tuple);
 			HeapTuple	child_copy;
+			bool		warm_update;
+			Bitmapset	*modified_attrs;
 
 			if (child_con->contype != CONSTRAINT_CHECK)
 				continue;
@@ -11124,8 +11219,10 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 				child_con->conislocal = false;
 			}
 
-			simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
-			CatalogUpdateIndexes(catalog_relation, child_copy);
+			simple_heap_update(catalog_relation, &child_copy->t_self,
+					child_copy, &warm_update, &modified_attrs);
+			CatalogUpdateIndexes(catalog_relation, child_copy, warm_update,
+					modified_attrs);
 			heap_freetuple(child_copy);
 
 			found = true;
@@ -11290,13 +11387,17 @@ RemoveInheritance(Relation child_rel, Relation parent_rel)
 			/* Decrement inhcount and possibly set islocal to true */
 			HeapTuple	copyTuple = heap_copytuple(attributeTuple);
 			Form_pg_attribute copy_att = (Form_pg_attribute) GETSTRUCT(copyTuple);
+			bool		warm_update;
+			Bitmapset	*modified_attrs;
 
 			copy_att->attinhcount--;
 			if (copy_att->attinhcount == 0)
 				copy_att->attislocal = true;
 
-			simple_heap_update(catalogRelation, &copyTuple->t_self, copyTuple);
-			CatalogUpdateIndexes(catalogRelation, copyTuple);
+			simple_heap_update(catalogRelation, &copyTuple->t_self, copyTuple,
+					&warm_update, &modified_attrs);
+			CatalogUpdateIndexes(catalogRelation, copyTuple, warm_update,
+					modified_attrs);
 			heap_freetuple(copyTuple);
 		}
 	}
@@ -11361,6 +11462,8 @@ RemoveInheritance(Relation child_rel, Relation parent_rel)
 			/* Decrement inhcount and possibly set islocal to true */
 			HeapTuple	copyTuple = heap_copytuple(constraintTuple);
 			Form_pg_constraint copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
+			bool		warm_update;
+			Bitmapset	*modified_attrs;
 
 			if (copy_con->coninhcount <= 0)		/* shouldn't happen */
 				elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
@@ -11370,8 +11473,10 @@ RemoveInheritance(Relation child_rel, Relation parent_rel)
 			if (copy_con->coninhcount == 0)
 				copy_con->conislocal = true;
 
-			simple_heap_update(catalogRelation, &copyTuple->t_self, copyTuple);
-			CatalogUpdateIndexes(catalogRelation, copyTuple);
+			simple_heap_update(catalogRelation, &copyTuple->t_self, copyTuple,
+					&warm_update, &modified_attrs);
+			CatalogUpdateIndexes(catalogRelation, copyTuple, warm_update,
+					modified_attrs);
 			heap_freetuple(copyTuple);
 		}
 	}
@@ -11468,6 +11573,8 @@ ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
 	ObjectAddress tableobj,
 				typeobj;
 	HeapTuple	classtuple;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/* Validate the type. */
 	typetuple = typenameType(NULL, ofTypename, NULL);
@@ -11571,8 +11678,10 @@ ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
 	if (!HeapTupleIsValid(classtuple))
 		elog(ERROR, "cache lookup failed for relation %u", relid);
 	((Form_pg_class) GETSTRUCT(classtuple))->reloftype = typeid;
-	simple_heap_update(relationRelation, &classtuple->t_self, classtuple);
-	CatalogUpdateIndexes(relationRelation, classtuple);
+	simple_heap_update(relationRelation, &classtuple->t_self, classtuple,
+			&warm_update, &modified_attrs);
+	CatalogUpdateIndexes(relationRelation, classtuple, warm_update,
+			modified_attrs);
 
 	InvokeObjectPostAlterHook(RelationRelationId, relid, 0);
 
@@ -11596,6 +11705,8 @@ ATExecDropOf(Relation rel, LOCKMODE lockmode)
 	Oid			relid = RelationGetRelid(rel);
 	Relation	relationRelation;
 	HeapTuple	tuple;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	if (!OidIsValid(rel->rd_rel->reloftype))
 		ereport(ERROR,
@@ -11616,8 +11727,9 @@ ATExecDropOf(Relation rel, LOCKMODE lockmode)
 	if (!HeapTupleIsValid(tuple))
 		elog(ERROR, "cache lookup failed for relation %u", relid);
 	((Form_pg_class) GETSTRUCT(tuple))->reloftype = InvalidOid;
-	simple_heap_update(relationRelation, &tuple->t_self, tuple);
-	CatalogUpdateIndexes(relationRelation, tuple);
+	simple_heap_update(relationRelation, &tuple->t_self, tuple, &warm_update,
+			&modified_attrs);
+	CatalogUpdateIndexes(relationRelation, tuple, warm_update, modified_attrs);
 
 	InvokeObjectPostAlterHook(RelationRelationId, relid, 0);
 
@@ -11656,9 +11768,14 @@ relation_mark_replica_identity(Relation rel, char ri_type, Oid indexOid,
 	pg_class_form = (Form_pg_class) GETSTRUCT(pg_class_tuple);
 	if (pg_class_form->relreplident != ri_type)
 	{
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
+
 		pg_class_form->relreplident = ri_type;
-		simple_heap_update(pg_class, &pg_class_tuple->t_self, pg_class_tuple);
-		CatalogUpdateIndexes(pg_class, pg_class_tuple);
+		simple_heap_update(pg_class, &pg_class_tuple->t_self, pg_class_tuple,
+				&warm_update, &modified_attrs);
+		CatalogUpdateIndexes(pg_class, pg_class_tuple, warm_update,
+				modified_attrs);
 	}
 	heap_close(pg_class, RowExclusiveLock);
 	heap_freetuple(pg_class_tuple);
@@ -11717,8 +11834,13 @@ relation_mark_replica_identity(Relation rel, char ri_type, Oid indexOid,
 
 		if (dirty)
 		{
-			simple_heap_update(pg_index, &pg_index_tuple->t_self, pg_index_tuple);
-			CatalogUpdateIndexes(pg_index, pg_index_tuple);
+			bool		warm_update;
+			Bitmapset	*modified_attrs;
+
+			simple_heap_update(pg_index, &pg_index_tuple->t_self,
+					pg_index_tuple, &warm_update, &modified_attrs);
+			CatalogUpdateIndexes(pg_index, pg_index_tuple, warm_update,
+					modified_attrs);
 			InvokeObjectPostAlterHookArg(IndexRelationId, thisIndexOid, 0,
 										 InvalidOid, is_internal);
 		}
@@ -11856,6 +11978,8 @@ ATExecEnableRowSecurity(Relation rel)
 	Relation	pg_class;
 	Oid			relid;
 	HeapTuple	tuple;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	relid = RelationGetRelid(rel);
 
@@ -11867,10 +11991,11 @@ ATExecEnableRowSecurity(Relation rel)
 		elog(ERROR, "cache lookup failed for relation %u", relid);
 
 	((Form_pg_class) GETSTRUCT(tuple))->relrowsecurity = true;
-	simple_heap_update(pg_class, &tuple->t_self, tuple);
+	simple_heap_update(pg_class, &tuple->t_self, tuple, &warm_update,
+			&modified_attrs);
 
 	/* keep catalog indexes current */
-	CatalogUpdateIndexes(pg_class, tuple);
+	CatalogUpdateIndexes(pg_class, tuple, warm_update, modified_attrs);
 
 	heap_close(pg_class, RowExclusiveLock);
 	heap_freetuple(tuple);
@@ -11882,6 +12007,8 @@ ATExecDisableRowSecurity(Relation rel)
 	Relation	pg_class;
 	Oid			relid;
 	HeapTuple	tuple;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	relid = RelationGetRelid(rel);
 
@@ -11894,10 +12021,11 @@ ATExecDisableRowSecurity(Relation rel)
 		elog(ERROR, "cache lookup failed for relation %u", relid);
 
 	((Form_pg_class) GETSTRUCT(tuple))->relrowsecurity = false;
-	simple_heap_update(pg_class, &tuple->t_self, tuple);
+	simple_heap_update(pg_class, &tuple->t_self, tuple, &warm_update,
+			&modified_attrs);
 
 	/* keep catalog indexes current */
-	CatalogUpdateIndexes(pg_class, tuple);
+	CatalogUpdateIndexes(pg_class, tuple, warm_update, modified_attrs);
 
 	heap_close(pg_class, RowExclusiveLock);
 	heap_freetuple(tuple);
@@ -11912,6 +12040,8 @@ ATExecForceNoForceRowSecurity(Relation rel, bool force_rls)
 	Relation	pg_class;
 	Oid			relid;
 	HeapTuple	tuple;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	relid = RelationGetRelid(rel);
 
@@ -11923,10 +12053,11 @@ ATExecForceNoForceRowSecurity(Relation rel, bool force_rls)
 		elog(ERROR, "cache lookup failed for relation %u", relid);
 
 	((Form_pg_class) GETSTRUCT(tuple))->relforcerowsecurity = force_rls;
-	simple_heap_update(pg_class, &tuple->t_self, tuple);
+	simple_heap_update(pg_class, &tuple->t_self, tuple, &warm_update,
+			&modified_attrs);
 
 	/* keep catalog indexes current */
-	CatalogUpdateIndexes(pg_class, tuple);
+	CatalogUpdateIndexes(pg_class, tuple, warm_update, modified_attrs);
 
 	heap_close(pg_class, RowExclusiveLock);
 	heap_freetuple(tuple);
@@ -11948,6 +12079,8 @@ ATExecGenericOptions(Relation rel, List *options)
 	bool		repl_repl[Natts_pg_foreign_table];
 	Datum		datum;
 	Form_pg_foreign_table tableform;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	if (options == NIL)
 		return;
@@ -11994,8 +12127,8 @@ ATExecGenericOptions(Relation rel, List *options)
 	tuple = heap_modify_tuple(tuple, RelationGetDescr(ftrel),
 							  repl_val, repl_null, repl_repl);
 
-	simple_heap_update(ftrel, &tuple->t_self, tuple);
-	CatalogUpdateIndexes(ftrel, tuple);
+	simple_heap_update(ftrel, &tuple->t_self, tuple, &warm_update, &modified_attrs);
+	CatalogUpdateIndexes(ftrel, tuple, warm_update, modified_attrs);
 
 	/*
 	 * Invalidate relcache so that all sessions will refresh any cached plans
@@ -12278,6 +12411,9 @@ AlterRelationNamespaceInternal(Relation classRel, Oid relOid,
 	already_done = object_address_present(&thisobj, objsMoved);
 	if (!already_done && oldNspOid != newNspOid)
 	{
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
+
 		/* check for duplicate name (more friendly than unique-index failure) */
 		if (get_relname_relid(NameStr(classForm->relname),
 							  newNspOid) != InvalidOid)
@@ -12290,8 +12426,9 @@ AlterRelationNamespaceInternal(Relation classRel, Oid relOid,
 		/* classTup is a copy, so OK to scribble on */
 		classForm->relnamespace = newNspOid;
 
-		simple_heap_update(classRel, &classTup->t_self, classTup);
-		CatalogUpdateIndexes(classRel, classTup);
+		simple_heap_update(classRel, &classTup->t_self, classTup, &warm_update,
+				&modified_attrs);
+		CatalogUpdateIndexes(classRel, classTup, warm_update, modified_attrs);
 
 		/* Update dependency on schema if caller said so */
 		if (hasDependEntry &&
@@ -13499,6 +13636,8 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 				new_null[Natts_pg_class],
 				new_repl[Natts_pg_class];
 	ObjectAddress address;
+	bool			warm_update;
+	Bitmapset		*modified_attrs;
 
 	partRel = heap_openrv(name, AccessShareLock);
 
@@ -13526,8 +13665,9 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 								 new_val, new_null, new_repl);
 
 	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
-	simple_heap_update(classRel, &newtuple->t_self, newtuple);
-	CatalogUpdateIndexes(classRel, newtuple);
+	simple_heap_update(classRel, &newtuple->t_self, newtuple, &warm_update,
+			&modified_attrs);
+	CatalogUpdateIndexes(classRel, newtuple, warm_update, modified_attrs);
 	heap_freetuple(newtuple);
 	heap_close(classRel, RowExclusiveLock);
 
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 651e1b3..bc18cfb 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -346,7 +346,7 @@ CreateTableSpace(CreateTableSpaceStmt *stmt)
 
 	tablespaceoid = simple_heap_insert(rel, tuple);
 
-	CatalogUpdateIndexes(rel, tuple);
+	CatalogUpdateIndexes(rel, tuple, false, NULL);
 
 	heap_freetuple(tuple);
 
@@ -920,6 +920,8 @@ RenameTableSpace(const char *oldname, const char *newname)
 	HeapTuple	newtuple;
 	Form_pg_tablespace newform;
 	ObjectAddress address;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/* Search pg_tablespace */
 	rel = heap_open(TableSpaceRelationId, RowExclusiveLock);
@@ -971,8 +973,9 @@ RenameTableSpace(const char *oldname, const char *newname)
 	/* OK, update the entry */
 	namestrcpy(&(newform->spcname), newname);
 
-	simple_heap_update(rel, &newtuple->t_self, newtuple);
-	CatalogUpdateIndexes(rel, newtuple);
+	simple_heap_update(rel, &newtuple->t_self, newtuple, &warm_update,
+			&modified_attrs);
+	CatalogUpdateIndexes(rel, newtuple, warm_update, modified_attrs);
 
 	InvokeObjectPostAlterHook(TableSpaceRelationId, tspId, 0);
 
@@ -1001,6 +1004,8 @@ AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt)
 	bool		repl_null[Natts_pg_tablespace];
 	bool		repl_repl[Natts_pg_tablespace];
 	HeapTuple	newtuple;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/* Search pg_tablespace */
 	rel = heap_open(TableSpaceRelationId, RowExclusiveLock);
@@ -1044,8 +1049,8 @@ AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt)
 								 repl_null, repl_repl);
 
 	/* Update system catalog. */
-	simple_heap_update(rel, &newtuple->t_self, newtuple);
-	CatalogUpdateIndexes(rel, newtuple);
+	simple_heap_update(rel, &newtuple->t_self, newtuple, &warm_update, &modified_attrs);
+	CatalogUpdateIndexes(rel, newtuple, warm_update, modified_attrs);
 
 	InvokeObjectPostAlterHook(TableSpaceRelationId, HeapTupleGetOid(tup), 0);
 
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 3fc3a21..1ed9e4b 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -166,6 +166,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 				referenced;
 	char	   *oldtablename = NULL;
 	char	   *newtablename = NULL;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	if (OidIsValid(relOid))
 		rel = heap_open(relOid, ShareRowExclusiveLock);
@@ -777,7 +779,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 */
 	simple_heap_insert(tgrel, tuple);
 
-	CatalogUpdateIndexes(tgrel, tuple);
+	CatalogUpdateIndexes(tgrel, tuple, false, NULL);
 
 	heap_freetuple(tuple);
 	heap_close(tgrel, RowExclusiveLock);
@@ -804,9 +806,10 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 
 	((Form_pg_class) GETSTRUCT(tuple))->relhastriggers = true;
 
-	simple_heap_update(pgrel, &tuple->t_self, tuple);
+	simple_heap_update(pgrel, &tuple->t_self, tuple, &warm_update,
+			&modified_attrs);
 
-	CatalogUpdateIndexes(pgrel, tuple);
+	CatalogUpdateIndexes(pgrel, tuple, warm_update, modified_attrs);
 
 	heap_freetuple(tuple);
 	heap_close(pgrel, RowExclusiveLock);
@@ -1436,6 +1439,9 @@ renametrig(RenameStmt *stmt)
 								NULL, 2, key);
 	if (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
 	{
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
+
 		tgoid = HeapTupleGetOid(tuple);
 
 		/*
@@ -1446,10 +1452,11 @@ renametrig(RenameStmt *stmt)
 		namestrcpy(&((Form_pg_trigger) GETSTRUCT(tuple))->tgname,
 				   stmt->newname);
 
-		simple_heap_update(tgrel, &tuple->t_self, tuple);
+		simple_heap_update(tgrel, &tuple->t_self, tuple, &warm_update,
+				&modified_attrs);
 
 		/* keep system catalog indexes current */
-		CatalogUpdateIndexes(tgrel, tuple);
+		CatalogUpdateIndexes(tgrel, tuple, warm_update, modified_attrs);
 
 		InvokeObjectPostAlterHook(TriggerRelationId,
 								  HeapTupleGetOid(tuple), 0);
@@ -1559,13 +1566,16 @@ EnableDisableTrigger(Relation rel, const char *tgname,
 			/* need to change this one ... make a copy to scribble on */
 			HeapTuple	newtup = heap_copytuple(tuple);
 			Form_pg_trigger newtrig = (Form_pg_trigger) GETSTRUCT(newtup);
+			bool		warm_update;
+			Bitmapset	*modified_attrs;
 
 			newtrig->tgenabled = fires_when;
 
-			simple_heap_update(tgrel, &newtup->t_self, newtup);
+			simple_heap_update(tgrel, &newtup->t_self, newtup, &warm_update,
+					&modified_attrs);
 
 			/* Keep catalog indexes current */
-			CatalogUpdateIndexes(tgrel, newtup);
+			CatalogUpdateIndexes(tgrel, newtup, warm_update, modified_attrs);
 
 			heap_freetuple(newtup);
 
diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c
index 479a160..d8355f6 100644
--- a/src/backend/commands/tsearchcmds.c
+++ b/src/backend/commands/tsearchcmds.c
@@ -273,7 +273,7 @@ DefineTSParser(List *names, List *parameters)
 
 	prsOid = simple_heap_insert(prsRel, tup);
 
-	CatalogUpdateIndexes(prsRel, tup);
+	CatalogUpdateIndexes(prsRel, tup, false, NULL);
 
 	address = makeParserDependencies(tup);
 
@@ -484,7 +484,7 @@ DefineTSDictionary(List *names, List *parameters)
 
 	dictOid = simple_heap_insert(dictRel, tup);
 
-	CatalogUpdateIndexes(dictRel, tup);
+	CatalogUpdateIndexes(dictRel, tup, false, NULL);
 
 	address = makeDictionaryDependencies(tup);
 
@@ -540,6 +540,8 @@ AlterTSDictionary(AlterTSDictionaryStmt *stmt)
 	bool		repl_null[Natts_pg_ts_dict];
 	bool		repl_repl[Natts_pg_ts_dict];
 	ObjectAddress address;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	dictId = get_ts_dict_oid(stmt->dictname, false);
 
@@ -620,9 +622,10 @@ AlterTSDictionary(AlterTSDictionaryStmt *stmt)
 	newtup = heap_modify_tuple(tup, RelationGetDescr(rel),
 							   repl_val, repl_null, repl_repl);
 
-	simple_heap_update(rel, &newtup->t_self, newtup);
+	simple_heap_update(rel, &newtup->t_self, newtup, &warm_update,
+			&modified_attrs);
 
-	CatalogUpdateIndexes(rel, newtup);
+	CatalogUpdateIndexes(rel, newtup, warm_update, modified_attrs);
 
 	InvokeObjectPostAlterHook(TSDictionaryRelationId, dictId, 0);
 
@@ -808,7 +811,7 @@ DefineTSTemplate(List *names, List *parameters)
 
 	tmplOid = simple_heap_insert(tmplRel, tup);
 
-	CatalogUpdateIndexes(tmplRel, tup);
+	CatalogUpdateIndexes(tmplRel, tup, false, NULL);
 
 	address = makeTSTemplateDependencies(tup);
 
@@ -1068,7 +1071,7 @@ DefineTSConfiguration(List *names, List *parameters, ObjectAddress *copied)
 
 	cfgOid = simple_heap_insert(cfgRel, tup);
 
-	CatalogUpdateIndexes(cfgRel, tup);
+	CatalogUpdateIndexes(cfgRel, tup, false, NULL);
 
 	if (OidIsValid(sourceOid))
 	{
@@ -1108,7 +1111,7 @@ DefineTSConfiguration(List *names, List *parameters, ObjectAddress *copied)
 
 			simple_heap_insert(mapRel, newmaptup);
 
-			CatalogUpdateIndexes(mapRel, newmaptup);
+			CatalogUpdateIndexes(mapRel, newmaptup, false, NULL);
 
 			heap_freetuple(newmaptup);
 		}
@@ -1398,6 +1401,8 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
 				bool		repl_null[Natts_pg_ts_config_map];
 				bool		repl_repl[Natts_pg_ts_config_map];
 				HeapTuple	newtup;
+				bool		warm_update;
+				Bitmapset	*modified_attrs;
 
 				memset(repl_val, 0, sizeof(repl_val));
 				memset(repl_null, false, sizeof(repl_null));
@@ -1409,9 +1414,10 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
 				newtup = heap_modify_tuple(maptup,
 										   RelationGetDescr(relMap),
 										   repl_val, repl_null, repl_repl);
-				simple_heap_update(relMap, &newtup->t_self, newtup);
+				simple_heap_update(relMap, &newtup->t_self, newtup,
+						&warm_update, &modified_attrs);
 
-				CatalogUpdateIndexes(relMap, newtup);
+				CatalogUpdateIndexes(relMap, newtup, warm_update, modified_attrs);
 			}
 		}
 
@@ -1437,7 +1443,7 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
 
 				tup = heap_form_tuple(relMap->rd_att, values, nulls);
 				simple_heap_insert(relMap, tup);
-				CatalogUpdateIndexes(relMap, tup);
+				CatalogUpdateIndexes(relMap, tup, false, NULL);
 
 				heap_freetuple(tup);
 			}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 4c33d55..ccbe96f 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2138,6 +2138,8 @@ AlterDomainDefault(List *names, Node *defaultRaw)
 	HeapTuple	newtuple;
 	Form_pg_type typTup;
 	ObjectAddress address;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/* Make a TypeName so we can use standard type lookup machinery */
 	typename = makeTypeNameFromNameList(names);
@@ -2221,9 +2223,9 @@ AlterDomainDefault(List *names, Node *defaultRaw)
 								 new_record, new_record_nulls,
 								 new_record_repl);
 
-	simple_heap_update(rel, &tup->t_self, newtuple);
+	simple_heap_update(rel, &tup->t_self, newtuple, &warm_update, &modified_attrs);
 
-	CatalogUpdateIndexes(rel, newtuple);
+	CatalogUpdateIndexes(rel, newtuple, warm_update, modified_attrs);
 
 	/* Rebuild dependencies */
 	GenerateTypeDependencies(typTup->typnamespace,
@@ -2272,6 +2274,8 @@ AlterDomainNotNull(List *names, bool notNull)
 	HeapTuple	tup;
 	Form_pg_type typTup;
 	ObjectAddress address = InvalidObjectAddress;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/* Make a TypeName so we can use standard type lookup machinery */
 	typename = makeTypeNameFromNameList(names);
@@ -2360,9 +2364,9 @@ AlterDomainNotNull(List *names, bool notNull)
 	 */
 	typTup->typnotnull = notNull;
 
-	simple_heap_update(typrel, &tup->t_self, tup);
+	simple_heap_update(typrel, &tup->t_self, tup, &warm_update, &modified_attrs);
 
-	CatalogUpdateIndexes(typrel, tup);
+	CatalogUpdateIndexes(typrel, tup, warm_update, modified_attrs);
 
 	InvokeObjectPostAlterHook(TypeRelationId, domainoid, 0);
 
@@ -2598,6 +2602,8 @@ AlterDomainValidateConstraint(List *names, char *constrName)
 	HeapTuple	copyTuple;
 	ScanKeyData key;
 	ObjectAddress address;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/* Make a TypeName so we can use standard type lookup machinery */
 	typename = makeTypeNameFromNameList(names);
@@ -2662,8 +2668,8 @@ AlterDomainValidateConstraint(List *names, char *constrName)
 	copyTuple = heap_copytuple(tuple);
 	copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
 	copy_con->convalidated = true;
-	simple_heap_update(conrel, &copyTuple->t_self, copyTuple);
-	CatalogUpdateIndexes(conrel, copyTuple);
+	simple_heap_update(conrel, &copyTuple->t_self, copyTuple, &warm_update, &modified_attrs);
+	CatalogUpdateIndexes(conrel, copyTuple, warm_update, modified_attrs);
 
 	InvokeObjectPostAlterHook(ConstraintRelationId,
 							  HeapTupleGetOid(copyTuple), 0);
@@ -3374,6 +3380,8 @@ AlterTypeOwnerInternal(Oid typeOid, Oid newOwnerId)
 	Acl		   *newAcl;
 	Datum		aclDatum;
 	bool		isNull;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	rel = heap_open(TypeRelationId, RowExclusiveLock);
 
@@ -3404,9 +3412,9 @@ AlterTypeOwnerInternal(Oid typeOid, Oid newOwnerId)
 	tup = heap_modify_tuple(tup, RelationGetDescr(rel), repl_val, repl_null,
 							repl_repl);
 
-	simple_heap_update(rel, &tup->t_self, tup);
+	simple_heap_update(rel, &tup->t_self, tup, &warm_update, &modified_attrs);
 
-	CatalogUpdateIndexes(rel, tup);
+	CatalogUpdateIndexes(rel, tup, warm_update, modified_attrs);
 
 	/* If it has an array type, update that too */
 	if (OidIsValid(typTup->typarray))
@@ -3561,13 +3569,16 @@ AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid,
 
 	if (oldNspOid != nspOid)
 	{
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
+
 		/* OK, modify the pg_type row */
 
 		/* tup is a copy, so we can scribble directly on it */
 		typform->typnamespace = nspOid;
 
-		simple_heap_update(rel, &tup->t_self, tup);
-		CatalogUpdateIndexes(rel, tup);
+		simple_heap_update(rel, &tup->t_self, tup, &warm_update, &modified_attrs);
+		CatalogUpdateIndexes(rel, tup, warm_update, modified_attrs);
 	}
 
 	/*
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index e6fdac3..5d67041 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -434,7 +434,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 	 * Insert new record in the pg_authid table
 	 */
 	roleid = simple_heap_insert(pg_authid_rel, tuple);
-	CatalogUpdateIndexes(pg_authid_rel, tuple);
+	CatalogUpdateIndexes(pg_authid_rel, tuple, false, NULL);
 
 	/*
 	 * Advance command counter so we can see new record; else tests in
@@ -531,6 +531,8 @@ AlterRole(AlterRoleStmt *stmt)
 	DefElem    *dvalidUntil = NULL;
 	DefElem    *dbypassRLS = NULL;
 	Oid			roleid;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	check_rolespec_name(stmt->role,
 						"Cannot alter reserved roles.");
@@ -838,10 +840,11 @@ AlterRole(AlterRoleStmt *stmt)
 
 	new_tuple = heap_modify_tuple(tuple, pg_authid_dsc, new_record,
 								  new_record_nulls, new_record_repl);
-	simple_heap_update(pg_authid_rel, &tuple->t_self, new_tuple);
+	simple_heap_update(pg_authid_rel, &tuple->t_self, new_tuple, &warm_update,
+			&modified_attrs);
 
 	/* Update indexes */
-	CatalogUpdateIndexes(pg_authid_rel, new_tuple);
+	CatalogUpdateIndexes(pg_authid_rel, new_tuple, warm_update, modified_attrs);
 
 	InvokeObjectPostAlterHook(AuthIdRelationId, roleid, 0);
 
@@ -1149,6 +1152,8 @@ RenameRole(const char *oldname, const char *newname)
 	Oid			roleid;
 	ObjectAddress address;
 	Form_pg_authid authform;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	rel = heap_open(AuthIdRelationId, RowExclusiveLock);
 	dsc = RelationGetDescr(rel);
@@ -1243,9 +1248,9 @@ RenameRole(const char *oldname, const char *newname)
 	}
 
 	newtuple = heap_modify_tuple(oldtuple, dsc, repl_val, repl_null, repl_repl);
-	simple_heap_update(rel, &oldtuple->t_self, newtuple);
+	simple_heap_update(rel, &oldtuple->t_self, newtuple, &warm_update, &modified_attrs);
 
-	CatalogUpdateIndexes(rel, newtuple);
+	CatalogUpdateIndexes(rel, newtuple, warm_update, modified_attrs);
 
 	InvokeObjectPostAlterHook(AuthIdRelationId, roleid, 0);
 
@@ -1527,13 +1532,16 @@ AddRoleMems(const char *rolename, Oid roleid,
 
 		if (HeapTupleIsValid(authmem_tuple))
 		{
+			bool		warm_update;
+			Bitmapset	*modified_attrs;
+
 			new_record_repl[Anum_pg_auth_members_grantor - 1] = true;
 			new_record_repl[Anum_pg_auth_members_admin_option - 1] = true;
 			tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc,
 									  new_record,
 									  new_record_nulls, new_record_repl);
-			simple_heap_update(pg_authmem_rel, &tuple->t_self, tuple);
-			CatalogUpdateIndexes(pg_authmem_rel, tuple);
+			simple_heap_update(pg_authmem_rel, &tuple->t_self, tuple, &warm_update, &modified_attrs);
+			CatalogUpdateIndexes(pg_authmem_rel, tuple, warm_update, modified_attrs);
 			ReleaseSysCache(authmem_tuple);
 		}
 		else
@@ -1541,7 +1549,7 @@ AddRoleMems(const char *rolename, Oid roleid,
 			tuple = heap_form_tuple(pg_authmem_dsc,
 									new_record, new_record_nulls);
 			simple_heap_insert(pg_authmem_rel, tuple);
-			CatalogUpdateIndexes(pg_authmem_rel, tuple);
+			CatalogUpdateIndexes(pg_authmem_rel, tuple, false, NULL);
 		}
 
 		/* CCI after each change, in case there are duplicates in list */
@@ -1637,6 +1645,8 @@ DelRoleMems(const char *rolename, Oid roleid,
 			Datum		new_record[Natts_pg_auth_members];
 			bool		new_record_nulls[Natts_pg_auth_members];
 			bool		new_record_repl[Natts_pg_auth_members];
+			bool		warm_update;
+			Bitmapset	*modified_attrs;
 
 			/* Build a tuple to update with */
 			MemSet(new_record, 0, sizeof(new_record));
@@ -1649,8 +1659,10 @@ DelRoleMems(const char *rolename, Oid roleid,
 			tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc,
 									  new_record,
 									  new_record_nulls, new_record_repl);
-			simple_heap_update(pg_authmem_rel, &tuple->t_self, tuple);
-			CatalogUpdateIndexes(pg_authmem_rel, tuple);
+			simple_heap_update(pg_authmem_rel, &tuple->t_self, tuple,
+					&warm_update, &modified_attrs);
+			CatalogUpdateIndexes(pg_authmem_rel, tuple, warm_update,
+					modified_attrs);
 		}
 
 		ReleaseSysCache(authmem_tuple);
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index 005440e..9e3d0ee 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -2158,6 +2158,22 @@ heap_page_is_all_visible(Relation rel, Buffer buf,
 						break;
 					}
 
+					/*
+					 * If this tuple was ever WARM updated or is a WARM tuple,
+					 * there could be multiple index entries pointing to the
+					 * root of this chain. We can't do index-only scans for
+					 * such tuples without verifying index key check. So mark
+					 * the page as !all_visible
+					 *
+					 * XXX Should we look at the root line pointer and check if
+					 * WARM flag is set there or checking for tuples in the
+					 * chain is good enough?
+					 */
+					if (HeapTupleHeaderIsHeapWarmTuple(tuple.t_data))
+					{
+						all_visible = false;
+					}
+
 					/* Track newest xmin on page. */
 					if (TransactionIdFollows(xmin, *visibility_cutoff_xid))
 						*visibility_cutoff_xid = xmin;
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 9920f48..94cf92f 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -270,6 +270,8 @@ ExecCloseIndices(ResultRelInfo *resultRelInfo)
 List *
 ExecInsertIndexTuples(TupleTableSlot *slot,
 					  ItemPointer tupleid,
+					  ItemPointer root_tid,
+					  Bitmapset *modified_attrs,
 					  EState *estate,
 					  bool noDupErr,
 					  bool *specConflict,
@@ -324,6 +326,17 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
 		if (!indexInfo->ii_ReadyForInserts)
 			continue;
 
+		/*
+		 * If modified_attrs is set, we only insert index entries for those
+		 * indexes whose column has changed. All other indexes can use their
+		 * existing index pointers to look up the new tuple
+		 */
+		if (modified_attrs)
+		{
+			if (!bms_overlap(modified_attrs, indexInfo->ii_indxattrs))
+				continue;
+		}
+
 		/* Check for partial index */
 		if (indexInfo->ii_Predicate != NIL)
 		{
@@ -389,7 +402,7 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
 			index_insert(indexRelation, /* index relation */
 						 values,	/* array of index Datums */
 						 isnull,	/* null flags */
-						 tupleid,		/* tid of heap tuple */
+						 root_tid,		/* tid of heap or root tuple */
 						 heapRelation,	/* heap relation */
 						 checkUnique);	/* type of uniqueness check to do */
 
@@ -790,6 +803,9 @@ retry:
 		{
 			if (!HeapTupleHeaderIsHeapLatest(tup->t_data, &tup->t_self))
 				HeapTupleHeaderGetNextTid(tup->t_data, &ctid_wait);
+			else
+				ItemPointerCopy(&tup->t_self, &ctid_wait);
+
 			reason_wait = indexInfo->ii_ExclusionOps ?
 				XLTW_RecheckExclusionConstr : XLTW_InsertIndex;
 			index_endscan(index_scan);
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index a18ae51..fb81633 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -399,6 +399,7 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 
 		if (resultRelInfo->ri_NumIndices > 0)
 			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+												   &(tuple->t_self), NULL,
 												   estate, false, NULL,
 												   NIL);
 
@@ -445,6 +446,8 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 	if (!skip_tuple)
 	{
 		List	   *recheckIndexes = NIL;
+		bool		warm_update;
+		Bitmapset  *modified_attrs;
 
 		/* Check the constraints of the tuple */
 		if (rel->rd_att->constr)
@@ -455,13 +458,31 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 
 		/* OK, update the tuple and index entries for it */
 		simple_heap_update(rel, &searchslot->tts_tuple->t_self,
-						   slot->tts_tuple);
+						   slot->tts_tuple,
+						   &warm_update,
+						   &modified_attrs);
 
 		if (resultRelInfo->ri_NumIndices > 0 &&
-			!HeapTupleIsHeapOnly(slot->tts_tuple))
+			(!HeapTupleIsHeapOnly(slot->tts_tuple) || warm_update))
+		{
+			ItemPointerData root_tid;
+
+			if (warm_update)
+				ItemPointerSet(&root_tid,
+						ItemPointerGetBlockNumber(&(tuple->t_self)),
+						HeapTupleHeaderGetRootOffset(tuple->t_data));
+			else
+			{
+				ItemPointerCopy(&tuple->t_self, &root_tid);
+				bms_free(modified_attrs);
+				modified_attrs = NULL;
+			}
+
 			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+												   &root_tid, modified_attrs,
 												   estate, false, NULL,
 												   NIL);
+		}
 
 		/* AFTER ROW UPDATE Triggers */
 		ExecARUpdateTriggers(estate, resultRelInfo,
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index f18827d..f81d290 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -37,6 +37,7 @@
 
 #include "access/relscan.h"
 #include "access/transam.h"
+#include "access/valid.h"
 #include "executor/execdebug.h"
 #include "executor/nodeBitmapHeapscan.h"
 #include "pgstat.h"
@@ -362,11 +363,27 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 			OffsetNumber offnum = tbmres->offsets[curslot];
 			ItemPointerData tid;
 			HeapTupleData heapTuple;
+			bool recheck = false;
 
 			ItemPointerSet(&tid, page, offnum);
 			if (heap_hot_search_buffer(&tid, scan->rs_rd, buffer, snapshot,
-									   &heapTuple, NULL, true))
-				scan->rs_vistuples[ntup++] = ItemPointerGetOffsetNumber(&tid);
+									   &heapTuple, NULL, true, &recheck))
+			{
+				bool valid = true;
+
+				if (scan->rs_key)
+					HeapKeyTest(&heapTuple, RelationGetDescr(scan->rs_rd),
+							scan->rs_nkeys, scan->rs_key, valid);
+				if (valid)
+					scan->rs_vistuples[ntup++] = ItemPointerGetOffsetNumber(&tid);
+
+				/*
+				 * If the heap tuple needs a recheck because of a WARM update,
+				 * it's a lossy case
+				 */
+				if (recheck)
+					tbmres->recheck = true;
+			}
 		}
 	}
 	else
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 5734550..c7be366 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -115,10 +115,10 @@ IndexNext(IndexScanState *node)
 					   false);	/* don't pfree */
 
 		/*
-		 * If the index was lossy, we have to recheck the index quals using
-		 * the fetched tuple.
+		 * If the index was lossy or the tuple was WARM, we have to recheck
+		 * the index quals using the fetched tuple.
 		 */
-		if (scandesc->xs_recheck)
+		if (scandesc->xs_recheck || scandesc->xs_tuple_recheck)
 		{
 			econtext->ecxt_scantuple = slot;
 			ResetExprContext(econtext);
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 2ac7407..142eb57 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -512,6 +512,7 @@ ExecInsert(ModifyTableState *mtstate,
 
 			/* insert index entries for tuple */
 			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+												 &(tuple->t_self), NULL,
 												 estate, true, &specConflict,
 												   arbiterIndexes);
 
@@ -558,6 +559,7 @@ ExecInsert(ModifyTableState *mtstate,
 			/* insert index entries for tuple */
 			if (resultRelInfo->ri_NumIndices > 0)
 				recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+													   &(tuple->t_self), NULL,
 													   estate, false, NULL,
 													   arbiterIndexes);
 		}
@@ -891,6 +893,9 @@ ExecUpdate(ItemPointer tupleid,
 	HTSU_Result result;
 	HeapUpdateFailureData hufd;
 	List	   *recheckIndexes = NIL;
+	Bitmapset  *modified_attrs = NULL;
+	ItemPointerData	root_tid;
+	bool		warm_update;
 
 	/*
 	 * abort the operation if not running transactions
@@ -1007,7 +1012,7 @@ lreplace:;
 							 estate->es_output_cid,
 							 estate->es_crosscheck_snapshot,
 							 true /* wait for commit */ ,
-							 &hufd, &lockmode);
+							 &hufd, &lockmode, &modified_attrs, &warm_update);
 		switch (result)
 		{
 			case HeapTupleSelfUpdated:
@@ -1094,10 +1099,28 @@ lreplace:;
 		 * the t_self field.
 		 *
 		 * If it's a HOT update, we mustn't insert new index entries.
+		 *
+		 * If it's a WARM update, then we must insert new entries with TID
+		 * pointing to the root of the WARM chain.
 		 */
-		if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple))
+		if (resultRelInfo->ri_NumIndices > 0 &&
+			(!HeapTupleIsHeapOnly(tuple) || warm_update))
+		{
+			if (warm_update)
+				ItemPointerSet(&root_tid,
+						ItemPointerGetBlockNumber(&(tuple->t_self)),
+						HeapTupleHeaderGetRootOffset(tuple->t_data));
+			else
+			{
+				ItemPointerCopy(&tuple->t_self, &root_tid);
+				bms_free(modified_attrs);
+				modified_attrs = NULL;
+			}
 			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+												   &root_tid,
+												   modified_attrs,
 												   estate, false, NULL, NIL);
+		}
 	}
 
 	if (canSetTag)
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 7176cf1..432dd4b 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -1823,7 +1823,7 @@ pgstat_count_heap_insert(Relation rel, int n)
  * pgstat_count_heap_update - count a tuple update
  */
 void
-pgstat_count_heap_update(Relation rel, bool hot)
+pgstat_count_heap_update(Relation rel, bool hot, bool warm)
 {
 	PgStat_TableStatus *pgstat_info = rel->pgstat_info;
 
@@ -1841,6 +1841,8 @@ pgstat_count_heap_update(Relation rel, bool hot)
 		/* t_tuples_hot_updated is nontransactional, so just advance it */
 		if (hot)
 			pgstat_info->t_counts.t_tuples_hot_updated++;
+		else if (warm)
+			pgstat_info->t_counts.t_tuples_warm_updated++;
 	}
 }
 
@@ -4085,6 +4087,7 @@ pgstat_get_tab_entry(PgStat_StatDBEntry *dbentry, Oid tableoid, bool create)
 		result->tuples_updated = 0;
 		result->tuples_deleted = 0;
 		result->tuples_hot_updated = 0;
+		result->tuples_warm_updated = 0;
 		result->n_live_tuples = 0;
 		result->n_dead_tuples = 0;
 		result->changes_since_analyze = 0;
@@ -5194,6 +5197,7 @@ pgstat_recv_tabstat(PgStat_MsgTabstat *msg, int len)
 			tabentry->tuples_updated = tabmsg->t_counts.t_tuples_updated;
 			tabentry->tuples_deleted = tabmsg->t_counts.t_tuples_deleted;
 			tabentry->tuples_hot_updated = tabmsg->t_counts.t_tuples_hot_updated;
+			tabentry->tuples_warm_updated = tabmsg->t_counts.t_tuples_warm_updated;
 			tabentry->n_live_tuples = tabmsg->t_counts.t_delta_live_tuples;
 			tabentry->n_dead_tuples = tabmsg->t_counts.t_delta_dead_tuples;
 			tabentry->changes_since_analyze = tabmsg->t_counts.t_changed_tuples;
@@ -5221,6 +5225,7 @@ pgstat_recv_tabstat(PgStat_MsgTabstat *msg, int len)
 			tabentry->tuples_updated += tabmsg->t_counts.t_tuples_updated;
 			tabentry->tuples_deleted += tabmsg->t_counts.t_tuples_deleted;
 			tabentry->tuples_hot_updated += tabmsg->t_counts.t_tuples_hot_updated;
+			tabentry->tuples_warm_updated += tabmsg->t_counts.t_tuples_warm_updated;
 			/* If table was truncated, first reset the live/dead counters */
 			if (tabmsg->t_counts.t_truncated)
 			{
diff --git a/src/backend/replication/logical/origin.c b/src/backend/replication/logical/origin.c
index d7dda6a..adafe23 100644
--- a/src/backend/replication/logical/origin.c
+++ b/src/backend/replication/logical/origin.c
@@ -300,7 +300,7 @@ replorigin_create(char *roname)
 
 			tuple = heap_form_tuple(RelationGetDescr(rel), values, nulls);
 			simple_heap_insert(rel, tuple);
-			CatalogUpdateIndexes(rel, tuple);
+			CatalogUpdateIndexes(rel, tuple, false, NULL);
 			CommandCounterIncrement();
 			break;
 		}
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 864d45f..59f163a 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -77,6 +77,8 @@ InsertRule(char *rulname,
 	ObjectAddress myself,
 				referenced;
 	bool		is_update = false;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/*
 	 * Set up *nulls and *values arrays
@@ -124,7 +126,7 @@ InsertRule(char *rulname,
 		tup = heap_modify_tuple(oldtup, RelationGetDescr(pg_rewrite_desc),
 								values, nulls, replaces);
 
-		simple_heap_update(pg_rewrite_desc, &tup->t_self, tup);
+		simple_heap_update(pg_rewrite_desc, &tup->t_self, tup, &warm_update, &modified_attrs);
 
 		ReleaseSysCache(oldtup);
 
@@ -136,10 +138,12 @@ InsertRule(char *rulname,
 		tup = heap_form_tuple(pg_rewrite_desc->rd_att, values, nulls);
 
 		rewriteObjectId = simple_heap_insert(pg_rewrite_desc, tup);
+		warm_update = false;
+		modified_attrs = NULL;
 	}
 
 	/* Need to update indexes in either case */
-	CatalogUpdateIndexes(pg_rewrite_desc, tup);
+	CatalogUpdateIndexes(pg_rewrite_desc, tup, warm_update, modified_attrs);
 
 	heap_freetuple(tup);
 
@@ -549,6 +553,8 @@ DefineQueryRewrite(char *rulename,
 		Oid			toastrelid;
 		HeapTuple	classTup;
 		Form_pg_class classForm;
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
 
 		relationRelation = heap_open(RelationRelationId, RowExclusiveLock);
 		toastrelid = event_relation->rd_rel->reltoastrelid;
@@ -613,8 +619,8 @@ DefineQueryRewrite(char *rulename,
 		classForm->relminmxid = InvalidMultiXactId;
 		classForm->relreplident = REPLICA_IDENTITY_NOTHING;
 
-		simple_heap_update(relationRelation, &classTup->t_self, classTup);
-		CatalogUpdateIndexes(relationRelation, classTup);
+		simple_heap_update(relationRelation, &classTup->t_self, classTup, &warm_update, &modified_attrs);
+		CatalogUpdateIndexes(relationRelation, classTup, warm_update, modified_attrs);
 
 		heap_freetuple(classTup);
 		heap_close(relationRelation, RowExclusiveLock);
@@ -864,12 +870,15 @@ EnableDisableRule(Relation rel, const char *rulename,
 	if (DatumGetChar(((Form_pg_rewrite) GETSTRUCT(ruletup))->ev_enabled) !=
 		fires_when)
 	{
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
+
 		((Form_pg_rewrite) GETSTRUCT(ruletup))->ev_enabled =
 			CharGetDatum(fires_when);
-		simple_heap_update(pg_rewrite_desc, &ruletup->t_self, ruletup);
+		simple_heap_update(pg_rewrite_desc, &ruletup->t_self, ruletup, &warm_update, &modified_attrs);
 
 		/* keep system catalog indexes current */
-		CatalogUpdateIndexes(pg_rewrite_desc, ruletup);
+		CatalogUpdateIndexes(pg_rewrite_desc, ruletup, warm_update, modified_attrs);
 
 		changed = true;
 	}
@@ -938,6 +947,8 @@ RenameRewriteRule(RangeVar *relation, const char *oldName,
 	Form_pg_rewrite ruleform;
 	Oid			ruleOid;
 	ObjectAddress address;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/*
 	 * Look up name, check permissions, and acquire lock (which we will NOT
@@ -985,10 +996,10 @@ RenameRewriteRule(RangeVar *relation, const char *oldName,
 	/* OK, do the update */
 	namestrcpy(&(ruleform->rulename), newName);
 
-	simple_heap_update(pg_rewrite_desc, &ruletup->t_self, ruletup);
+	simple_heap_update(pg_rewrite_desc, &ruletup->t_self, ruletup, &warm_update, &modified_attrs);
 
 	/* keep system catalog indexes current */
-	CatalogUpdateIndexes(pg_rewrite_desc, ruletup);
+	CatalogUpdateIndexes(pg_rewrite_desc, ruletup, warm_update, modified_attrs);
 
 	heap_freetuple(ruletup);
 	heap_close(pg_rewrite_desc, RowExclusiveLock);
diff --git a/src/backend/rewrite/rewriteSupport.c b/src/backend/rewrite/rewriteSupport.c
index 0154072..848ee7a 100644
--- a/src/backend/rewrite/rewriteSupport.c
+++ b/src/backend/rewrite/rewriteSupport.c
@@ -69,13 +69,16 @@ SetRelationRuleStatus(Oid relationId, bool relHasRules)
 
 	if (classForm->relhasrules != relHasRules)
 	{
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
+
 		/* Do the update */
 		classForm->relhasrules = relHasRules;
 
-		simple_heap_update(relationRelation, &tuple->t_self, tuple);
+		simple_heap_update(relationRelation, &tuple->t_self, tuple, &warm_update, &modified_attrs);
 
 		/* Keep the catalog indexes up to date */
-		CatalogUpdateIndexes(relationRelation, tuple);
+		CatalogUpdateIndexes(relationRelation, tuple, warm_update, modified_attrs);
 	}
 	else
 	{
diff --git a/src/backend/storage/large_object/inv_api.c b/src/backend/storage/large_object/inv_api.c
index 262b0b2..7a643bf 100644
--- a/src/backend/storage/large_object/inv_api.c
+++ b/src/backend/storage/large_object/inv_api.c
@@ -638,6 +638,9 @@ inv_write(LargeObjectDesc *obj_desc, const char *buf, int nbytes)
 		 */
 		if (olddata != NULL && olddata->pageno == pageno)
 		{
+			bool warm_update;
+			Bitmapset *modified_attrs;
+
 			/*
 			 * Update an existing page with fresh data.
 			 *
@@ -678,8 +681,9 @@ inv_write(LargeObjectDesc *obj_desc, const char *buf, int nbytes)
 			replace[Anum_pg_largeobject_data - 1] = true;
 			newtup = heap_modify_tuple(oldtuple, RelationGetDescr(lo_heap_r),
 									   values, nulls, replace);
-			simple_heap_update(lo_heap_r, &newtup->t_self, newtup);
-			CatalogIndexInsert(indstate, newtup);
+			simple_heap_update(lo_heap_r, &newtup->t_self, newtup,
+					&warm_update, &modified_attrs);
+			CatalogIndexInsert(indstate, newtup, warm_update, modified_attrs);
 			heap_freetuple(newtup);
 
 			/*
@@ -722,7 +726,7 @@ inv_write(LargeObjectDesc *obj_desc, const char *buf, int nbytes)
 			values[Anum_pg_largeobject_data - 1] = PointerGetDatum(&workbuf);
 			newtup = heap_form_tuple(lo_heap_r->rd_att, values, nulls);
 			simple_heap_insert(lo_heap_r, newtup);
-			CatalogIndexInsert(indstate, newtup);
+			CatalogIndexInsert(indstate, newtup, false, NULL);
 			heap_freetuple(newtup);
 		}
 		pageno++;
@@ -824,6 +828,8 @@ inv_truncate(LargeObjectDesc *obj_desc, int64 len)
 		bytea	   *datafield;
 		int			pagelen;
 		bool		pfreeit;
+		bool		warm_update;
+		Bitmapset	*modified_attrs;
 
 		getdatafield(olddata, &datafield, &pagelen, &pfreeit);
 		memcpy(workb, VARDATA(datafield), pagelen);
@@ -850,8 +856,9 @@ inv_truncate(LargeObjectDesc *obj_desc, int64 len)
 		replace[Anum_pg_largeobject_data - 1] = true;
 		newtup = heap_modify_tuple(oldtuple, RelationGetDescr(lo_heap_r),
 								   values, nulls, replace);
-		simple_heap_update(lo_heap_r, &newtup->t_self, newtup);
-		CatalogIndexInsert(indstate, newtup);
+		simple_heap_update(lo_heap_r, &newtup->t_self, newtup, &warm_update,
+				&modified_attrs);
+		CatalogIndexInsert(indstate, newtup, warm_update, modified_attrs);
 		heap_freetuple(newtup);
 	}
 	else
@@ -889,7 +896,7 @@ inv_truncate(LargeObjectDesc *obj_desc, int64 len)
 		values[Anum_pg_largeobject_data - 1] = PointerGetDatum(&workbuf);
 		newtup = heap_form_tuple(lo_heap_r->rd_att, values, nulls);
 		simple_heap_insert(lo_heap_r, newtup);
-		CatalogIndexInsert(indstate, newtup);
+		CatalogIndexInsert(indstate, newtup, false, NULL);
 		heap_freetuple(newtup);
 	}
 
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index a987d0d..b8677f3 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -145,6 +145,22 @@ pg_stat_get_tuples_hot_updated(PG_FUNCTION_ARGS)
 
 
 Datum
+pg_stat_get_tuples_warm_updated(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int64		result;
+	PgStat_StatTabEntry *tabentry;
+
+	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
+		result = 0;
+	else
+		result = (int64) (tabentry->tuples_warm_updated);
+
+	PG_RETURN_INT64(result);
+}
+
+
+Datum
 pg_stat_get_live_tuples(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
@@ -1644,6 +1660,21 @@ pg_stat_get_xact_tuples_hot_updated(PG_FUNCTION_ARGS)
 }
 
 Datum
+pg_stat_get_xact_tuples_warm_updated(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int64		result;
+	PgStat_TableStatus *tabentry;
+
+	if ((tabentry = find_tabstat_entry(relid)) == NULL)
+		result = 0;
+	else
+		result = (int64) (tabentry->t_counts.t_tuples_warm_updated);
+
+	PG_RETURN_INT64(result);
+}
+
+Datum
 pg_stat_get_xact_blocks_fetched(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 26ff7e1..1976753 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -2338,6 +2338,7 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 	list_free_deep(relation->rd_fkeylist);
 	list_free(relation->rd_indexlist);
 	bms_free(relation->rd_indexattr);
+	bms_free(relation->rd_exprindexattr);
 	bms_free(relation->rd_keyattr);
 	bms_free(relation->rd_pkattr);
 	bms_free(relation->rd_idattr);
@@ -3419,6 +3420,8 @@ RelationSetNewRelfilenode(Relation relation, char persistence,
 	Relation	pg_class;
 	HeapTuple	tuple;
 	Form_pg_class classform;
+	bool		warm_update;
+	Bitmapset	*modified_attrs;
 
 	/* Indexes, sequences must have Invalid frozenxid; other rels must not */
 	Assert((relation->rd_rel->relkind == RELKIND_INDEX ||
@@ -3484,8 +3487,8 @@ RelationSetNewRelfilenode(Relation relation, char persistence,
 	classform->relminmxid = minmulti;
 	classform->relpersistence = persistence;
 
-	simple_heap_update(pg_class, &tuple->t_self, tuple);
-	CatalogUpdateIndexes(pg_class, tuple);
+	simple_heap_update(pg_class, &tuple->t_self, tuple, &warm_update, &modified_attrs);
+	CatalogUpdateIndexes(pg_class, tuple, warm_update, modified_attrs);
 
 	heap_freetuple(tuple);
 
@@ -4757,6 +4760,8 @@ Bitmapset *
 RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 {
 	Bitmapset  *indexattrs;		/* indexed columns */
+	Bitmapset  *exprindexattrs;	/* indexed columns in expression/prediacate
+									 indexes */
 	Bitmapset  *uindexattrs;	/* columns in unique indexes */
 	Bitmapset  *pkindexattrs;	/* columns in the primary index */
 	Bitmapset  *idindexattrs;	/* columns in the replica identity */
@@ -4765,6 +4770,7 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 	Oid			relreplindex;
 	ListCell   *l;
 	MemoryContext oldcxt;
+	bool		supportswarm = true;/* True if the table can be WARM updated */
 
 	/* Quick exit if we already computed the result. */
 	if (relation->rd_indexattr != NULL)
@@ -4779,6 +4785,8 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 				return bms_copy(relation->rd_pkattr);
 			case INDEX_ATTR_BITMAP_IDENTITY_KEY:
 				return bms_copy(relation->rd_idattr);
+			case INDEX_ATTR_BITMAP_EXPR_PREDICATE:
+				return bms_copy(relation->rd_exprindexattr);
 			default:
 				elog(ERROR, "unknown attrKind %u", attrKind);
 		}
@@ -4819,6 +4827,7 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 	 * won't be returned at all by RelationGetIndexList.
 	 */
 	indexattrs = NULL;
+	exprindexattrs = NULL;
 	uindexattrs = NULL;
 	pkindexattrs = NULL;
 	idindexattrs = NULL;
@@ -4873,19 +4882,38 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 		}
 
 		/* Collect all attributes used in expressions, too */
-		pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs);
+		pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &exprindexattrs);
 
 		/* Collect all attributes in the index predicate, too */
-		pull_varattnos((Node *) indexInfo->ii_Predicate, 1, &indexattrs);
+		pull_varattnos((Node *) indexInfo->ii_Predicate, 1, &exprindexattrs);
+
+		/*
+		 * indexattrs should include attributes referenced in index expressions
+		 * and predicates too
+		 */
+		indexattrs = bms_add_members(indexattrs, exprindexattrs);
+
+		/*
+		 * Check if the index has amrecheck method defined. If the method is
+		 * not defined, the index does not support WARM update. Completely
+		 * disable WARM updates on such tables
+		 */
+		if (!indexDesc->rd_amroutine->amrecheck)
+			supportswarm = false;
 
 		index_close(indexDesc, AccessShareLock);
 	}
 
 	list_free(indexoidlist);
 
+	/* Remember if the table can do WARM updates */
+	relation->rd_supportswarm = supportswarm;
+
 	/* Don't leak the old values of these bitmaps, if any */
 	bms_free(relation->rd_indexattr);
 	relation->rd_indexattr = NULL;
+	bms_free(relation->rd_exprindexattr);
+	relation->rd_exprindexattr = NULL;
 	bms_free(relation->rd_keyattr);
 	relation->rd_keyattr = NULL;
 	bms_free(relation->rd_pkattr);
@@ -4904,7 +4932,8 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 	relation->rd_keyattr = bms_copy(uindexattrs);
 	relation->rd_pkattr = bms_copy(pkindexattrs);
 	relation->rd_idattr = bms_copy(idindexattrs);
-	relation->rd_indexattr = bms_copy(indexattrs);
+	relation->rd_exprindexattr = bms_copy(exprindexattrs);
+	relation->rd_indexattr = bms_copy(bms_union(indexattrs, exprindexattrs));
 	MemoryContextSwitchTo(oldcxt);
 
 	/* We return our original working copy for caller to play with */
@@ -4918,6 +4947,8 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 			return bms_copy(relation->rd_pkattr);
 		case INDEX_ATTR_BITMAP_IDENTITY_KEY:
 			return idindexattrs;
+		case INDEX_ATTR_BITMAP_EXPR_PREDICATE:
+			return exprindexattrs;
 		default:
 			elog(ERROR, "unknown attrKind %u", attrKind);
 			return NULL;
diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h
index 6a5f279..0de82fa 100644
--- a/src/include/access/amapi.h
+++ b/src/include/access/amapi.h
@@ -13,6 +13,7 @@
 #define AMAPI_H
 
 #include "access/genam.h"
+#include "access/itup.h"
 
 /*
  * We don't wish to include planner header files here, since most of an index
@@ -137,6 +138,9 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan);
 /* restore marked scan position */
 typedef void (*amrestrpos_function) (IndexScanDesc scan);
 
+/* recheck index tuple and heap tuple match */
+typedef bool (*amrecheck_function) (Relation indexRel, IndexTuple indexTuple,
+		Relation heapRel, HeapTuple heapTuple);
 
 /*
  * API struct for an index AM.  Note this must be stored in a single palloc'd
@@ -196,6 +200,7 @@ typedef struct IndexAmRoutine
 	amendscan_function amendscan;
 	ammarkpos_function ammarkpos;		/* can be NULL */
 	amrestrpos_function amrestrpos;		/* can be NULL */
+	amrecheck_function amrecheck;		/* can be NULL */
 } IndexAmRoutine;
 
 
diff --git a/src/include/access/hash.h b/src/include/access/hash.h
index 69a3873..3e14023 100644
--- a/src/include/access/hash.h
+++ b/src/include/access/hash.h
@@ -364,4 +364,8 @@ extern void hashbucketcleanup(Relation rel, Bucket cur_bucket,
 				  bool bucket_has_garbage,
 				  IndexBulkDeleteCallback callback, void *callback_state);
 
+/* hash.c */
+extern bool hashrecheck(Relation indexRel, IndexTuple indexTuple,
+		Relation heapRel, HeapTuple heapTuple);
+
 #endif   /* HASH_H */
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 22507dc..06e22a3 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -137,9 +137,10 @@ extern bool heap_fetch(Relation relation, Snapshot snapshot,
 		   Relation stats_relation);
 extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation,
 					   Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
-					   bool *all_dead, bool first_call);
+					   bool *all_dead, bool first_call, bool *recheck);
 extern bool heap_hot_search(ItemPointer tid, Relation relation,
-				Snapshot snapshot, bool *all_dead);
+				Snapshot snapshot, bool *all_dead,
+				bool *recheck, Buffer *buffer, HeapTuple heapTuple);
 
 extern void heap_get_latest_tid(Relation relation, Snapshot snapshot,
 					ItemPointer tid);
@@ -160,7 +161,8 @@ extern void heap_abort_speculative(Relation relation, HeapTuple tuple);
 extern HTSU_Result heap_update(Relation relation, ItemPointer otid,
 			HeapTuple newtup,
 			CommandId cid, Snapshot crosscheck, bool wait,
-			HeapUpdateFailureData *hufd, LockTupleMode *lockmode);
+			HeapUpdateFailureData *hufd, LockTupleMode *lockmode,
+			Bitmapset **modified_attrsp, bool *warm_update);
 extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 				CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 				bool follow_update,
@@ -175,7 +177,9 @@ extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
 extern Oid	simple_heap_insert(Relation relation, HeapTuple tup);
 extern void simple_heap_delete(Relation relation, ItemPointer tid);
 extern void simple_heap_update(Relation relation, ItemPointer otid,
-				   HeapTuple tup);
+				   HeapTuple tup,
+				   bool *warm_update,
+				   Bitmapset **modified_attrs);
 
 extern void heap_sync(Relation relation);
 
diff --git a/src/include/access/heapam_xlog.h b/src/include/access/heapam_xlog.h
index a4a1fe1..b4238e5 100644
--- a/src/include/access/heapam_xlog.h
+++ b/src/include/access/heapam_xlog.h
@@ -80,6 +80,7 @@
 #define XLH_UPDATE_CONTAINS_NEW_TUPLE			(1<<4)
 #define XLH_UPDATE_PREFIX_FROM_OLD				(1<<5)
 #define XLH_UPDATE_SUFFIX_FROM_OLD				(1<<6)
+#define XLH_UPDATE_WARM_UPDATE					(1<<7)
 
 /* convenience macro for checking whether any form of old tuple was logged */
 #define XLH_UPDATE_CONTAINS_OLD						\
diff --git a/src/include/access/htup.h b/src/include/access/htup.h
index 870adf4..0ae223e 100644
--- a/src/include/access/htup.h
+++ b/src/include/access/htup.h
@@ -14,6 +14,7 @@
 #ifndef HTUP_H
 #define HTUP_H
 
+#include "nodes/bitmapset.h"
 #include "storage/itemptr.h"
 
 /* typedefs and forward declarations for structs defined in htup_details.h */
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index fff1832..2ea4865 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -260,7 +260,8 @@ struct HeapTupleHeaderData
  * information stored in t_infomask2:
  */
 #define HEAP_NATTS_MASK			0x07FF	/* 11 bits for number of attributes */
-/* bits 0x0800 are available */
+#define HEAP_WARM_TUPLE			0x0800	/* This tuple is a part of a WARM chain
+										 */
 #define HEAP_LATEST_TUPLE		0x1000	/*
 										 * This is the last tuple in chain and
 										 * ip_posid points to the root line
@@ -271,7 +272,7 @@ struct HeapTupleHeaderData
 #define HEAP_HOT_UPDATED		0x4000	/* tuple was HOT-updated */
 #define HEAP_ONLY_TUPLE			0x8000	/* this is heap-only tuple */
 
-#define HEAP2_XACT_MASK			0xF000	/* visibility-related bits */
+#define HEAP2_XACT_MASK			0xF800	/* visibility-related bits */
 
 
 /*
@@ -510,6 +511,22 @@ do { \
   ((tup)->t_infomask2 & HEAP_ONLY_TUPLE) != 0 \
 )
 
+#define HeapTupleHeaderSetHeapWarmTuple(tup) \
+do { \
+	(tup)->t_infomask2 |= HEAP_WARM_TUPLE; \
+} while (0)
+
+#define HeapTupleHeaderClearHeapWarmTuple(tup) \
+do { \
+	(tup)->t_infomask2 &= ~HEAP_WARM_TUPLE; \
+} while (0)
+
+#define HeapTupleHeaderIsHeapWarmTuple(tup) \
+( \
+  ((tup)->t_infomask2 & HEAP_WARM_TUPLE) != 0 \
+)
+
+
 #define HeapTupleHeaderSetHeapLatest(tup, offnum) \
 do { \
 	AssertMacro(OffsetNumberIsValid(offnum)); \
@@ -763,6 +780,15 @@ struct MinimalTupleData
 #define HeapTupleClearHeapOnly(tuple) \
 		HeapTupleHeaderClearHeapOnly((tuple)->t_data)
 
+#define HeapTupleIsHeapWarmTuple(tuple) \
+		HeapTupleHeaderIsHeapWarmTuple((tuple)->t_data)
+
+#define HeapTupleSetHeapWarmTuple(tuple) \
+		HeapTupleHeaderSetHeapWarmTuple((tuple)->t_data)
+
+#define HeapTupleClearHeapWarmTuple(tuple) \
+		HeapTupleHeaderClearHeapWarmTuple((tuple)->t_data)
+
 #define HeapTupleGetOid(tuple) \
 		HeapTupleHeaderGetOid((tuple)->t_data)
 
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 011a72e..98129d6 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -750,6 +750,8 @@ extern bytea *btoptions(Datum reloptions, bool validate);
 extern bool btproperty(Oid index_oid, int attno,
 		   IndexAMProperty prop, const char *propname,
 		   bool *res, bool *isnull);
+extern bool btrecheck(Relation indexRel, IndexTuple indexTuple,
+		Relation heapRel, HeapTuple heapTuple);
 
 /*
  * prototypes for functions in nbtvalidate.c
diff --git a/src/include/access/relscan.h b/src/include/access/relscan.h
index 8746045..1f5b361 100644
--- a/src/include/access/relscan.h
+++ b/src/include/access/relscan.h
@@ -111,7 +111,8 @@ typedef struct IndexScanDescData
 	HeapTupleData xs_ctup;		/* current heap tuple, if any */
 	Buffer		xs_cbuf;		/* current heap buffer in scan, if any */
 	/* NB: if xs_cbuf is not InvalidBuffer, we hold a pin on that buffer */
-	bool		xs_recheck;		/* T means scan keys must be rechecked */
+	bool		xs_recheck;		/* T means scan keys must be rechecked for each tuple */
+	bool		xs_tuple_recheck;	/* T means scan keys must be rechecked for current tuple */
 
 	/*
 	 * When fetching with an ordering operator, the values of the ORDER BY
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 45605a0..5a8fb70 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -31,8 +31,12 @@ typedef struct ResultRelInfo *CatalogIndexState;
 extern CatalogIndexState CatalogOpenIndexes(Relation heapRel);
 extern void CatalogCloseIndexes(CatalogIndexState indstate);
 extern void CatalogIndexInsert(CatalogIndexState indstate,
-				   HeapTuple heapTuple);
-extern void CatalogUpdateIndexes(Relation heapRel, HeapTuple heapTuple);
+				   HeapTuple heapTuple,
+				   bool warm_update,
+				   Bitmapset *modified_attrs);
+extern void CatalogUpdateIndexes(Relation heapRel, HeapTuple heapTuple,
+				   bool warm_update,
+				   Bitmapset *modified_attrs);
 
 
 /*
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index ab12761..201e8b6 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2740,6 +2740,8 @@ DATA(insert OID = 1933 (  pg_stat_get_tuples_deleted	PGNSP PGUID 12 1 0 0 0 f f
 DESCR("statistics: number of tuples deleted");
 DATA(insert OID = 1972 (  pg_stat_get_tuples_hot_updated PGNSP PGUID 12 1 0 0 0 f f f f t f s r 1 0 20 "26" _null_ _null_ _null_ _null_ _null_ pg_stat_get_tuples_hot_updated _null_ _null_ _null_ ));
 DESCR("statistics: number of tuples hot updated");
+DATA(insert OID = 3353 (  pg_stat_get_tuples_warm_updated PGNSP PGUID 12 1 0 0 0 f f f f t f s r 1 0 20 "26" _null_ _null_ _null_ _null_ _null_ pg_stat_get_tuples_warm_updated _null_ _null_ _null_ ));
+DESCR("statistics: number of tuples warm updated");
 DATA(insert OID = 2878 (  pg_stat_get_live_tuples	PGNSP PGUID 12 1 0 0 0 f f f f t f s r 1 0 20 "26" _null_ _null_ _null_ _null_ _null_ pg_stat_get_live_tuples _null_ _null_ _null_ ));
 DESCR("statistics: number of live tuples");
 DATA(insert OID = 2879 (  pg_stat_get_dead_tuples	PGNSP PGUID 12 1 0 0 0 f f f f t f s r 1 0 20 "26" _null_ _null_ _null_ _null_ _null_ pg_stat_get_dead_tuples _null_ _null_ _null_ ));
@@ -2892,6 +2894,8 @@ DATA(insert OID = 3042 (  pg_stat_get_xact_tuples_deleted		PGNSP PGUID 12 1 0 0
 DESCR("statistics: number of tuples deleted in current transaction");
 DATA(insert OID = 3043 (  pg_stat_get_xact_tuples_hot_updated	PGNSP PGUID 12 1 0 0 0 f f f f t f v r 1 0 20 "26" _null_ _null_ _null_ _null_ _null_ pg_stat_get_xact_tuples_hot_updated _null_ _null_ _null_ ));
 DESCR("statistics: number of tuples hot updated in current transaction");
+DATA(insert OID = 3354 (  pg_stat_get_xact_tuples_warm_updated	PGNSP PGUID 12 1 0 0 0 f f f f t f v r 1 0 20 "26" _null_ _null_ _null_ _null_ _null_ pg_stat_get_xact_tuples_warm_updated _null_ _null_ _null_ ));
+DESCR("statistics: number of tuples warm updated in current transaction");
 DATA(insert OID = 3044 (  pg_stat_get_xact_blocks_fetched		PGNSP PGUID 12 1 0 0 0 f f f f t f v r 1 0 20 "26" _null_ _null_ _null_ _null_ _null_ pg_stat_get_xact_blocks_fetched _null_ _null_ _null_ ));
 DESCR("statistics: number of blocks fetched in current transaction");
 DATA(insert OID = 3045 (  pg_stat_get_xact_blocks_hit			PGNSP PGUID 12 1 0 0 0 f f f f t f v r 1 0 20 "26" _null_ _null_ _null_ _null_ _null_ pg_stat_get_xact_blocks_hit _null_ _null_ _null_ ));
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 02dbe7b..c4495a3 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -382,6 +382,7 @@ extern void UnregisterExprContextCallback(ExprContext *econtext,
 extern void ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative);
 extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
 extern List *ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid,
+					  ItemPointer root_tid, Bitmapset *modified_attrs,
 					  EState *estate, bool noDupErr, bool *specConflict,
 					  List *arbiterIndexes);
 extern bool ExecCheckIndexConstraints(TupleTableSlot *slot, EState *estate,
diff --git a/src/include/executor/nodeIndexscan.h b/src/include/executor/nodeIndexscan.h
index 46d6f45..2c4d884 100644
--- a/src/include/executor/nodeIndexscan.h
+++ b/src/include/executor/nodeIndexscan.h
@@ -37,5 +37,4 @@ extern void ExecIndexEvalRuntimeKeys(ExprContext *econtext,
 extern bool ExecIndexEvalArrayKeys(ExprContext *econtext,
 					   IndexArrayKeyInfo *arrayKeys, int numArrayKeys);
 extern bool ExecIndexAdvanceArrayKeys(IndexArrayKeyInfo *arrayKeys, int numArrayKeys);
-
 #endif   /* NODEINDEXSCAN_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index f9bcdd6..07f2900 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -62,6 +62,7 @@ typedef struct IndexInfo
 	NodeTag		type;
 	int			ii_NumIndexAttrs;
 	AttrNumber	ii_KeyAttrNumbers[INDEX_MAX_KEYS];
+	Bitmapset  *ii_indxattrs;	/* bitmap of all columns used in this index */
 	List	   *ii_Expressions; /* list of Expr */
 	List	   *ii_ExpressionsState;	/* list of ExprState */
 	List	   *ii_Predicate;	/* list of Expr */
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index de8225b..ee635be 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -105,6 +105,7 @@ typedef struct PgStat_TableCounts
 	PgStat_Counter t_tuples_updated;
 	PgStat_Counter t_tuples_deleted;
 	PgStat_Counter t_tuples_hot_updated;
+	PgStat_Counter t_tuples_warm_updated;
 	bool		t_truncated;
 
 	PgStat_Counter t_delta_live_tuples;
@@ -625,6 +626,7 @@ typedef struct PgStat_StatTabEntry
 	PgStat_Counter tuples_updated;
 	PgStat_Counter tuples_deleted;
 	PgStat_Counter tuples_hot_updated;
+	PgStat_Counter tuples_warm_updated;
 
 	PgStat_Counter n_live_tuples;
 	PgStat_Counter n_dead_tuples;
@@ -1177,7 +1179,7 @@ pgstat_report_wait_end(void)
 	(pgStatBlockWriteTime += (n))
 
 extern void pgstat_count_heap_insert(Relation rel, int n);
-extern void pgstat_count_heap_update(Relation rel, bool hot);
+extern void pgstat_count_heap_update(Relation rel, bool hot, bool warm);
 extern void pgstat_count_heap_delete(Relation rel);
 extern void pgstat_count_truncate(Relation rel);
 extern void pgstat_update_heap_dead_tuples(Relation rel, int delta);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index a1750ac..092491f 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -138,9 +138,12 @@ typedef struct RelationData
 
 	/* data managed by RelationGetIndexAttrBitmap: */
 	Bitmapset  *rd_indexattr;	/* identifies columns used in indexes */
+	Bitmapset  *rd_exprindexattr; /* indentified columns used in expression or
+									 predicate indexes */
 	Bitmapset  *rd_keyattr;		/* cols that can be ref'd by foreign keys */
 	Bitmapset  *rd_pkattr;		/* cols included in primary key */
 	Bitmapset  *rd_idattr;		/* included in replica identity index */
+	bool		rd_supportswarm;/* True if the table can be WARM updated */
 
 	PublicationActions  *rd_pubactions;	/* publication actions */
 
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index da36b67..83a7f20 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -50,7 +50,8 @@ typedef enum IndexAttrBitmapKind
 	INDEX_ATTR_BITMAP_ALL,
 	INDEX_ATTR_BITMAP_KEY,
 	INDEX_ATTR_BITMAP_PRIMARY_KEY,
-	INDEX_ATTR_BITMAP_IDENTITY_KEY
+	INDEX_ATTR_BITMAP_IDENTITY_KEY,
+	INDEX_ATTR_BITMAP_EXPR_PREDICATE
 } IndexAttrBitmapKind;
 
 extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,
diff --git a/src/test/regress/expected/alter_generic.out b/src/test/regress/expected/alter_generic.out
index b01be59..37719c9 100644
--- a/src/test/regress/expected/alter_generic.out
+++ b/src/test/regress/expected/alter_generic.out
@@ -161,15 +161,15 @@ ALTER SERVER alt_fserv1 RENAME TO alt_fserv3;   -- OK
 SELECT fdwname FROM pg_foreign_data_wrapper WHERE fdwname like 'alt_fdw%';
  fdwname  
 ----------
- alt_fdw2
  alt_fdw3
+ alt_fdw2
 (2 rows)
 
 SELECT srvname FROM pg_foreign_server WHERE srvname like 'alt_fserv%';
   srvname   
 ------------
- alt_fserv2
  alt_fserv3
+ alt_fserv2
 (2 rows)
 
 --
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 60abcad..42d45a1 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1718,6 +1718,7 @@ pg_stat_all_tables| SELECT c.oid AS relid,
     pg_stat_get_tuples_updated(c.oid) AS n_tup_upd,
     pg_stat_get_tuples_deleted(c.oid) AS n_tup_del,
     pg_stat_get_tuples_hot_updated(c.oid) AS n_tup_hot_upd,
+    pg_stat_get_tuples_warm_updated(c.oid) AS n_tup_warm_upd,
     pg_stat_get_live_tuples(c.oid) AS n_live_tup,
     pg_stat_get_dead_tuples(c.oid) AS n_dead_tup,
     pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze,
@@ -1861,6 +1862,7 @@ pg_stat_sys_tables| SELECT pg_stat_all_tables.relid,
     pg_stat_all_tables.n_tup_upd,
     pg_stat_all_tables.n_tup_del,
     pg_stat_all_tables.n_tup_hot_upd,
+    pg_stat_all_tables.n_tup_warm_upd,
     pg_stat_all_tables.n_live_tup,
     pg_stat_all_tables.n_dead_tup,
     pg_stat_all_tables.n_mod_since_analyze,
@@ -1904,6 +1906,7 @@ pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
     pg_stat_all_tables.n_tup_upd,
     pg_stat_all_tables.n_tup_del,
     pg_stat_all_tables.n_tup_hot_upd,
+    pg_stat_all_tables.n_tup_warm_upd,
     pg_stat_all_tables.n_live_tup,
     pg_stat_all_tables.n_dead_tup,
     pg_stat_all_tables.n_mod_since_analyze,
@@ -1941,7 +1944,8 @@ pg_stat_xact_all_tables| SELECT c.oid AS relid,
     pg_stat_get_xact_tuples_inserted(c.oid) AS n_tup_ins,
     pg_stat_get_xact_tuples_updated(c.oid) AS n_tup_upd,
     pg_stat_get_xact_tuples_deleted(c.oid) AS n_tup_del,
-    pg_stat_get_xact_tuples_hot_updated(c.oid) AS n_tup_hot_upd
+    pg_stat_get_xact_tuples_hot_updated(c.oid) AS n_tup_hot_upd,
+    pg_stat_get_xact_tuples_warm_updated(c.oid) AS n_tup_warm_upd
    FROM ((pg_class c
      LEFT JOIN pg_index i ON ((c.oid = i.indrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
@@ -1957,7 +1961,8 @@ pg_stat_xact_sys_tables| SELECT pg_stat_xact_all_tables.relid,
     pg_stat_xact_all_tables.n_tup_ins,
     pg_stat_xact_all_tables.n_tup_upd,
     pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+    pg_stat_xact_all_tables.n_tup_hot_upd,
+    pg_stat_xact_all_tables.n_tup_warm_upd
    FROM pg_stat_xact_all_tables
   WHERE ((pg_stat_xact_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_xact_all_tables.schemaname ~ '^pg_toast'::text));
 pg_stat_xact_user_functions| SELECT p.oid AS funcid,
@@ -1979,7 +1984,8 @@ pg_stat_xact_user_tables| SELECT pg_stat_xact_all_tables.relid,
     pg_stat_xact_all_tables.n_tup_ins,
     pg_stat_xact_all_tables.n_tup_upd,
     pg_stat_xact_all_tables.n_tup_del,
-    pg_stat_xact_all_tables.n_tup_hot_upd
+    pg_stat_xact_all_tables.n_tup_hot_upd,
+    pg_stat_xact_all_tables.n_tup_warm_upd
    FROM pg_stat_xact_all_tables
   WHERE ((pg_stat_xact_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_xact_all_tables.schemaname !~ '^pg_toast'::text));
 pg_statio_all_indexes| SELECT c.oid AS relid,
diff --git a/src/test/regress/expected/warm.out b/src/test/regress/expected/warm.out
new file mode 100644
index 0000000..ebbc4ca
--- /dev/null
+++ b/src/test/regress/expected/warm.out
@@ -0,0 +1,367 @@
+CREATE TABLE updtst_tab1 (a integer unique, b int, c text, d text);
+CREATE INDEX updtst_indx1 ON updtst_tab1 (b);
+INSERT INTO updtst_tab1
+       SELECT generate_series(1,10000), generate_series(70001, 80000), 'foo', 'bar';
+-- This should be a HOT update as non-index key is updated, but the
+-- page won't have any free space, so probably a non-HOT update
+UPDATE updtst_tab1 SET c = 'foo1' WHERE a = 1;
+-- Next update should be a HOT update as dead space is recycled
+UPDATE updtst_tab1 SET c = 'foo2' WHERE a = 1;
+-- And next too
+UPDATE updtst_tab1 SET c = 'foo3' WHERE a = 1;
+-- Now update one of the index key columns
+UPDATE updtst_tab1 SET b = b + 70000 WHERE a = 1;
+-- Ensure that the correct row is fetched
+SELECT * FROM updtst_tab1 WHERE a = 1;
+ a |   b    |  c   |  d  
+---+--------+------+-----
+ 1 | 140001 | foo3 | bar
+(1 row)
+
+SELECT * FROM updtst_tab1 WHERE b = 70001 + 70000;
+ a |   b    |  c   |  d  
+---+--------+------+-----
+ 1 | 140001 | foo3 | bar
+(1 row)
+
+-- Even when seqscan is disabled and indexscan is forced
+SET enable_seqscan = false;
+EXPLAIN SELECT * FROM updtst_tab1 WHERE b = 70001 + 70000;
+                                 QUERY PLAN                                 
+----------------------------------------------------------------------------
+ Bitmap Heap Scan on updtst_tab1  (cost=4.45..47.23 rows=22 width=72)
+   Recheck Cond: (b = 140001)
+   ->  Bitmap Index Scan on updtst_indx1  (cost=0.00..4.45 rows=22 width=0)
+         Index Cond: (b = 140001)
+(4 rows)
+
+SELECT * FROM updtst_tab1 WHERE b = 70001 + 70000;
+ a |   b    |  c   |  d  
+---+--------+------+-----
+ 1 | 140001 | foo3 | bar
+(1 row)
+
+-- Check if index only scan works correctly
+EXPLAIN SELECT b FROM updtst_tab1 WHERE b = 70001 + 70000;
+                                 QUERY PLAN                                 
+----------------------------------------------------------------------------
+ Bitmap Heap Scan on updtst_tab1  (cost=4.45..47.23 rows=22 width=4)
+   Recheck Cond: (b = 140001)
+   ->  Bitmap Index Scan on updtst_indx1  (cost=0.00..4.45 rows=22 width=0)
+         Index Cond: (b = 140001)
+(4 rows)
+
+SELECT b FROM updtst_tab1 WHERE b = 70001 + 70000;
+   b    
+--------
+ 140001
+(1 row)
+
+-- Table must be vacuumed to force index-only scan
+VACUUM updtst_tab1;
+EXPLAIN SELECT b FROM updtst_tab1 WHERE b = 70001 + 70000;
+                                      QUERY PLAN                                      
+--------------------------------------------------------------------------------------
+ Index Only Scan using updtst_indx1 on updtst_tab1  (cost=0.29..5.16 rows=50 width=4)
+   Index Cond: (b = 140001)
+(2 rows)
+
+SELECT b FROM updtst_tab1 WHERE b = 70001 + 70000;
+   b    
+--------
+ 140001
+(1 row)
+
+SET enable_seqscan = true;
+DROP TABLE updtst_tab1;
+------------------
+CREATE TABLE updtst_tab2 (a integer unique, b int, c text, d text) WITH (fillfactor = 80);
+CREATE INDEX updtst_indx2 ON updtst_tab2 (b);
+INSERT INTO updtst_tab2
+       SELECT generate_series(1,100), generate_series(701, 800), 'foo', 'bar';
+UPDATE updtst_tab2 SET b = b + 700 WHERE a = 1;
+UPDATE updtst_tab2 SET c = 'foo1'  WHERE a = 1;
+UPDATE updtst_tab2 SET c = 'foo2'  WHERE a = 1;
+UPDATE updtst_tab2 SET c = 'foo3'  WHERE a = 1;
+UPDATE updtst_tab2 SET b = b - 700 WHERE a = 1;
+UPDATE updtst_tab2 SET c = 'foo4'  WHERE a = 1;
+UPDATE updtst_tab2 SET c = 'foo5'  WHERE a = 1;
+UPDATE updtst_tab2 SET c = 'foo6'  WHERE a = 1;
+SELECT count(*) FROM updtst_tab2 WHERE c = 'foo';
+ count 
+-------
+    99
+(1 row)
+
+SELECT * FROM updtst_tab2 WHERE c = 'foo6';
+ a |  b  |  c   |  d  
+---+-----+------+-----
+ 1 | 701 | foo6 | bar
+(1 row)
+
+EXPLAIN SELECT * FROM updtst_tab2 WHERE b = 701;
+                                QUERY PLAN                                 
+---------------------------------------------------------------------------
+ Bitmap Heap Scan on updtst_tab2  (cost=4.18..12.64 rows=4 width=72)
+   Recheck Cond: (b = 701)
+   ->  Bitmap Index Scan on updtst_indx2  (cost=0.00..4.18 rows=4 width=0)
+         Index Cond: (b = 701)
+(4 rows)
+
+SELECT * FROM updtst_tab2 WHERE a = 1;
+ a |  b  |  c   |  d  
+---+-----+------+-----
+ 1 | 701 | foo6 | bar
+(1 row)
+
+SET enable_seqscan = false;
+EXPLAIN SELECT * FROM updtst_tab2 WHERE b = 701;
+                                QUERY PLAN                                 
+---------------------------------------------------------------------------
+ Bitmap Heap Scan on updtst_tab2  (cost=4.18..12.64 rows=4 width=72)
+   Recheck Cond: (b = 701)
+   ->  Bitmap Index Scan on updtst_indx2  (cost=0.00..4.18 rows=4 width=0)
+         Index Cond: (b = 701)
+(4 rows)
+
+SELECT * FROM updtst_tab2 WHERE b = 701;
+ a |  b  |  c   |  d  
+---+-----+------+-----
+ 1 | 701 | foo6 | bar
+(1 row)
+
+VACUUM updtst_tab2;
+EXPLAIN SELECT b FROM updtst_tab2 WHERE b = 701;
+                                     QUERY PLAN                                      
+-------------------------------------------------------------------------------------
+ Index Only Scan using updtst_indx2 on updtst_tab2  (cost=0.14..4.16 rows=1 width=4)
+   Index Cond: (b = 701)
+(2 rows)
+
+SELECT b FROM updtst_tab2 WHERE b = 701;
+  b  
+-----
+ 701
+(1 row)
+
+SET enable_seqscan = true;
+DROP TABLE updtst_tab2;
+------------------
+CREATE TABLE updtst_tab3 (a integer unique, b int, c text, d text) WITH (fillfactor = 80);
+CREATE INDEX updtst_indx3 ON updtst_tab3 (b);
+INSERT INTO updtst_tab3
+       SELECT generate_series(1,100), generate_series(701, 800), 'foo', 'bar';
+BEGIN;
+UPDATE updtst_tab3 SET c = 'foo1', b = b + 700 WHERE a = 1;
+UPDATE updtst_tab3 SET c = 'foo2'  WHERE a = 1;
+UPDATE updtst_tab3 SET c = 'foo3'  WHERE a = 1;
+UPDATE updtst_tab3 SET b = b - 700 WHERE a = 1;
+UPDATE updtst_tab3 SET c = 'foo4'  WHERE a = 1;
+UPDATE updtst_tab3 SET c = 'foo5'  WHERE a = 1;
+UPDATE updtst_tab3 SET c = 'foo6'  WHERE a = 1;
+-- Abort the transaction and ensure the original tuple is visible correctly
+ROLLBACK;
+BEGIN;
+UPDATE updtst_tab3 SET c = 'foo11', b = b + 750 WHERE b = 701;
+UPDATE updtst_tab3 SET c = 'foo12'  WHERE a = 1;
+UPDATE updtst_tab3 SET b = b - 30 WHERE a = 1;
+COMMIT;
+SELECT count(*) FROM updtst_tab3 WHERE c = 'foo';
+ count 
+-------
+    99
+(1 row)
+
+SELECT * FROM updtst_tab3 WHERE c = 'foo6';
+ a | b | c | d 
+---+---+---+---
+(0 rows)
+
+SELECT * FROM updtst_tab3 WHERE c = 'foo12';
+ a |  b   |   c   |  d  
+---+------+-------+-----
+ 1 | 1421 | foo12 | bar
+(1 row)
+
+SELECT * FROM updtst_tab3 WHERE b = 701;
+ a | b | c | d 
+---+---+---+---
+(0 rows)
+
+SELECT * FROM updtst_tab3 WHERE b = 1421;
+ a |  b   |   c   |  d  
+---+------+-------+-----
+ 1 | 1421 | foo12 | bar
+(1 row)
+
+SELECT * FROM updtst_tab3 WHERE a = 1;
+ a |  b   |   c   |  d  
+---+------+-------+-----
+ 1 | 1421 | foo12 | bar
+(1 row)
+
+SELECT * FROM updtst_tab3 WHERE b = 701;
+ a | b | c | d 
+---+---+---+---
+(0 rows)
+
+SELECT * FROM updtst_tab3 WHERE b = 1421;
+ a |  b   |   c   |  d  
+---+------+-------+-----
+ 1 | 1421 | foo12 | bar
+(1 row)
+
+VACUUM updtst_tab3;
+EXPLAIN SELECT b FROM updtst_tab3 WHERE b = 701;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Seq Scan on updtst_tab3  (cost=0.00..2.25 rows=1 width=4)
+   Filter: (b = 701)
+(2 rows)
+
+SELECT b FROM updtst_tab3 WHERE b = 701;
+ b 
+---
+(0 rows)
+
+SELECT b FROM updtst_tab3 WHERE b = 1421;
+  b   
+------
+ 1421
+(1 row)
+
+BEGIN;
+UPDATE updtst_tab3 SET c = 'foo21', b = b + 700 WHERE a = 2;
+UPDATE updtst_tab3 SET c = 'foo22'  WHERE a = 2;
+UPDATE updtst_tab3 SET c = 'foo23'  WHERE a = 2;
+UPDATE updtst_tab3 SET b = b - 700 WHERE a = 2;
+UPDATE updtst_tab3 SET c = 'foo24'  WHERE a = 2;
+UPDATE updtst_tab3 SET c = 'foo25'  WHERE a = 2;
+UPDATE updtst_tab3 SET c = 'foo26'  WHERE a = 2;
+-- Abort the transaction and ensure the original tuple is visible correctly
+ROLLBACK;
+SET enable_seqscan = false;
+BEGIN;
+UPDATE updtst_tab3 SET c = 'foo21', b = b + 750 WHERE b = 702;
+UPDATE updtst_tab3 SET c = 'foo22'  WHERE a = 2;
+UPDATE updtst_tab3 SET b = b - 30 WHERE a = 2;
+COMMIT;
+SELECT count(*) FROM updtst_tab3 WHERE c = 'foo';
+ count 
+-------
+    98
+(1 row)
+
+SELECT * FROM updtst_tab3 WHERE c = 'foo26';
+ a | b | c | d 
+---+---+---+---
+(0 rows)
+
+SELECT * FROM updtst_tab3 WHERE c = 'foo22';
+ a |  b   |   c   |  d  
+---+------+-------+-----
+ 2 | 1422 | foo22 | bar
+(1 row)
+
+SELECT * FROM updtst_tab3 WHERE b = 702;
+ a | b | c | d 
+---+---+---+---
+(0 rows)
+
+SELECT * FROM updtst_tab3 WHERE b = 1422;
+ a |  b   |   c   |  d  
+---+------+-------+-----
+ 2 | 1422 | foo22 | bar
+(1 row)
+
+SELECT * FROM updtst_tab3 WHERE a = 2;
+ a |  b   |   c   |  d  
+---+------+-------+-----
+ 2 | 1422 | foo22 | bar
+(1 row)
+
+-- Try fetching both old and new value using updtst_indx3
+SELECT * FROM updtst_tab3 WHERE b = 702;
+ a | b | c | d 
+---+---+---+---
+(0 rows)
+
+SELECT * FROM updtst_tab3 WHERE b = 1422;
+ a |  b   |   c   |  d  
+---+------+-------+-----
+ 2 | 1422 | foo22 | bar
+(1 row)
+
+VACUUM updtst_tab3;
+EXPLAIN SELECT b FROM updtst_tab3 WHERE b = 702;
+                                     QUERY PLAN                                      
+-------------------------------------------------------------------------------------
+ Index Only Scan using updtst_indx3 on updtst_tab3  (cost=0.14..8.16 rows=1 width=4)
+   Index Cond: (b = 702)
+(2 rows)
+
+SELECT b FROM updtst_tab3 WHERE b = 702;
+ b 
+---
+(0 rows)
+
+SELECT b FROM updtst_tab3 WHERE b = 1422;
+  b   
+------
+ 1422
+(1 row)
+
+SET enable_seqscan = true;
+DROP TABLE updtst_tab3;
+------------------
+CREATE TABLE test_warm (a text unique, b text);
+CREATE INDEX test_warmindx ON test_warm (lower(a));
+INSERT INTO test_warm values ('test', 'foo');
+UPDATE test_warm SET a = 'TEST';
+select *, ctid from test_warm where lower(a) = 'test';
+  a   |  b  | ctid  
+------+-----+-------
+ TEST | foo | (0,2)
+(1 row)
+
+explain select * from test_warm where lower(a) = 'test';
+                                 QUERY PLAN                                 
+----------------------------------------------------------------------------
+ Bitmap Heap Scan on test_warm  (cost=4.18..12.65 rows=4 width=64)
+   Recheck Cond: (lower(a) = 'test'::text)
+   ->  Bitmap Index Scan on test_warmindx  (cost=0.00..4.18 rows=4 width=0)
+         Index Cond: (lower(a) = 'test'::text)
+(4 rows)
+
+select *, ctid from test_warm where lower(a) = 'test';
+  a   |  b  | ctid  
+------+-----+-------
+ TEST | foo | (0,2)
+(1 row)
+
+select *, ctid from test_warm where a = 'test';
+ a | b | ctid 
+---+---+------
+(0 rows)
+
+select *, ctid from test_warm where a = 'TEST';
+  a   |  b  | ctid  
+------+-----+-------
+ TEST | foo | (0,2)
+(1 row)
+
+set enable_bitmapscan TO false;
+explain select * from test_warm where lower(a) = 'test';
+                                   QUERY PLAN                                    
+---------------------------------------------------------------------------------
+ Index Scan using test_warmindx on test_warm  (cost=0.15..20.22 rows=4 width=64)
+   Index Cond: (lower(a) = 'test'::text)
+(2 rows)
+
+select *, ctid from test_warm where lower(a) = 'test';
+  a   |  b  | ctid  
+------+-----+-------
+ TEST | foo | (0,2)
+(1 row)
+
+DROP TABLE test_warm;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e9b2bad..a9a269b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -42,6 +42,8 @@ test: create_type
 test: create_table
 test: create_function_2
 
+test: warm
+
 # ----------
 # Load huge amounts of data
 # We should split the data files into single files and then
diff --git a/src/test/regress/sql/warm.sql b/src/test/regress/sql/warm.sql
new file mode 100644
index 0000000..b73c278
--- /dev/null
+++ b/src/test/regress/sql/warm.sql
@@ -0,0 +1,172 @@
+
+CREATE TABLE updtst_tab1 (a integer unique, b int, c text, d text);
+CREATE INDEX updtst_indx1 ON updtst_tab1 (b);
+INSERT INTO updtst_tab1
+       SELECT generate_series(1,10000), generate_series(70001, 80000), 'foo', 'bar';
+
+-- This should be a HOT update as non-index key is updated, but the
+-- page won't have any free space, so probably a non-HOT update
+UPDATE updtst_tab1 SET c = 'foo1' WHERE a = 1;
+
+-- Next update should be a HOT update as dead space is recycled
+UPDATE updtst_tab1 SET c = 'foo2' WHERE a = 1;
+
+-- And next too
+UPDATE updtst_tab1 SET c = 'foo3' WHERE a = 1;
+
+-- Now update one of the index key columns
+UPDATE updtst_tab1 SET b = b + 70000 WHERE a = 1;
+
+-- Ensure that the correct row is fetched
+SELECT * FROM updtst_tab1 WHERE a = 1;
+SELECT * FROM updtst_tab1 WHERE b = 70001 + 70000;
+
+-- Even when seqscan is disabled and indexscan is forced
+SET enable_seqscan = false;
+EXPLAIN SELECT * FROM updtst_tab1 WHERE b = 70001 + 70000;
+SELECT * FROM updtst_tab1 WHERE b = 70001 + 70000;
+
+-- Check if index only scan works correctly
+EXPLAIN SELECT b FROM updtst_tab1 WHERE b = 70001 + 70000;
+SELECT b FROM updtst_tab1 WHERE b = 70001 + 70000;
+
+-- Table must be vacuumed to force index-only scan
+VACUUM updtst_tab1;
+EXPLAIN SELECT b FROM updtst_tab1 WHERE b = 70001 + 70000;
+SELECT b FROM updtst_tab1 WHERE b = 70001 + 70000;
+
+SET enable_seqscan = true;
+
+DROP TABLE updtst_tab1;
+
+------------------
+
+CREATE TABLE updtst_tab2 (a integer unique, b int, c text, d text) WITH (fillfactor = 80);
+CREATE INDEX updtst_indx2 ON updtst_tab2 (b);
+INSERT INTO updtst_tab2
+       SELECT generate_series(1,100), generate_series(701, 800), 'foo', 'bar';
+
+UPDATE updtst_tab2 SET b = b + 700 WHERE a = 1;
+UPDATE updtst_tab2 SET c = 'foo1'  WHERE a = 1;
+UPDATE updtst_tab2 SET c = 'foo2'  WHERE a = 1;
+UPDATE updtst_tab2 SET c = 'foo3'  WHERE a = 1;
+UPDATE updtst_tab2 SET b = b - 700 WHERE a = 1;
+UPDATE updtst_tab2 SET c = 'foo4'  WHERE a = 1;
+UPDATE updtst_tab2 SET c = 'foo5'  WHERE a = 1;
+UPDATE updtst_tab2 SET c = 'foo6'  WHERE a = 1;
+
+SELECT count(*) FROM updtst_tab2 WHERE c = 'foo';
+SELECT * FROM updtst_tab2 WHERE c = 'foo6';
+
+EXPLAIN SELECT * FROM updtst_tab2 WHERE b = 701;
+SELECT * FROM updtst_tab2 WHERE a = 1;
+
+SET enable_seqscan = false;
+EXPLAIN SELECT * FROM updtst_tab2 WHERE b = 701;
+SELECT * FROM updtst_tab2 WHERE b = 701;
+
+VACUUM updtst_tab2;
+EXPLAIN SELECT b FROM updtst_tab2 WHERE b = 701;
+SELECT b FROM updtst_tab2 WHERE b = 701;
+
+SET enable_seqscan = true;
+
+DROP TABLE updtst_tab2;
+------------------
+
+CREATE TABLE updtst_tab3 (a integer unique, b int, c text, d text) WITH (fillfactor = 80);
+CREATE INDEX updtst_indx3 ON updtst_tab3 (b);
+INSERT INTO updtst_tab3
+       SELECT generate_series(1,100), generate_series(701, 800), 'foo', 'bar';
+
+BEGIN;
+UPDATE updtst_tab3 SET c = 'foo1', b = b + 700 WHERE a = 1;
+UPDATE updtst_tab3 SET c = 'foo2'  WHERE a = 1;
+UPDATE updtst_tab3 SET c = 'foo3'  WHERE a = 1;
+UPDATE updtst_tab3 SET b = b - 700 WHERE a = 1;
+UPDATE updtst_tab3 SET c = 'foo4'  WHERE a = 1;
+UPDATE updtst_tab3 SET c = 'foo5'  WHERE a = 1;
+UPDATE updtst_tab3 SET c = 'foo6'  WHERE a = 1;
+
+-- Abort the transaction and ensure the original tuple is visible correctly
+ROLLBACK;
+
+BEGIN;
+UPDATE updtst_tab3 SET c = 'foo11', b = b + 750 WHERE b = 701;
+UPDATE updtst_tab3 SET c = 'foo12'  WHERE a = 1;
+UPDATE updtst_tab3 SET b = b - 30 WHERE a = 1;
+COMMIT;
+
+SELECT count(*) FROM updtst_tab3 WHERE c = 'foo';
+SELECT * FROM updtst_tab3 WHERE c = 'foo6';
+SELECT * FROM updtst_tab3 WHERE c = 'foo12';
+
+SELECT * FROM updtst_tab3 WHERE b = 701;
+SELECT * FROM updtst_tab3 WHERE b = 1421;
+SELECT * FROM updtst_tab3 WHERE a = 1;
+
+SELECT * FROM updtst_tab3 WHERE b = 701;
+SELECT * FROM updtst_tab3 WHERE b = 1421;
+
+VACUUM updtst_tab3;
+EXPLAIN SELECT b FROM updtst_tab3 WHERE b = 701;
+SELECT b FROM updtst_tab3 WHERE b = 701;
+SELECT b FROM updtst_tab3 WHERE b = 1421;
+
+BEGIN;
+UPDATE updtst_tab3 SET c = 'foo21', b = b + 700 WHERE a = 2;
+UPDATE updtst_tab3 SET c = 'foo22'  WHERE a = 2;
+UPDATE updtst_tab3 SET c = 'foo23'  WHERE a = 2;
+UPDATE updtst_tab3 SET b = b - 700 WHERE a = 2;
+UPDATE updtst_tab3 SET c = 'foo24'  WHERE a = 2;
+UPDATE updtst_tab3 SET c = 'foo25'  WHERE a = 2;
+UPDATE updtst_tab3 SET c = 'foo26'  WHERE a = 2;
+
+-- Abort the transaction and ensure the original tuple is visible correctly
+ROLLBACK;
+
+SET enable_seqscan = false;
+
+BEGIN;
+UPDATE updtst_tab3 SET c = 'foo21', b = b + 750 WHERE b = 702;
+UPDATE updtst_tab3 SET c = 'foo22'  WHERE a = 2;
+UPDATE updtst_tab3 SET b = b - 30 WHERE a = 2;
+COMMIT;
+
+SELECT count(*) FROM updtst_tab3 WHERE c = 'foo';
+SELECT * FROM updtst_tab3 WHERE c = 'foo26';
+SELECT * FROM updtst_tab3 WHERE c = 'foo22';
+
+SELECT * FROM updtst_tab3 WHERE b = 702;
+SELECT * FROM updtst_tab3 WHERE b = 1422;
+SELECT * FROM updtst_tab3 WHERE a = 2;
+
+-- Try fetching both old and new value using updtst_indx3
+SELECT * FROM updtst_tab3 WHERE b = 702;
+SELECT * FROM updtst_tab3 WHERE b = 1422;
+
+VACUUM updtst_tab3;
+EXPLAIN SELECT b FROM updtst_tab3 WHERE b = 702;
+SELECT b FROM updtst_tab3 WHERE b = 702;
+SELECT b FROM updtst_tab3 WHERE b = 1422;
+
+SET enable_seqscan = true;
+
+DROP TABLE updtst_tab3;
+------------------
+
+CREATE TABLE test_warm (a text unique, b text);
+CREATE INDEX test_warmindx ON test_warm (lower(a));
+INSERT INTO test_warm values ('test', 'foo');
+UPDATE test_warm SET a = 'TEST';
+select *, ctid from test_warm where lower(a) = 'test';
+explain select * from test_warm where lower(a) = 'test';
+select *, ctid from test_warm where lower(a) = 'test';
+select *, ctid from test_warm where a = 'test';
+select *, ctid from test_warm where a = 'TEST';
+set enable_bitmapscan TO false;
+explain select * from test_warm where lower(a) = 'test';
+select *, ctid from test_warm where lower(a) = 'test';
+DROP TABLE test_warm;
+
+
