From cff9a17e02e98f4bdfadfd251174605588c46ea2 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Wed, 11 Jan 2023 15:00:49 +0300
Subject: [PATCH 1/2] Evade extra table_tuple_fetch_row_version() in
 ExecUpdate()/ExecDelete()

We can skip table_tuple_fetch_row_version() in the case we already fetched the
tuple during locking it.

Reported-by:
Bug:
Discussion:
Author:
Reviewed-by:
Tested-by:
Backpatch-through:
---
 src/backend/executor/nodeModifyTable.c | 46 ++++++++++++++++++--------
 1 file changed, 33 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 687a5422eab..87a4e553b9e 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1561,6 +1561,21 @@ ldelete:
 					{
 						case TM_Ok:
 							Assert(context->tmfd.traversed);
+
+							/*
+							 * Save locked tuple for further processing of
+							 * RETURNING clause.
+							 */
+							if (processReturning &&
+								resultRelInfo->ri_projectReturning &&
+								!resultRelInfo->ri_FdwRoutine)
+							{
+								TupleTableSlot *returningSlot;
+								returningSlot = ExecGetReturningSlot(estate, resultRelInfo);
+								ExecCopySlot(returningSlot, inputslot);
+								ExecMaterializeSlot(returningSlot);
+							}
+
 							epqslot = EvalPlanQual(context->epqstate,
 												   resultRelationDesc,
 												   resultRelInfo->ri_RangeTableIndex,
@@ -1675,12 +1690,16 @@ ldelete:
 		}
 		else
 		{
+			/*
+			 * Tuple can be already fetched to the returning slot in case we've
+			 * previously locked it.  Fetch the tuple only if the slot is empty.
+			 */
 			slot = ExecGetReturningSlot(estate, resultRelInfo);
 			if (oldtuple != NULL)
 			{
 				ExecForceStoreHeapTuple(oldtuple, slot, false);
 			}
-			else
+			else if (TupIsNull(slot))
 			{
 				if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid,
 												   SnapshotAny, slot))
@@ -2377,6 +2396,19 @@ redo_act:
 						case TM_Ok:
 							Assert(context->tmfd.traversed);
 
+							/* Make sure ri_oldTupleSlot is initialized. */
+							if (unlikely(!resultRelInfo->ri_projectNewInfoValid))
+								ExecInitUpdateProjection(context->mtstate,
+														 resultRelInfo);
+
+							/*
+							 * Save the locked tuple for further calculation of
+							 * the new tuple.
+							 */
+							oldSlot = resultRelInfo->ri_oldTupleSlot;
+							ExecCopySlot(oldSlot, inputslot);
+							ExecMaterializeSlot(oldSlot);
+
 							epqslot = EvalPlanQual(context->epqstate,
 												   resultRelationDesc,
 												   resultRelInfo->ri_RangeTableIndex,
@@ -2385,18 +2417,6 @@ redo_act:
 								/* Tuple not passing quals anymore, exiting... */
 								return NULL;
 
-							/* Make sure ri_oldTupleSlot is initialized. */
-							if (unlikely(!resultRelInfo->ri_projectNewInfoValid))
-								ExecInitUpdateProjection(context->mtstate,
-														 resultRelInfo);
-
-							/* Fetch the most recent version of old tuple. */
-							oldSlot = resultRelInfo->ri_oldTupleSlot;
-							if (!table_tuple_fetch_row_version(resultRelationDesc,
-															   tupleid,
-															   SnapshotAny,
-															   oldSlot))
-								elog(ERROR, "failed to fetch tuple being updated");
 							slot = ExecGetUpdateNewTuple(resultRelInfo,
 														 epqslot, oldSlot);
 							goto redo_act;
-- 
2.37.1 (Apple Git-137.1)

