From f2c55c23fc262d4c18c9a53b26be0c050c522f09 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:59:20 +1100
Subject: [PATCH 11/15] Improve tuple locking interface

Currently, executor code have to traverse heap update chains.  That's doesn't
seems to be acceptable if we're going to have pluggable table access methods
whose could provide alternative implementations for our MVCC model.  New locking
function is responsible for finding latest tuple version (if required).
EvalPlanQual() is now only responsible for re-evaluating quals, but not for
locking tuple.  In addition, we've distinguish HeapTupleUpdated and
HeapTupleDeleted HTSU_Result's, because in alternative MVCC implementations
multiple tuple versions may have same TID, and immutability of TID after update
isn't sign of tuple deletion anymore.  For the same reason, TID is not pointer
to particular tuple version anymore.  And in order to point particular tuple
version we're going to lock, we've to provide snapshot as well.  In heap
storage access method, this snapshot is used for assert checking only, but
it might be vital for other table access methods.  Similar changes are
upcoming to tuple_update() and tuple_delete() interface methods.
---
 src/backend/access/heap/heapam.c                   |  27 +-
 src/backend/access/heap/heapam_handler.c           | 179 ++++++++++++-
 src/backend/access/heap/heapam_visibility.c        |  20 +-
 src/backend/access/table/tableam.c                 |  11 +-
 src/backend/commands/trigger.c                     |  71 ++---
 src/backend/executor/execMain.c                    | 292 +--------------------
 src/backend/executor/execReplication.c             |  36 ++-
 src/backend/executor/nodeLockRows.c                |  70 ++---
 src/backend/executor/nodeModifyTable.c             | 154 +++++++----
 src/include/access/heapam.h                        |   1 +
 src/include/access/tableam.h                       |   8 +-
 src/include/access/tableamapi.h                    |   4 +-
 src/include/executor/executor.h                    |   6 +-
 src/include/nodes/lockoptions.h                    |   5 +
 src/include/utils/snapshot.h                       |   1 +
 .../isolation/expected/partition-key-update-1.out  |   2 +-
 16 files changed, 416 insertions(+), 471 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 74f611f2a9..08ec2c6d4f 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3210,6 +3210,7 @@ l1:
 	{
 		Assert(result == HeapTupleSelfUpdated ||
 			   result == HeapTupleUpdated ||
+			   result == HeapTupleDeleted ||
 			   result == HeapTupleBeingUpdated);
 		Assert(!(tp.t_data->t_infomask & HEAP_XMAX_INVALID));
 		hufd->ctid = tp.t_data->t_ctid;
@@ -3223,6 +3224,8 @@ l1:
 			UnlockTupleTuplock(relation, &(tp.t_self), LockTupleExclusive);
 		if (vmbuffer != InvalidBuffer)
 			ReleaseBuffer(vmbuffer);
+		if (result == HeapTupleUpdated && ItemPointerEquals(tid, &hufd->ctid))
+			result = HeapTupleDeleted;
 		return result;
 	}
 
@@ -3440,6 +3443,10 @@ simple_heap_delete(Relation relation, ItemPointer tid)
 			elog(ERROR, "tuple concurrently updated");
 			break;
 
+		case HeapTupleDeleted:
+			elog(ERROR, "tuple concurrently deleted");
+			break;
+
 		default:
 			elog(ERROR, "unrecognized heap_delete status: %u", result);
 			break;
@@ -3860,6 +3867,7 @@ l2:
 	{
 		Assert(result == HeapTupleSelfUpdated ||
 			   result == HeapTupleUpdated ||
+			   result == HeapTupleDeleted ||
 			   result == HeapTupleBeingUpdated);
 		Assert(!(oldtup.t_data->t_infomask & HEAP_XMAX_INVALID));
 		hufd->ctid = oldtup.t_data->t_ctid;
@@ -3879,6 +3887,8 @@ l2:
 		bms_free(id_attrs);
 		bms_free(modified_attrs);
 		bms_free(interesting_attrs);
+		if (result == HeapTupleUpdated && ItemPointerEquals(otid, &hufd->ctid))
+			result = HeapTupleDeleted;
 		return result;
 	}
 
@@ -4582,6 +4592,10 @@ simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup)
 			elog(ERROR, "tuple concurrently updated");
 			break;
 
+		case HeapTupleDeleted:
+			elog(ERROR, "tuple concurrently deleted");
+			break;
+
 		default:
 			elog(ERROR, "unrecognized heap_update status: %u", result);
 			break;
@@ -4634,6 +4648,7 @@ get_mxact_status_for_lock(LockTupleMode mode, bool is_update)
  *	HeapTupleInvisible: lock failed because tuple was never visible to us
  *	HeapTupleSelfUpdated: lock failed because tuple updated by self
  *	HeapTupleUpdated: lock failed because tuple updated by other xact
+ *	HeapTupleDeleted: lock failed because tuple deleted by other xact
  *	HeapTupleWouldBlock: lock couldn't be acquired and wait_policy is skip
  *
  * In the failure cases other than HeapTupleInvisible, the routine fills
@@ -4702,7 +4717,7 @@ l3:
 		result = HeapTupleInvisible;
 		goto out_locked;
 	}
-	else if (result == HeapTupleBeingUpdated || result == HeapTupleUpdated)
+	else if (result == HeapTupleBeingUpdated || result == HeapTupleUpdated || result == HeapTupleDeleted)
 	{
 		TransactionId xwait;
 		uint16		infomask;
@@ -4982,7 +4997,7 @@ l3:
 		 * or we must wait for the locking transaction or multixact; so below
 		 * we ensure that we grab buffer lock after the sleep.
 		 */
-		if (require_sleep && (result == HeapTupleUpdated))
+		if (require_sleep && (result == HeapTupleUpdated || result == HeapTupleDeleted))
 		{
 			LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
 			goto failed;
@@ -5142,6 +5157,8 @@ l3:
 			HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask) ||
 			HeapTupleHeaderIsOnlyLocked(tuple->t_data))
 			result = HeapTupleMayBeUpdated;
+		else if (ItemPointerEquals(&tuple->t_self, &tuple->t_data->t_ctid))
+			result = HeapTupleDeleted;
 		else
 			result = HeapTupleUpdated;
 	}
@@ -5150,7 +5167,7 @@ failed:
 	if (result != HeapTupleMayBeUpdated)
 	{
 		Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated ||
-			   result == HeapTupleWouldBlock);
+			   result == HeapTupleWouldBlock || result == HeapTupleDeleted);
 		Assert(!(tuple->t_data->t_infomask & HEAP_XMAX_INVALID));
 		hufd->ctid = tuple->t_data->t_ctid;
 		hufd->xmax = HeapTupleHeaderGetUpdateXid(tuple->t_data);
@@ -6038,6 +6055,10 @@ next:
 	result = HeapTupleMayBeUpdated;
 
 out_locked:
+
+	if (result == HeapTupleUpdated && ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid))
+		result = HeapTupleDeleted;
+
 	UnlockReleaseBuffer(buf);
 
 out_unlocked:
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f2de024fdb..062e184095 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -174,6 +174,7 @@ heapam_heap_delete(Relation relation, ItemPointer tid, CommandId cid,
  *	HeapTupleInvisible: lock failed because tuple was never visible to us
  *	HeapTupleSelfUpdated: lock failed because tuple updated by self
  *	HeapTupleUpdated: lock failed because tuple updated by other xact
+ *	HeapTupleDeleted: lock failed because tuple deleted by other xact
  *	HeapTupleWouldBlock: lock couldn't be acquired and wait_policy is skip
  *
  * In the failure cases other than HeapTupleInvisible, the routine fills
@@ -184,21 +185,191 @@ heapam_heap_delete(Relation relation, ItemPointer tid, CommandId cid,
  * See comments for struct HeapUpdateFailureData for additional info.
  */
 static HTSU_Result
-heapam_lock_tuple(Relation relation, ItemPointer tid, TableTuple *stuple,
-				CommandId cid, LockTupleMode mode,
-				LockWaitPolicy wait_policy, bool follow_updates, Buffer *buffer,
+heapam_lock_tuple(Relation relation, ItemPointer tid, Snapshot snapshot,
+				TableTuple *stuple, CommandId cid, LockTupleMode mode,
+				LockWaitPolicy wait_policy, uint8 flags,
 				HeapUpdateFailureData *hufd)
 {
 	HTSU_Result		result;
 	HeapTupleData	tuple;
+	Buffer			buffer;
 
 	Assert(stuple != NULL);
 	*stuple = NULL;
 
+	hufd->traversed = false;
+
+retry:
 	tuple.t_self = *tid;
-	result = heap_lock_tuple(relation, &tuple, cid, mode, wait_policy, follow_updates, buffer, hufd);
+	result = heap_lock_tuple(relation, &tuple, cid, mode, wait_policy,
+		(flags & TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS) ? true : false,
+		&buffer, hufd);
+
+	if (result == HeapTupleUpdated &&
+		(flags & TUPLE_LOCK_FLAG_FIND_LAST_VERSION))
+	{
+		ReleaseBuffer(buffer);
+		/* Should not encounter speculative tuple on recheck */
+		Assert(!HeapTupleHeaderIsSpeculative(tuple.t_data));
+
+		if (!ItemPointerEquals(&hufd->ctid, &tuple.t_self))
+		{
+			SnapshotData	SnapshotDirty;
+			TransactionId	priorXmax;
+
+			/* it was updated, so look at the updated version */
+			*tid = hufd->ctid;
+			/* updated row should have xmin matching this xmax */
+			priorXmax = hufd->xmax;
+
+			/*
+			 * fetch target tuple
+			 *
+			 * Loop here to deal with updated or busy tuples
+			 */
+			InitDirtySnapshot(SnapshotDirty);
+			for (;;)
+			{
+				if (ItemPointerIndicatesMovedPartitions(tid))
+					ereport(ERROR,
+							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+							 errmsg("tuple to be locked was already moved to another partition due to concurrent update")));
+
+
+				if (heap_fetch(relation, tid, &SnapshotDirty, &tuple, &buffer, true, NULL))
+				{
+					/*
+					 * If xmin isn't what we're expecting, the slot must have been
+					 * recycled and reused for an unrelated tuple.  This implies that
+					 * the latest version of the row was deleted, so we need do
+					 * nothing.  (Should be safe to examine xmin without getting
+					 * buffer's content lock.  We assume reading a TransactionId to be
+					 * atomic, and Xmin never changes in an existing tuple, except to
+					 * invalid or frozen, and neither of those can match priorXmax.)
+					 */
+					if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple.t_data),
+											 priorXmax))
+					{
+						ReleaseBuffer(buffer);
+						return HeapTupleDeleted;
+					}
+
+					/* otherwise xmin should not be dirty... */
+					if (TransactionIdIsValid(SnapshotDirty.xmin))
+						elog(ERROR, "t_xmin is uncommitted in tuple to be updated");
+
+					/*
+					 * If tuple is being updated by other transaction then we have to
+					 * wait for its commit/abort, or die trying.
+					 */
+					if (TransactionIdIsValid(SnapshotDirty.xmax))
+					{
+						ReleaseBuffer(buffer);
+						switch (wait_policy)
+						{
+							case LockWaitBlock:
+								XactLockTableWait(SnapshotDirty.xmax,
+												  relation, &tuple.t_self,
+												  XLTW_FetchUpdated);
+								break;
+							case LockWaitSkip:
+								if (!ConditionalXactLockTableWait(SnapshotDirty.xmax))
+									return result;	/* skip instead of waiting */
+								break;
+							case LockWaitError:
+								if (!ConditionalXactLockTableWait(SnapshotDirty.xmax))
+									ereport(ERROR,
+											(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+											 errmsg("could not obtain lock on row in relation \"%s\"",
+													RelationGetRelationName(relation))));
+								break;
+						}
+						continue;		/* loop back to repeat heap_fetch */
+					}
+
+					/*
+					 * If tuple was inserted by our own transaction, we have to check
+					 * cmin against es_output_cid: cmin >= current CID means our
+					 * command cannot see the tuple, so we should ignore it. Otherwise
+					 * heap_lock_tuple() will throw an error, and so would any later
+					 * attempt to update or delete the tuple.  (We need not check cmax
+					 * because HeapTupleSatisfiesDirty will consider a tuple deleted
+					 * by our transaction dead, regardless of cmax.) We just checked
+					 * that priorXmax == xmin, so we can test that variable instead of
+					 * doing HeapTupleHeaderGetXmin again.
+					 */
+					if (TransactionIdIsCurrentTransactionId(priorXmax) &&
+						HeapTupleHeaderGetCmin(tuple.t_data) >= cid)
+					{
+						ReleaseBuffer(buffer);
+						return result;
+					}
+
+					hufd->traversed = true;
+					*tid = tuple.t_data->t_ctid;
+					ReleaseBuffer(buffer);
+					goto retry;
+				}
+
+				/*
+				 * If the referenced slot was actually empty, the latest version of
+				 * the row must have been deleted, so we need do nothing.
+				 */
+				if (tuple.t_data == NULL)
+				{
+					ReleaseBuffer(buffer);
+					return HeapTupleDeleted;
+				}
+
+				/*
+				 * As above, if xmin isn't what we're expecting, do nothing.
+				 */
+				if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple.t_data),
+										 priorXmax))
+				{
+					ReleaseBuffer(buffer);
+					return HeapTupleDeleted;
+				}
+
+				/*
+				 * If we get here, the tuple was found but failed SnapshotDirty.
+				 * Assuming the xmin is either a committed xact or our own xact (as it
+				 * certainly should be if we're trying to modify the tuple), this must
+				 * mean that the row was updated or deleted by either a committed xact
+				 * or our own xact.  If it was deleted, we can ignore it; if it was
+				 * updated then chain up to the next version and repeat the whole
+				 * process.
+				 *
+				 * As above, it should be safe to examine xmax and t_ctid without the
+				 * buffer content lock, because they can't be changing.
+				 */
+				if (ItemPointerEquals(&tuple.t_self, &tuple.t_data->t_ctid))
+				{
+					/* deleted, so forget about it */
+					ReleaseBuffer(buffer);
+					return HeapTupleDeleted;
+				}
+
+				/* updated, so look at the updated row */
+				*tid = tuple.t_data->t_ctid;
+				/* updated row should have xmin matching this xmax */
+				priorXmax = HeapTupleHeaderGetUpdateXid(tuple.t_data);
+				ReleaseBuffer(buffer);
+				/* loop back to fetch next in chain */
+			}
+		}
+		else
+		{
+			/* tuple was deleted, so give up */
+			return HeapTupleDeleted;
+		}
+	}
+
+	Assert((flags & TUPLE_LOCK_FLAG_FIND_LAST_VERSION) ||
+			HeapTupleSatisfies((TableTuple) &tuple, snapshot, InvalidBuffer));
 
 	*stuple = heap_copytuple(&tuple);
+	ReleaseBuffer(buffer);
 
 	return result;
 }
diff --git a/src/backend/access/heap/heapam_visibility.c b/src/backend/access/heap/heapam_visibility.c
index 9051d4be88..1d45f98a2e 100644
--- a/src/backend/access/heap/heapam_visibility.c
+++ b/src/backend/access/heap/heapam_visibility.c
@@ -616,7 +616,11 @@ HeapTupleSatisfiesUpdate(TableTuple stup, CommandId curcid,
 	{
 		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
 			return HeapTupleMayBeUpdated;
-		return HeapTupleUpdated;	/* updated by other */
+		/* updated by other */
+		if (ItemPointerEquals(&htup->t_self, &tuple->t_ctid))
+			return HeapTupleDeleted;
+		else
+			return HeapTupleUpdated;
 	}
 
 	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
@@ -657,7 +661,12 @@ HeapTupleSatisfiesUpdate(TableTuple stup, CommandId curcid,
 			return HeapTupleBeingUpdated;
 
 		if (TransactionIdDidCommit(xmax))
-			return HeapTupleUpdated;
+		{
+			if (ItemPointerEquals(&htup->t_self, &tuple->t_ctid))
+				return HeapTupleDeleted;
+			else
+				return HeapTupleUpdated;
+		}
 
 		/*
 		 * By here, the update in the Xmax is either aborted or crashed, but
@@ -713,7 +722,12 @@ HeapTupleSatisfiesUpdate(TableTuple stup, CommandId curcid,
 
 	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
 				HeapTupleHeaderGetRawXmax(tuple));
-	return HeapTupleUpdated;	/* updated by other */
+
+	/* updated by other */
+	if (ItemPointerEquals(&htup->t_self, &tuple->t_ctid))
+		return HeapTupleDeleted;
+	else
+		return HeapTupleUpdated;
 }
 
 /*
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index 576afe5de2..39b0f990c4 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -41,13 +41,14 @@ table_fetch(Relation relation,
  *	table_lock_tuple - lock a tuple in shared or exclusive mode
  */
 HTSU_Result
-table_lock_tuple(Relation relation, ItemPointer tid, TableTuple * stuple,
-				   CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
-				   bool follow_updates, Buffer *buffer, HeapUpdateFailureData *hufd)
+table_lock_tuple(Relation relation, ItemPointer tid, Snapshot snapshot,
+				   TableTuple *stuple, CommandId cid, LockTupleMode mode,
+				   LockWaitPolicy wait_policy, uint8 flags,
+				   HeapUpdateFailureData *hufd)
 {
-	return relation->rd_tableamroutine->tuple_lock(relation, tid, stuple,
+	return relation->rd_tableamroutine->tuple_lock(relation, tid, snapshot, stuple,
 												cid, mode, wait_policy,
-												follow_updates, buffer, hufd);
+												flags, hufd);
 }
 
 /* ----------------
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 4eed3fe434..65fd58f35f 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -3251,8 +3251,6 @@ GetTupleForTrigger(EState *estate,
 	Relation	relation = relinfo->ri_RelationDesc;
 	TableTuple tuple;
 	HeapTuple	result;
-	Buffer		buffer;
-	tuple_data	t_data;
 
 	if (newSlot != NULL)
 	{
@@ -3267,11 +3265,11 @@ GetTupleForTrigger(EState *estate,
 		/*
 		 * lock tuple for update
 		 */
-ltrmark:;
-		test = table_lock_tuple(relation, tid, &tuple,
+		test = table_lock_tuple(relation, tid, estate->es_snapshot, &tuple,
 								  estate->es_output_cid,
 								  lockmode, LockWaitBlock,
-								  false, &buffer, &hufd);
+								  IsolationUsesXactSnapshot() ? 0 : TUPLE_LOCK_FLAG_FIND_LAST_VERSION,
+								  &hufd);
 		result = tuple;
 		switch (test)
 		{
@@ -3292,68 +3290,55 @@ ltrmark:;
 							 errhint("Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.")));
 
 				/* treat it as deleted; do not process */
-				ReleaseBuffer(buffer);
 				return NULL;
 
 			case HeapTupleMayBeUpdated:
-				break;
-
-			case HeapTupleUpdated:
-				ReleaseBuffer(buffer);
-				if (IsolationUsesXactSnapshot())
-					ereport(ERROR,
-							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
-							 errmsg("could not serialize access due to concurrent update")));
-				if (ItemPointerIndicatesMovedPartitions(&hufd.ctid))
-					ereport(ERROR,
-							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
-							 errmsg("tuple to be locked was already moved to another partition due to concurrent update")));
 
-				t_data = relation->rd_tableamroutine->get_tuple_data(tuple, TID);
-				if (!ItemPointerEquals(&hufd.ctid, &(t_data.tid)))
+				if (hufd.traversed)
 				{
-					/* it was updated, so look at the updated version */
 					TupleTableSlot *epqslot;
 
 					epqslot = EvalPlanQual(estate,
 										   epqstate,
 										   relation,
 										   relinfo->ri_RangeTableIndex,
-										   lockmode,
-										   &hufd.ctid,
-										   hufd.xmax);
-					if (!TupIsNull(epqslot))
-					{
-						*tid = hufd.ctid;
-						*newSlot = epqslot;
-
-						/*
-						 * EvalPlanQual already locked the tuple, but we
-						 * re-call heap_lock_tuple anyway as an easy way of
-						 * re-fetching the correct tuple.  Speed is hardly a
-						 * criterion in this path anyhow.
-						 */
-						goto ltrmark;
-					}
+										   tuple);
+
+					/* If PlanQual failed for updated tuple - we must not process this tuple!*/
+					if (TupIsNull(epqslot))
+						return NULL;
+
+					*newSlot = epqslot;
 				}
+				break;
 
-				/*
-				 * if tuple was deleted or PlanQual failed for updated tuple -
-				 * we must not process this tuple!
-				 */
+			case HeapTupleUpdated:
+				if (IsolationUsesXactSnapshot())
+					ereport(ERROR,
+							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+							 errmsg("could not serialize access due to concurrent update")));
+				elog(ERROR, "wrong heap_lock_tuple status: %u", test);
+				break;
+
+			case HeapTupleDeleted:
+				if (IsolationUsesXactSnapshot())
+					ereport(ERROR,
+							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+							 errmsg("could not serialize access due to concurrent update")));
+				/* tuple was deleted */
 				return NULL;
 
 			case HeapTupleInvisible:
 				elog(ERROR, "attempted to lock invisible tuple");
 
 			default:
-				ReleaseBuffer(buffer);
 				elog(ERROR, "unrecognized heap_lock_tuple status: %u", test);
 				return NULL;	/* keep compiler quiet */
 		}
 	}
 	else
 	{
+		Buffer		buffer;
 		Page		page;
 		ItemId		lp;
 		HeapTupleData tupledata;
@@ -3383,9 +3368,9 @@ ltrmark:;
 		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
 
 		result = heap_copytuple(&tupledata);
+		ReleaseBuffer(buffer);
 	}
 
-	ReleaseBuffer(buffer);
 	return result;
 }
 
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index cbc429e21f..a4dd306445 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -2484,9 +2484,7 @@ ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist)
  *	epqstate - state for EvalPlanQual rechecking
  *	relation - table containing tuple
  *	rti - rangetable index of table containing tuple
- *	lockmode - requested tuple lock mode
- *	*tid - t_ctid from the outdated tuple (ie, next updated version)
- *	priorXmax - t_xmax from the outdated tuple
+ *	tuple - tuple for processing
  *
  * *tid is also an output parameter: it's modified to hold the TID of the
  * latest version of the tuple (note this may be changed even on failure)
@@ -2499,32 +2497,12 @@ ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist)
  */
 TupleTableSlot *
 EvalPlanQual(EState *estate, EPQState *epqstate,
-			 Relation relation, Index rti, int lockmode,
-			 ItemPointer tid, TransactionId priorXmax)
+			 Relation relation, Index rti, TableTuple tuple)
 {
 	TupleTableSlot *slot;
-	TableTuple copyTuple;
-	tuple_data	t_data;
 
 	Assert(rti > 0);
 
-	/*
-	 * Get and lock the updated version of the row; if fail, return NULL.
-	 */
-	copyTuple = EvalPlanQualFetch(estate, relation, lockmode, LockWaitBlock,
-								  tid, priorXmax);
-
-	if (copyTuple == NULL)
-		return NULL;
-
-	/*
-	 * For UPDATE/DELETE we have to return tid of actual row we're executing
-	 * PQ for.
-	 */
-
-	t_data = table_tuple_get_data(relation, copyTuple, TID);
-	*tid = t_data.tid;
-
 	/*
 	 * Need to run a recheck subquery.  Initialize or reinitialize EPQ state.
 	 */
@@ -2534,7 +2512,7 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
 	 * Free old test tuple, if any, and store new tuple where relation's scan
 	 * node will see it
 	 */
-	EvalPlanQualSetTuple(epqstate, rti, copyTuple);
+	EvalPlanQualSetTuple(epqstate, rti, tuple);
 
 	/*
 	 * Fetch any non-locked source rows
@@ -2566,270 +2544,6 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
 	return slot;
 }
 
-/*
- * Fetch a copy of the newest version of an outdated tuple
- *
- *	estate - executor state data
- *	relation - table containing tuple
- *	lockmode - requested tuple lock mode
- *	wait_policy - requested lock wait policy
- *	*tid - t_ctid from the outdated tuple (ie, next updated version)
- *	priorXmax - t_xmax from the outdated tuple
- *
- * Returns a palloc'd copy of the newest tuple version, or NULL if we find
- * that there is no newest version (ie, the row was deleted not updated).
- * We also return NULL if the tuple is locked and the wait policy is to skip
- * such tuples.
- *
- * If successful, we have locked the newest tuple version, so caller does not
- * need to worry about it changing anymore.
- *
- * Note: properly, lockmode should be declared as enum LockTupleMode,
- * but we use "int" to avoid having to include heapam.h in executor.h.
- */
-TableTuple
-EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
-				  LockWaitPolicy wait_policy,
-				  ItemPointer tid, TransactionId priorXmax)
-{
-	TableTuple tuple = NULL;
-	SnapshotData SnapshotDirty;
-	tuple_data	t_data;
-
-	/*
-	 * fetch target tuple
-	 *
-	 * Loop here to deal with updated or busy tuples
-	 */
-	InitDirtySnapshot(SnapshotDirty);
-	for (;;)
-	{
-		Buffer		buffer;
-		ItemPointerData ctid;
-
-		if (table_fetch(relation, tid, &SnapshotDirty, &tuple, &buffer, true, NULL))
-		{
-			HTSU_Result test;
-			HeapUpdateFailureData hufd;
-
-			/*
-			 * If xmin isn't what we're expecting, the slot must have been
-			 * recycled and reused for an unrelated tuple.  This implies that
-			 * the latest version of the row was deleted, so we need do
-			 * nothing.  (Should be safe to examine xmin without getting
-			 * buffer's content lock.  We assume reading a TransactionId to be
-			 * atomic, and Xmin never changes in an existing tuple, except to
-			 * invalid or frozen, and neither of those can match priorXmax.)
-			 */
-			if (!TransactionIdEquals(HeapTupleHeaderGetXmin(((HeapTuple) tuple)->t_data),
-									 priorXmax))
-			{
-				ReleaseBuffer(buffer);
-				return NULL;
-			}
-
-			/* otherwise xmin should not be dirty... */
-			if (TransactionIdIsValid(SnapshotDirty.xmin))
-				elog(ERROR, "t_xmin is uncommitted in tuple to be updated");
-
-			/*
-			 * If tuple is being updated by other transaction then we have to
-			 * wait for its commit/abort, or die trying.
-			 */
-			if (TransactionIdIsValid(SnapshotDirty.xmax))
-			{
-				ReleaseBuffer(buffer);
-				switch (wait_policy)
-				{
-					case LockWaitBlock:
-						XactLockTableWait(SnapshotDirty.xmax,
-										  relation,
-										  tid,
-										  XLTW_FetchUpdated);
-						break;
-					case LockWaitSkip:
-						if (!ConditionalXactLockTableWait(SnapshotDirty.xmax))
-							return NULL;	/* skip instead of waiting */
-						break;
-					case LockWaitError:
-						if (!ConditionalXactLockTableWait(SnapshotDirty.xmax))
-							ereport(ERROR,
-									(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-									 errmsg("could not obtain lock on row in relation \"%s\"",
-											RelationGetRelationName(relation))));
-						break;
-				}
-				continue;		/* loop back to repeat heap_fetch */
-			}
-
-			/*
-			 * If tuple was inserted by our own transaction, we have to check
-			 * cmin against es_output_cid: cmin >= current CID means our
-			 * command cannot see the tuple, so we should ignore it. Otherwise
-			 * heap_lock_tuple() will throw an error, and so would any later
-			 * attempt to update or delete the tuple.  (We need not check cmax
-			 * because HeapTupleSatisfiesDirty will consider a tuple deleted
-			 * by our transaction dead, regardless of cmax.) We just checked
-			 * that priorXmax == xmin, so we can test that variable instead of
-			 * doing HeapTupleHeaderGetXmin again.
-			 */
-			if (TransactionIdIsCurrentTransactionId(priorXmax))
-			{
-				t_data = table_tuple_get_data(relation, tuple, CMIN);
-				if (t_data.cid >= estate->es_output_cid)
-				{
-					ReleaseBuffer(buffer);
-					return NULL;
-				}
-			}
-
-			/*
-			 * This is a live tuple, so now try to lock it.
-			 */
-			test = table_lock_tuple(relation, tid, &tuple,
-									  estate->es_output_cid,
-									  lockmode, wait_policy,
-									  false, &buffer, &hufd);
-			/* We now have two pins on the buffer, get rid of one */
-			ReleaseBuffer(buffer);
-
-			switch (test)
-			{
-				case HeapTupleSelfUpdated:
-
-					/*
-					 * The target tuple was already updated or deleted by the
-					 * current command, or by a later command in the current
-					 * transaction.  We *must* ignore the tuple in the former
-					 * case, so as to avoid the "Halloween problem" of
-					 * repeated update attempts.  In the latter case it might
-					 * be sensible to fetch the updated tuple instead, but
-					 * doing so would require changing heap_update and
-					 * heap_delete to not complain about updating "invisible"
-					 * tuples, which seems pretty scary (heap_lock_tuple will
-					 * not complain, but few callers expect
-					 * HeapTupleInvisible, and we're not one of them).  So for
-					 * now, treat the tuple as deleted and do not process.
-					 */
-					ReleaseBuffer(buffer);
-					return NULL;
-
-				case HeapTupleMayBeUpdated:
-					/* successfully locked */
-					break;
-
-				case HeapTupleUpdated:
-					ReleaseBuffer(buffer);
-					if (IsolationUsesXactSnapshot())
-						ereport(ERROR,
-								(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
-								 errmsg("could not serialize access due to concurrent update")));
-					if (ItemPointerIndicatesMovedPartitions(&hufd.ctid))
-						ereport(ERROR,
-								(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
-								 errmsg("tuple to be locked was already moved to another partition due to concurrent update")));
-
-#if 0 //hari
-					/* Should not encounter speculative tuple on recheck */
-					Assert(!HeapTupleHeaderIsSpeculative(tuple.t_data));
-#endif
-					t_data = table_tuple_get_data(relation, tuple, TID);
-					if (!ItemPointerEquals(&hufd.ctid, &t_data.tid))
-					{
-						/* it was updated, so look at the updated version */
-						*tid = hufd.ctid;
-						/* updated row should have xmin matching this xmax */
-						priorXmax = hufd.xmax;
-						continue;
-					}
-					/* tuple was deleted, so give up */
-					return NULL;
-
-				case HeapTupleWouldBlock:
-					ReleaseBuffer(buffer);
-					return NULL;
-
-				case HeapTupleInvisible:
-					elog(ERROR, "attempted to lock invisible tuple");
-
-				default:
-					ReleaseBuffer(buffer);
-					elog(ERROR, "unrecognized heap_lock_tuple status: %u",
-						 test);
-					return NULL;	/* keep compiler quiet */
-			}
-
-			ReleaseBuffer(buffer);
-			break;
-		}
-
-		/*
-		 * If the referenced slot was actually empty, the latest version of
-		 * the row must have been deleted, so we need do nothing.
-		 */
-		if (tuple == NULL)
-		{
-			ReleaseBuffer(buffer);
-			return NULL;
-		}
-
-		/*
-		 * As above, if xmin isn't what we're expecting, do nothing.
-		 */
-		if (!TransactionIdEquals(HeapTupleHeaderGetXmin(((HeapTuple) tuple)->t_data),
-								 priorXmax))
-		{
-			ReleaseBuffer(buffer);
-			return NULL;
-		}
-
-		/*
-		 * If we get here, the tuple was found but failed SnapshotDirty.
-		 * Assuming the xmin is either a committed xact or our own xact (as it
-		 * certainly should be if we're trying to modify the tuple), this must
-		 * mean that the row was updated or deleted by either a committed xact
-		 * or our own xact.  If it was deleted, we can ignore it; if it was
-		 * updated then chain up to the next version and repeat the whole
-		 * process.
-		 *
-		 * As above, it should be safe to examine xmax and t_ctid without the
-		 * buffer content lock, because they can't be changing.
-		 */
-
-		/* check whether next version would be in a different partition */
-		/* hari: FIXME: use a new table API */
-		if (HeapTupleHeaderIndicatesMovedPartitions(((HeapTuple) tuple)->t_data))
-			ereport(ERROR,
-					(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
-					 errmsg("tuple to be locked was already moved to another partition due to concurrent update")));
-
-		t_data = table_tuple_get_data(relation, tuple, CTID);
-		ctid = t_data.tid;
-
-		/* check whether tuple has been deleted */
-		if (ItemPointerEquals(tid, &ctid))
-		{
-			/* deleted, so forget about it */
-			ReleaseBuffer(buffer);
-			return NULL;
-		}
-
-		/* updated, so look at the updated row */
-		*tid = ctid;
-
-		/* updated row should have xmin matching this xmax */
-		t_data = table_tuple_get_data(relation, tuple, UPDATED_XID);
-		priorXmax = t_data.xid;
-		ReleaseBuffer(buffer);
-		/* loop back to fetch next in chain */
-	}
-
-	/*
-	 * Return the tuple
-	 */
-	return tuple;
-}
-
 /*
  * EvalPlanQualInit -- initialize during creation of a plan state node
  * that might need to invoke EPQ processing.
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 5c62d6c760..8480044cd2 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -167,21 +167,19 @@ retry:
 	/* Found tuple, try to lock it in the lockmode. */
 	if (found)
 	{
-		Buffer		buf;
 		HeapUpdateFailureData hufd;
 		HTSU_Result res;
 		TableTuple locktup;
 
 		PushActiveSnapshot(GetLatestSnapshot());
 
-		res = table_lock_tuple(rel, &(outslot->tts_tid), &locktup, GetCurrentCommandId(false),
+		res = table_lock_tuple(rel, &(outslot->tts_tid), GetLatestSnapshot(),
+								 &locktup,
+								 GetCurrentCommandId(false),
 								 lockmode,
 								 LockWaitBlock,
-								 false /* don't follow updates */ ,
-								 &buf, &hufd);
-		/* the tuple slot already has the buffer pinned */
-		if (BufferIsValid(buf))
-			ReleaseBuffer(buf);
+								 0 /* don't follow updates */ ,
+								 &hufd);
 		pfree(locktup);
 
 		PopActiveSnapshot();
@@ -201,6 +199,12 @@ retry:
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("concurrent update, retrying")));
 				goto retry;
+			case HeapTupleDeleted:
+				/* XXX: Improve handling here */
+				ereport(LOG,
+						(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+						 errmsg("concurrent delete, retrying")));
+				goto retry;
 			case HeapTupleInvisible:
 				elog(ERROR, "attempted to lock invisible tuple");
 			default:
@@ -279,21 +283,19 @@ retry:
 	/* Found tuple, try to lock it in the lockmode. */
 	if (found)
 	{
-		Buffer		buf;
 		HeapUpdateFailureData hufd;
 		HTSU_Result res;
 		TableTuple locktup;
 
 		PushActiveSnapshot(GetLatestSnapshot());
 
-		res = table_lock_tuple(rel, &(outslot->tts_tid), &locktup, GetCurrentCommandId(false),
+		res = table_lock_tuple(rel, &(outslot->tts_tid), GetLatestSnapshot(),
+								 &locktup,
+								 GetCurrentCommandId(false),
 								 lockmode,
 								 LockWaitBlock,
-								 false /* don't follow updates */ ,
-								 &buf, &hufd);
-		/* the tuple slot already has the buffer pinned */
-		if (BufferIsValid(buf))
-			ReleaseBuffer(buf);
+								 0 /* don't follow updates */ ,
+								 &hufd);
 
 		pfree(locktup);
 
@@ -314,6 +316,12 @@ retry:
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("concurrent update, retrying")));
 				goto retry;
+			case HeapTupleDeleted:
+				/* XXX: Improve handling here */
+				ereport(LOG,
+						(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+						 errmsg("concurrent delete, retrying")));
+				goto retry;
 			case HeapTupleInvisible:
 				elog(ERROR, "attempted to lock invisible tuple");
 			default:
diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c
index 6dff5eeadb..f5417c1db0 100644
--- a/src/backend/executor/nodeLockRows.c
+++ b/src/backend/executor/nodeLockRows.c
@@ -79,13 +79,11 @@ lnext:
 		Datum		datum;
 		bool		isNull;
 		TableTuple tuple;
-		Buffer		buffer;
 		HeapUpdateFailureData hufd;
 		LockTupleMode lockmode;
 		HTSU_Result test;
 		TableTuple copyTuple;
 		ItemPointerData tid;
-		tuple_data	t_data;
 
 		/* clear any leftover test tuple for this rel */
 		testTuple = (TableTuple) (&(node->lr_curtuples[erm->rti - 1]));
@@ -183,12 +181,12 @@ lnext:
 				break;
 		}
 
-		test = table_lock_tuple(erm->relation, &tid, &tuple,
-								  estate->es_output_cid,
-								  lockmode, erm->waitPolicy, true,
-								  &buffer, &hufd);
-		if (BufferIsValid(buffer))
-			ReleaseBuffer(buffer);
+		test = table_lock_tuple(erm->relation, &tid, estate->es_snapshot,
+								  &tuple, estate->es_output_cid,
+								  lockmode, erm->waitPolicy,
+								  (IsolationUsesXactSnapshot() ? 0 : TUPLE_LOCK_FLAG_FIND_LAST_VERSION)
+								  | TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS,
+								  &hufd);
 
 		switch (test)
 		{
@@ -216,6 +214,16 @@ lnext:
 
 			case HeapTupleMayBeUpdated:
 				/* got the lock successfully */
+				if (hufd.traversed)
+				{
+					/* Save locked tuple for EvalPlanQual testing below */
+					*testTuple = tuple;
+
+					/* Remember we need to do EPQ testing */
+					epq_needed = true;
+
+					/* Continue loop until we have all target tuples */
+				}
 				break;
 
 			case HeapTupleUpdated:
@@ -223,43 +231,19 @@ lnext:
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("could not serialize access due to concurrent update")));
-				if (ItemPointerIndicatesMovedPartitions(&hufd.ctid))
+				/* skip lock */
+				goto lnext;
+
+			case HeapTupleDeleted:
+				if (IsolationUsesXactSnapshot())
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
-							 errmsg("tuple to be locked was already moved to another partition due to concurrent update")));
-
-				t_data = erm->relation->rd_tableamroutine->get_tuple_data(tuple, TID);
-				if (ItemPointerEquals(&hufd.ctid, &(t_data.tid)))
-				{
-					/* Tuple was deleted, so don't return it */
-					goto lnext;
-				}
-
-				/* updated, so fetch and lock the updated version */
-				copyTuple = EvalPlanQualFetch(estate, erm->relation,
-											  lockmode, erm->waitPolicy,
-											  &hufd.ctid, hufd.xmax);
-
-				if (copyTuple == NULL)
-				{
-					/*
-					 * Tuple was deleted; or it's locked and we're under SKIP
-					 * LOCKED policy, so don't return it
-					 */
-					goto lnext;
-				}
-				/* remember the actually locked tuple's TID */
-				t_data = erm->relation->rd_tableamroutine->get_tuple_data(copyTuple, TID);
-				tid = t_data.tid;
-
-				/* Save locked tuple for EvalPlanQual testing below */
-				*testTuple = copyTuple;
-
-				/* Remember we need to do EPQ testing */
-				epq_needed = true;
-
-				/* Continue loop until we have all target tuples */
-				break;
+							 errmsg("could not serialize access due to concurrent update")));
+				/*
+				 * Tuple was deleted; or it's locked and we're under SKIP
+				 * LOCKED policy, so don't return it
+				 */
+				goto lnext;
 
 			case HeapTupleInvisible:
 				elog(ERROR, "attempted to lock invisible tuple");
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index b87fd90d19..0633a27d78 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -211,7 +211,8 @@ ExecCheckHeapTupleVisible(EState *estate,
 	 * We need buffer pin and lock to call HeapTupleSatisfiesVisibility.
 	 * Caller should be holding pin, but not lock.
 	 */
-	LockBuffer(buffer, BUFFER_LOCK_SHARE);
+	if (BufferIsValid(buffer))
+		LockBuffer(buffer, BUFFER_LOCK_SHARE);
 	if (!HeapTupleSatisfiesVisibility(rel->rd_tableamroutine, tuple, estate->es_snapshot, buffer))
 	{
 		tuple_data	t_data = table_tuple_get_data(rel, tuple, XMIN);
@@ -227,7 +228,8 @@ ExecCheckHeapTupleVisible(EState *estate,
 					(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 					 errmsg("could not serialize access due to concurrent update")));
 	}
-	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+	if (BufferIsValid(buffer))
+		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
 }
 
 /*
@@ -623,6 +625,7 @@ ExecDelete(ModifyTableState *mtstate,
 	HeapUpdateFailureData hufd;
 	TupleTableSlot *slot = NULL;
 	TransitionCaptureState *ar_delete_trig_tcs;
+	TableTuple	tuple;
 
 	if (tupleDeleted)
 		*tupleDeleted = false;
@@ -709,6 +712,35 @@ ldelete:;
 							 NULL,
 							 &hufd,
 							 changingPart);
+
+		if (result == HeapTupleUpdated && !IsolationUsesXactSnapshot())
+		{
+			result = table_lock_tuple(resultRelationDesc, tupleid,
+										estate->es_snapshot,
+										&tuple, estate->es_output_cid,
+										LockTupleExclusive, LockWaitBlock,
+										TUPLE_LOCK_FLAG_FIND_LAST_VERSION,
+										&hufd);
+			/*hari FIXME*/
+			/*Assert(result != HeapTupleUpdated && hufd.traversed);*/
+			if (result == HeapTupleMayBeUpdated)
+			{
+				TupleTableSlot *epqslot;
+
+				epqslot = EvalPlanQual(estate,
+									   epqstate,
+									   resultRelationDesc,
+									   resultRelInfo->ri_RangeTableIndex,
+									   tuple);
+				if (TupIsNull(epqslot))
+				{
+					/* Tuple no more passing quals, exiting... */
+					return NULL;
+				}
+				goto ldelete;
+			}
+		}
+
 		switch (result)
 		{
 			case HeapTupleSelfUpdated:
@@ -754,28 +786,16 @@ ldelete:;
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("could not serialize access due to concurrent update")));
-				if (ItemPointerIndicatesMovedPartitions(&hufd.ctid))
+				else
+					/* shouldn't get there */
+					elog(ERROR, "wrong heap_delete status: %u", result);
+				break;
+
+			case HeapTupleDeleted:
+				if (IsolationUsesXactSnapshot())
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
-							 errmsg("tuple to be deleted was already moved to another partition due to concurrent update")));
-
-				if (!ItemPointerEquals(tupleid, &hufd.ctid))
-				{
-					TupleTableSlot *epqslot;
-
-					epqslot = EvalPlanQual(estate,
-										   epqstate,
-										   resultRelationDesc,
-										   resultRelInfo->ri_RangeTableIndex,
-										   LockTupleExclusive,
-										   &hufd.ctid,
-										   hufd.xmax);
-					if (!TupIsNull(epqslot))
-					{
-						*tupleid = hufd.ctid;
-						goto ldelete;
-					}
-				}
+							 errmsg("could not serialize access due to concurrent delete")));
 				/* tuple already deleted; nothing to do */
 				return NULL;
 
@@ -1175,6 +1195,37 @@ lreplace:;
 								&hufd, &lockmode,
 								ExecInsertIndexTuples,
 								&recheckIndexes);
+
+		if (result == HeapTupleUpdated && !IsolationUsesXactSnapshot())
+		{
+			result = table_lock_tuple(resultRelationDesc, tupleid,
+										estate->es_snapshot,
+										&tuple, estate->es_output_cid,
+										lockmode, LockWaitBlock,
+										TUPLE_LOCK_FLAG_FIND_LAST_VERSION,
+										&hufd);
+			/* hari FIXME*/
+			/*Assert(result != HeapTupleUpdated && hufd.traversed);*/
+			if (result == HeapTupleMayBeUpdated)
+			{
+				TupleTableSlot *epqslot;
+
+				epqslot = EvalPlanQual(estate,
+									   epqstate,
+									   resultRelationDesc,
+									   resultRelInfo->ri_RangeTableIndex,
+									   tuple);
+				if (TupIsNull(epqslot))
+				{
+					/* Tuple no more passing quals, exiting... */
+					return NULL;
+				}
+				slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
+				tuple = ExecHeapifySlot(slot);
+				goto lreplace;
+			}
+		}
+
 		switch (result)
 		{
 			case HeapTupleSelfUpdated:
@@ -1219,30 +1270,16 @@ lreplace:;
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("could not serialize access due to concurrent update")));
-				if (ItemPointerIndicatesMovedPartitions(&hufd.ctid))
+				else
+					/* shouldn't get there */
+					elog(ERROR, "wrong heap_delete status: %u", result);
+				break;
+
+			case HeapTupleDeleted:
+				if (IsolationUsesXactSnapshot())
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
-							 errmsg("tuple to be updated was already moved to another partition due to concurrent update")));
-
-				if (!ItemPointerEquals(tupleid, &hufd.ctid))
-				{
-					TupleTableSlot *epqslot;
-
-					epqslot = EvalPlanQual(estate,
-										   epqstate,
-										   resultRelationDesc,
-										   resultRelInfo->ri_RangeTableIndex,
-										   lockmode,
-										   &hufd.ctid,
-										   hufd.xmax);
-					if (!TupIsNull(epqslot))
-					{
-						*tupleid = hufd.ctid;
-						slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
-						tuple = ExecHeapifySlot(slot);
-						goto lreplace;
-					}
-				}
+							 errmsg("could not serialize access due to concurrent delete")));
 				/* tuple already deleted; nothing to do */
 				return NULL;
 
@@ -1311,8 +1348,8 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	HeapUpdateFailureData hufd;
 	LockTupleMode lockmode;
 	HTSU_Result test;
-	Buffer		buffer;
 	tuple_data	t_data;
+	SnapshotData	snapshot;
 
 	/* Determine lock mode to use */
 	lockmode = ExecUpdateLockMode(estate, resultRelInfo);
@@ -1323,8 +1360,12 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	 * previous conclusion that the tuple is conclusively committed is not
 	 * true anymore.
 	 */
-	test = table_lock_tuple(relation, conflictTid, &tuple, estate->es_output_cid,
-							  lockmode, LockWaitBlock, false, &buffer, &hufd);
+	InitDirtySnapshot(snapshot);
+	test = table_lock_tuple(relation, conflictTid,
+							  &snapshot,
+							  /*estate->es_snapshot,*/
+							  &tuple, estate->es_output_cid,
+							  lockmode, LockWaitBlock, 0, &hufd);
 	switch (test)
 	{
 		case HeapTupleMayBeUpdated:
@@ -1389,8 +1430,15 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 			 * loop here, as the new version of the row might not conflict
 			 * anymore, or the conflicting tuple has actually been deleted.
 			 */
-			if (BufferIsValid(buffer))
-				ReleaseBuffer(buffer);
+			pfree(tuple);
+			return false;
+
+		case HeapTupleDeleted:
+			if (IsolationUsesXactSnapshot())
+				ereport(ERROR,
+						(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+						 errmsg("could not serialize access due to concurrent delete")));
+
 			pfree(tuple);
 			return false;
 
@@ -1419,10 +1467,10 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	 * snapshot.  This is in line with the way UPDATE deals with newer tuple
 	 * versions.
 	 */
-	ExecCheckHeapTupleVisible(estate, relation, tuple, buffer);
+	ExecCheckHeapTupleVisible(estate, relation, tuple, InvalidBuffer);
 
 	/* Store target's existing tuple in the state's dedicated slot */
-	ExecStoreTuple(tuple, mtstate->mt_existing, buffer, false);
+	ExecStoreTuple(tuple, mtstate->mt_existing, InvalidBuffer, false);
 
 	/*
 	 * Make tuple and any needed join variables available to ExecQual and
@@ -1437,8 +1485,6 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 
 	if (!ExecQual(onConflictSetWhere, econtext))
 	{
-		if (BufferIsValid(buffer))
-			ReleaseBuffer(buffer);
 		pfree(tuple);
 		InstrCountFiltered1(&mtstate->ps, 1);
 		return true;			/* done with the tuple */
@@ -1484,8 +1530,6 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 							&mtstate->mt_epqstate, mtstate->ps.state,
 							canSetTag);
 
-	if (BufferIsValid(buffer))
-		ReleaseBuffer(buffer);
 	pfree(tuple);
 	return true;
 }
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 13658f9e93..26d882eb00 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -69,6 +69,7 @@ typedef struct HeapUpdateFailureData
 	ItemPointerData ctid;
 	TransactionId xmax;
 	CommandId	cmax;
+	bool		traversed;
 } HeapUpdateFailureData;
 
 
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 77d08eae14..7227f834a5 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -87,10 +87,10 @@ extern bool table_hot_search_buffer(ItemPointer tid, Relation relation, Buffer b
 extern bool table_hot_search(ItemPointer tid, Relation relation, Snapshot snapshot,
 				   bool *all_dead);
 
-extern HTSU_Result table_lock_tuple(Relation relation, ItemPointer tid, TableTuple * stuple,
-				   CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
-				   bool follow_updates,
-				   Buffer *buffer, HeapUpdateFailureData *hufd);
+extern HTSU_Result table_lock_tuple(Relation relation, ItemPointer tid, Snapshot snapshot,
+				   TableTuple *stuple, CommandId cid, LockTupleMode mode,
+				   LockWaitPolicy wait_policy, uint8 flags,
+				   HeapUpdateFailureData *hufd);
 
 extern Oid table_insert(Relation relation, TupleTableSlot *slot, CommandId cid,
 			   int options, BulkInsertState bistate, InsertIndexTuples IndexFunc,
diff --git a/src/include/access/tableamapi.h b/src/include/access/tableamapi.h
index f6fae31449..4897f1407e 100644
--- a/src/include/access/tableamapi.h
+++ b/src/include/access/tableamapi.h
@@ -61,12 +61,12 @@ typedef bool (*TupleFetch_function) (Relation relation,
 
 typedef HTSU_Result (*TupleLock_function) (Relation relation,
 										   ItemPointer tid,
+										   Snapshot snapshot,
 										   TableTuple * tuple,
 										   CommandId cid,
 										   LockTupleMode mode,
 										   LockWaitPolicy wait_policy,
-										   bool follow_update,
-										   Buffer *buffer,
+										   uint8 flags,
 										   HeapUpdateFailureData *hufd);
 
 typedef void (*MultiInsert_function) (Relation relation, HeapTuple *tuples, int ntuples,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index d754daf441..20fce56c94 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -192,11 +192,7 @@ extern LockTupleMode ExecUpdateLockMode(EState *estate, ResultRelInfo *relinfo);
 extern ExecRowMark *ExecFindRowMark(EState *estate, Index rti, bool missing_ok);
 extern ExecAuxRowMark *ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist);
 extern TupleTableSlot *EvalPlanQual(EState *estate, EPQState *epqstate,
-			 Relation relation, Index rti, int lockmode,
-			 ItemPointer tid, TransactionId priorXmax);
-extern TableTuple EvalPlanQualFetch(EState *estate, Relation relation,
-									  int lockmode, LockWaitPolicy wait_policy, ItemPointer tid,
-									  TransactionId priorXmax);
+			 Relation relation, Index rti, TableTuple tuple);
 extern void EvalPlanQualInit(EPQState *epqstate, EState *estate,
 				 Plan *subplan, List *auxrowmarks, int epqParam);
 extern void EvalPlanQualSetPlan(EPQState *epqstate,
diff --git a/src/include/nodes/lockoptions.h b/src/include/nodes/lockoptions.h
index 24afd6efd4..bcde234614 100644
--- a/src/include/nodes/lockoptions.h
+++ b/src/include/nodes/lockoptions.h
@@ -43,4 +43,9 @@ typedef enum LockWaitPolicy
 	LockWaitError
 } LockWaitPolicy;
 
+/* Follow tuples whose update is in progress if lock modes don't conflict  */
+#define TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS	0x01
+/* Follow update chain and lock lastest version of tuple */
+#define TUPLE_LOCK_FLAG_FIND_LAST_VERSION		0x02
+
 #endif							/* LOCKOPTIONS_H */
diff --git a/src/include/utils/snapshot.h b/src/include/utils/snapshot.h
index ca96fd00fa..95a91db03c 100644
--- a/src/include/utils/snapshot.h
+++ b/src/include/utils/snapshot.h
@@ -136,6 +136,7 @@ typedef enum
 	HeapTupleInvisible,
 	HeapTupleSelfUpdated,
 	HeapTupleUpdated,
+	HeapTupleDeleted,
 	HeapTupleBeingUpdated,
 	HeapTupleWouldBlock			/* can be returned by heap_tuple_lock */
 } HTSU_Result;
diff --git a/src/test/isolation/expected/partition-key-update-1.out b/src/test/isolation/expected/partition-key-update-1.out
index 37fe6a7b27..a632d7f7ba 100644
--- a/src/test/isolation/expected/partition-key-update-1.out
+++ b/src/test/isolation/expected/partition-key-update-1.out
@@ -15,7 +15,7 @@ step s1u: UPDATE foo SET a=2 WHERE a=1;
 step s2d: DELETE FROM foo WHERE a=1; <waiting ...>
 step s1c: COMMIT;
 step s2d: <... completed>
-error in steps s1c s2d: ERROR:  tuple to be deleted was already moved to another partition due to concurrent update
+error in steps s1c s2d: ERROR:  tuple to be locked was already moved to another partition due to concurrent update
 step s2c: COMMIT;
 
 starting permutation: s1b s2b s2d s1u s2c s1c
-- 
2.16.1.windows.4

