Hi all,

I have been looking at the ODBC driver and the need for currtid() as
well as currtid2(), and as mentioned already in [1], matching with my
lookup of things, these are actually not needed by the driver as long
as we connect to a server newer than 8.2 able to support RETURNING.  I
am adding in CC of this thread Saito-san and Inoue-san who are the
two main maintainers of the driver for comments.  It is worth noting
that on its latest HEAD the ODBC driver requires libpq from at least
9.2.

I would like to remove those two functions and the surrounding code
for v14, leading to some cleanup:
 6 files changed, 326 deletions(-)

While on it, I have noticed that heap_get_latest_tid() is still
located within heapam.c, but we can just move it within
heapam_handler.c.

Attached are two patches to address both points.  Comments are
welcome.

Thanks,

[1]: 
https://www.postgresql.org/message-id/20200529005559.jl2gsolomyro4...@alap3.anarazel.de
--
Michael
From 4afe881f449be8b51ff130563195e9ed73860a8c Mon Sep 17 00:00:00 2001
From: Michael Paquier <mich...@paquier.xyz>
Date: Wed, 3 Jun 2020 10:49:41 +0900
Subject: [PATCH 1/2] Remove currtid() and currtid2()

Those two functions have been used within the Postgres ODBC driver in
the old days, requiring them when connecting to Postgres 8.1 or older.
In 8.2, support for RETURNING has allowed the driver to fetch ctids
by directly using this clause.
---
 src/include/access/heapam.h            |   1 -
 src/include/catalog/pg_proc.dat        |   7 -
 src/backend/executor/nodeModifyTable.c |   1 -
 src/backend/utils/adt/tid.c            | 177 -------------------------
 src/test/regress/expected/tid.out      |  88 ------------
 src/test/regress/sql/tid.sql           |  52 --------
 6 files changed, 326 deletions(-)

diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index f279edc473..4a5b14f10e 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -129,7 +129,6 @@ extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation,
 								   bool *all_dead, bool first_call);
 
 extern void heap_get_latest_tid(TableScanDesc scan, ItemPointer tid);
-extern void setLastTid(const ItemPointer tid);
 
 extern BulkInsertState GetBulkInsertState(void);
 extern void FreeBulkInsertState(BulkInsertState);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 61f2c2f5b4..92aa44c628 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -2533,13 +2533,6 @@
 { oid => '1292',
   proname => 'tideq', proleakproof => 't', prorettype => 'bool',
   proargtypes => 'tid tid', prosrc => 'tideq' },
-{ oid => '1293', descr => 'latest tid of a tuple',
-  proname => 'currtid', provolatile => 'v', proparallel => 'u',
-  prorettype => 'tid', proargtypes => 'oid tid', prosrc => 'currtid_byreloid' },
-{ oid => '1294', descr => 'latest tid of a tuple',
-  proname => 'currtid2', provolatile => 'v', proparallel => 'u',
-  prorettype => 'tid', proargtypes => 'text tid',
-  prosrc => 'currtid_byrelname' },
 { oid => '1265',
   proname => 'tidne', proleakproof => 't', prorettype => 'bool',
   proargtypes => 'tid tid', prosrc => 'tidne' },
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 20a4c474cc..fc53a12d56 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -629,7 +629,6 @@ ExecInsert(ModifyTableState *mtstate,
 	if (canSetTag)
 	{
 		(estate->es_processed)++;
-		setLastTid(&slot->tts_tid);
 	}
 
 	/*
diff --git a/src/backend/utils/adt/tid.c b/src/backend/utils/adt/tid.c
index 509a0fdffc..29dddb20e6 100644
--- a/src/backend/utils/adt/tid.c
+++ b/src/backend/utils/adt/tid.c
@@ -267,180 +267,3 @@ hashtidextended(PG_FUNCTION_ARGS)
 							 sizeof(BlockIdData) + sizeof(OffsetNumber),
 							 seed);
 }
-
-
-/*
- *	Functions to get latest tid of a specified tuple.
- *
- *	Maybe these implementations should be moved to another place
- */
-
-static ItemPointerData Current_last_tid = {{0, 0}, 0};
-
-void
-setLastTid(const ItemPointer tid)
-{
-	Current_last_tid = *tid;
-}
-
-/*
- *	Handle CTIDs of views.
- *		CTID should be defined in the view and it must
- *		correspond to the CTID of a base relation.
- */
-static Datum
-currtid_for_view(Relation viewrel, ItemPointer tid)
-{
-	TupleDesc	att = RelationGetDescr(viewrel);
-	RuleLock   *rulelock;
-	RewriteRule *rewrite;
-	int			i,
-				natts = att->natts,
-				tididx = -1;
-
-	for (i = 0; i < natts; i++)
-	{
-		Form_pg_attribute attr = TupleDescAttr(att, i);
-
-		if (strcmp(NameStr(attr->attname), "ctid") == 0)
-		{
-			if (attr->atttypid != TIDOID)
-				elog(ERROR, "ctid isn't of type TID");
-			tididx = i;
-			break;
-		}
-	}
-	if (tididx < 0)
-		elog(ERROR, "currtid cannot handle views with no CTID");
-	rulelock = viewrel->rd_rules;
-	if (!rulelock)
-		elog(ERROR, "the view has no rules");
-	for (i = 0; i < rulelock->numLocks; i++)
-	{
-		rewrite = rulelock->rules[i];
-		if (rewrite->event == CMD_SELECT)
-		{
-			Query	   *query;
-			TargetEntry *tle;
-
-			if (list_length(rewrite->actions) != 1)
-				elog(ERROR, "only one select rule is allowed in views");
-			query = (Query *) linitial(rewrite->actions);
-			tle = get_tle_by_resno(query->targetList, tididx + 1);
-			if (tle && tle->expr && IsA(tle->expr, Var))
-			{
-				Var		   *var = (Var *) tle->expr;
-				RangeTblEntry *rte;
-
-				if (!IS_SPECIAL_VARNO(var->varno) &&
-					var->varattno == SelfItemPointerAttributeNumber)
-				{
-					rte = rt_fetch(var->varno, query->rtable);
-					if (rte)
-					{
-						Datum		result;
-
-						result = DirectFunctionCall2(currtid_byreloid,
-													 ObjectIdGetDatum(rte->relid),
-													 PointerGetDatum(tid));
-						table_close(viewrel, AccessShareLock);
-						return result;
-					}
-				}
-			}
-			break;
-		}
-	}
-	elog(ERROR, "currtid cannot handle this view");
-	return (Datum) 0;
-}
-
-Datum
-currtid_byreloid(PG_FUNCTION_ARGS)
-{
-	Oid			reloid = PG_GETARG_OID(0);
-	ItemPointer tid = PG_GETARG_ITEMPOINTER(1);
-	ItemPointer result;
-	Relation	rel;
-	AclResult	aclresult;
-	Snapshot	snapshot;
-	TableScanDesc scan;
-
-	result = (ItemPointer) palloc(sizeof(ItemPointerData));
-	if (!reloid)
-	{
-		*result = Current_last_tid;
-		PG_RETURN_ITEMPOINTER(result);
-	}
-
-	rel = table_open(reloid, AccessShareLock);
-
-	aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
-								  ACL_SELECT);
-	if (aclresult != ACLCHECK_OK)
-		aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
-					   RelationGetRelationName(rel));
-
-	if (rel->rd_rel->relkind == RELKIND_VIEW)
-		return currtid_for_view(rel, tid);
-
-	if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
-		elog(ERROR, "cannot look at latest visible tid for relation \"%s.%s\"",
-			 get_namespace_name(RelationGetNamespace(rel)),
-			 RelationGetRelationName(rel));
-
-	ItemPointerCopy(tid, result);
-
-	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	scan = table_beginscan_tid(rel, snapshot);
-	table_tuple_get_latest_tid(scan, result);
-	table_endscan(scan);
-	UnregisterSnapshot(snapshot);
-
-	table_close(rel, AccessShareLock);
-
-	PG_RETURN_ITEMPOINTER(result);
-}
-
-Datum
-currtid_byrelname(PG_FUNCTION_ARGS)
-{
-	text	   *relname = PG_GETARG_TEXT_PP(0);
-	ItemPointer tid = PG_GETARG_ITEMPOINTER(1);
-	ItemPointer result;
-	RangeVar   *relrv;
-	Relation	rel;
-	AclResult	aclresult;
-	Snapshot	snapshot;
-	TableScanDesc scan;
-
-	relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
-	rel = table_openrv(relrv, AccessShareLock);
-
-	aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
-								  ACL_SELECT);
-	if (aclresult != ACLCHECK_OK)
-		aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
-					   RelationGetRelationName(rel));
-
-	if (rel->rd_rel->relkind == RELKIND_VIEW)
-		return currtid_for_view(rel, tid);
-
-	if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
-		elog(ERROR, "cannot look at latest visible tid for relation \"%s.%s\"",
-			 get_namespace_name(RelationGetNamespace(rel)),
-			 RelationGetRelationName(rel));
-
-	result = (ItemPointer) palloc(sizeof(ItemPointerData));
-	ItemPointerCopy(tid, result);
-
-	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	scan = table_beginscan_tid(rel, snapshot);
-	table_tuple_get_latest_tid(scan, result);
-	table_endscan(scan);
-	UnregisterSnapshot(snapshot);
-
-	table_close(rel, AccessShareLock);
-
-	PG_RETURN_ITEMPOINTER(result);
-}
diff --git a/src/test/regress/expected/tid.out b/src/test/regress/expected/tid.out
index e7e0d74780..f6971796f3 100644
--- a/src/test/regress/expected/tid.out
+++ b/src/test/regress/expected/tid.out
@@ -15,92 +15,4 @@ SELECT max(ctid) FROM tid_tab;
 (1 row)
 
 TRUNCATE tid_tab;
--- Tests for currtid() and currtid2() with various relation kinds
--- Materialized view
-CREATE MATERIALIZED VIEW tid_matview AS SELECT a FROM tid_tab;
-SELECT currtid('tid_matview'::regclass::oid, '(0,1)'::tid); -- fails
-ERROR:  tid (0, 1) is not valid for relation "tid_matview"
-SELECT currtid2('tid_matview'::text, '(0,1)'::tid); -- fails
-ERROR:  tid (0, 1) is not valid for relation "tid_matview"
-INSERT INTO tid_tab VALUES (1);
-REFRESH MATERIALIZED VIEW tid_matview;
-SELECT currtid('tid_matview'::regclass::oid, '(0,1)'::tid); -- ok
- currtid 
----------
- (0,1)
-(1 row)
-
-SELECT currtid2('tid_matview'::text, '(0,1)'::tid); -- ok
- currtid2 
-----------
- (0,1)
-(1 row)
-
-DROP MATERIALIZED VIEW tid_matview;
-TRUNCATE tid_tab;
--- Sequence
-CREATE SEQUENCE tid_seq;
-SELECT currtid('tid_seq'::regclass::oid, '(0,1)'::tid); -- ok
- currtid 
----------
- (0,1)
-(1 row)
-
-SELECT currtid2('tid_seq'::text, '(0,1)'::tid); -- ok
- currtid2 
-----------
- (0,1)
-(1 row)
-
-DROP SEQUENCE tid_seq;
--- Index, fails with incorrect relation type
-CREATE INDEX tid_ind ON tid_tab(a);
-SELECT currtid('tid_ind'::regclass::oid, '(0,1)'::tid); -- fails
-ERROR:  "tid_ind" is an index
-SELECT currtid2('tid_ind'::text, '(0,1)'::tid); -- fails
-ERROR:  "tid_ind" is an index
-DROP INDEX tid_ind;
--- Partitioned table, no storage
-CREATE TABLE tid_part (a int) PARTITION BY RANGE (a);
-SELECT currtid('tid_part'::regclass::oid, '(0,1)'::tid); -- fails
-ERROR:  cannot look at latest visible tid for relation "public.tid_part"
-SELECT currtid2('tid_part'::text, '(0,1)'::tid); -- fails
-ERROR:  cannot look at latest visible tid for relation "public.tid_part"
-DROP TABLE tid_part;
--- Views
--- ctid not defined in the view
-CREATE VIEW tid_view_no_ctid AS SELECT a FROM tid_tab;
-SELECT currtid('tid_view_no_ctid'::regclass::oid, '(0,1)'::tid); -- fails
-ERROR:  currtid cannot handle views with no CTID
-SELECT currtid2('tid_view_no_ctid'::text, '(0,1)'::tid); -- fails
-ERROR:  currtid cannot handle views with no CTID
-DROP VIEW tid_view_no_ctid;
--- ctid fetched directly from the source table.
-CREATE VIEW tid_view_with_ctid AS SELECT ctid, a FROM tid_tab;
-SELECT currtid('tid_view_with_ctid'::regclass::oid, '(0,1)'::tid); -- fails
-ERROR:  tid (0, 1) is not valid for relation "tid_tab"
-SELECT currtid2('tid_view_with_ctid'::text, '(0,1)'::tid); -- fails
-ERROR:  tid (0, 1) is not valid for relation "tid_tab"
-INSERT INTO tid_tab VALUES (1);
-SELECT currtid('tid_view_with_ctid'::regclass::oid, '(0,1)'::tid); -- ok
- currtid 
----------
- (0,1)
-(1 row)
-
-SELECT currtid2('tid_view_with_ctid'::text, '(0,1)'::tid); -- ok
- currtid2 
-----------
- (0,1)
-(1 row)
-
-DROP VIEW tid_view_with_ctid;
-TRUNCATE tid_tab;
--- ctid attribute with incorrect data type
-CREATE VIEW tid_view_fake_ctid AS SELECT 1 AS ctid, 2 AS a;
-SELECT currtid('tid_view_fake_ctid'::regclass::oid, '(0,1)'::tid); -- fails
-ERROR:  ctid isn't of type TID
-SELECT currtid2('tid_view_fake_ctid'::text, '(0,1)'::tid); -- fails
-ERROR:  ctid isn't of type TID
-DROP VIEW tid_view_fake_ctid;
 DROP TABLE tid_tab CASCADE;
diff --git a/src/test/regress/sql/tid.sql b/src/test/regress/sql/tid.sql
index c0d02df34f..cbf165fa99 100644
--- a/src/test/regress/sql/tid.sql
+++ b/src/test/regress/sql/tid.sql
@@ -8,56 +8,4 @@ SELECT min(ctid) FROM tid_tab;
 SELECT max(ctid) FROM tid_tab;
 TRUNCATE tid_tab;
 
--- Tests for currtid() and currtid2() with various relation kinds
-
--- Materialized view
-CREATE MATERIALIZED VIEW tid_matview AS SELECT a FROM tid_tab;
-SELECT currtid('tid_matview'::regclass::oid, '(0,1)'::tid); -- fails
-SELECT currtid2('tid_matview'::text, '(0,1)'::tid); -- fails
-INSERT INTO tid_tab VALUES (1);
-REFRESH MATERIALIZED VIEW tid_matview;
-SELECT currtid('tid_matview'::regclass::oid, '(0,1)'::tid); -- ok
-SELECT currtid2('tid_matview'::text, '(0,1)'::tid); -- ok
-DROP MATERIALIZED VIEW tid_matview;
-TRUNCATE tid_tab;
-
--- Sequence
-CREATE SEQUENCE tid_seq;
-SELECT currtid('tid_seq'::regclass::oid, '(0,1)'::tid); -- ok
-SELECT currtid2('tid_seq'::text, '(0,1)'::tid); -- ok
-DROP SEQUENCE tid_seq;
-
--- Index, fails with incorrect relation type
-CREATE INDEX tid_ind ON tid_tab(a);
-SELECT currtid('tid_ind'::regclass::oid, '(0,1)'::tid); -- fails
-SELECT currtid2('tid_ind'::text, '(0,1)'::tid); -- fails
-DROP INDEX tid_ind;
-
--- Partitioned table, no storage
-CREATE TABLE tid_part (a int) PARTITION BY RANGE (a);
-SELECT currtid('tid_part'::regclass::oid, '(0,1)'::tid); -- fails
-SELECT currtid2('tid_part'::text, '(0,1)'::tid); -- fails
-DROP TABLE tid_part;
-
--- Views
--- ctid not defined in the view
-CREATE VIEW tid_view_no_ctid AS SELECT a FROM tid_tab;
-SELECT currtid('tid_view_no_ctid'::regclass::oid, '(0,1)'::tid); -- fails
-SELECT currtid2('tid_view_no_ctid'::text, '(0,1)'::tid); -- fails
-DROP VIEW tid_view_no_ctid;
--- ctid fetched directly from the source table.
-CREATE VIEW tid_view_with_ctid AS SELECT ctid, a FROM tid_tab;
-SELECT currtid('tid_view_with_ctid'::regclass::oid, '(0,1)'::tid); -- fails
-SELECT currtid2('tid_view_with_ctid'::text, '(0,1)'::tid); -- fails
-INSERT INTO tid_tab VALUES (1);
-SELECT currtid('tid_view_with_ctid'::regclass::oid, '(0,1)'::tid); -- ok
-SELECT currtid2('tid_view_with_ctid'::text, '(0,1)'::tid); -- ok
-DROP VIEW tid_view_with_ctid;
-TRUNCATE tid_tab;
--- ctid attribute with incorrect data type
-CREATE VIEW tid_view_fake_ctid AS SELECT 1 AS ctid, 2 AS a;
-SELECT currtid('tid_view_fake_ctid'::regclass::oid, '(0,1)'::tid); -- fails
-SELECT currtid2('tid_view_fake_ctid'::text, '(0,1)'::tid); -- fails
-DROP VIEW tid_view_fake_ctid;
-
 DROP TABLE tid_tab CASCADE;
-- 
2.27.0.rc0

From 94f29c626633fa05050b702b00ff31c04de55de1 Mon Sep 17 00:00:00 2001
From: Michael Paquier <mich...@paquier.xyz>
Date: Wed, 3 Jun 2020 11:01:48 +0900
Subject: [PATCH 2/2] Move heap_get_latest_tid() within the heap AM handler

This routine is not getting called in any code path except through the
table AM interface, so let's make the cut clear.
---
 src/include/access/heapam.h              |   2 -
 src/backend/access/heap/heapam.c         | 117 -----------------------
 src/backend/access/heap/heapam_handler.c | 117 +++++++++++++++++++++++
 3 files changed, 117 insertions(+), 119 deletions(-)

diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 4a5b14f10e..273b8be02e 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -128,8 +128,6 @@ extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation,
 								   Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
 								   bool *all_dead, bool first_call);
 
-extern void heap_get_latest_tid(TableScanDesc scan, ItemPointer tid);
-
 extern BulkInsertState GetBulkInsertState(void);
 extern void FreeBulkInsertState(BulkInsertState);
 extern void ReleaseBulkInsertStatePin(BulkInsertState bistate);
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 94eb37d48d..e177065e20 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -1618,123 +1618,6 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
 	return false;
 }
 
-/*
- *	heap_get_latest_tid -  get the latest tid of a specified tuple
- *
- * Actually, this gets the latest version that is visible according to the
- * scan's snapshot.  Create a scan using SnapshotDirty to get the very latest,
- * possibly uncommitted version.
- *
- * *tid is both an input and an output parameter: it is updated to
- * show the latest version of the row.  Note that it will not be changed
- * if no version of the row passes the snapshot test.
- */
-void
-heap_get_latest_tid(TableScanDesc sscan,
-					ItemPointer tid)
-{
-	Relation	relation = sscan->rs_rd;
-	Snapshot	snapshot = sscan->rs_snapshot;
-	ItemPointerData ctid;
-	TransactionId priorXmax;
-
-	/*
-	 * table_get_latest_tid verified that the passed in tid is valid.  Assume
-	 * that t_ctid links are valid however - there shouldn't be invalid ones
-	 * in the table.
-	 */
-	Assert(ItemPointerIsValid(tid));
-
-	/*
-	 * Loop to chase down t_ctid links.  At top of loop, ctid is the tuple we
-	 * need to examine, and *tid is the TID we will return if ctid turns out
-	 * to be bogus.
-	 *
-	 * Note that we will loop until we reach the end of the t_ctid chain.
-	 * Depending on the snapshot passed, there might be at most one visible
-	 * version of the row, but we don't try to optimize for that.
-	 */
-	ctid = *tid;
-	priorXmax = InvalidTransactionId;	/* cannot check first XMIN */
-	for (;;)
-	{
-		Buffer		buffer;
-		Page		page;
-		OffsetNumber offnum;
-		ItemId		lp;
-		HeapTupleData tp;
-		bool		valid;
-
-		/*
-		 * Read, pin, and lock the page.
-		 */
-		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&ctid));
-		LockBuffer(buffer, BUFFER_LOCK_SHARE);
-		page = BufferGetPage(buffer);
-		TestForOldSnapshot(snapshot, relation, page);
-
-		/*
-		 * Check for bogus item number.  This is not treated as an error
-		 * condition because it can happen while following a t_ctid link. We
-		 * just assume that the prior tid is OK and return it unchanged.
-		 */
-		offnum = ItemPointerGetOffsetNumber(&ctid);
-		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
-		{
-			UnlockReleaseBuffer(buffer);
-			break;
-		}
-		lp = PageGetItemId(page, offnum);
-		if (!ItemIdIsNormal(lp))
-		{
-			UnlockReleaseBuffer(buffer);
-			break;
-		}
-
-		/* OK to access the tuple */
-		tp.t_self = ctid;
-		tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
-		tp.t_len = ItemIdGetLength(lp);
-		tp.t_tableOid = RelationGetRelid(relation);
-
-		/*
-		 * After following a t_ctid link, we might arrive at an unrelated
-		 * tuple.  Check for XMIN match.
-		 */
-		if (TransactionIdIsValid(priorXmax) &&
-			!TransactionIdEquals(priorXmax, HeapTupleHeaderGetXmin(tp.t_data)))
-		{
-			UnlockReleaseBuffer(buffer);
-			break;
-		}
-
-		/*
-		 * Check tuple visibility; if visible, set it as the new result
-		 * candidate.
-		 */
-		valid = HeapTupleSatisfiesVisibility(&tp, snapshot, buffer);
-		HeapCheckForSerializableConflictOut(valid, relation, &tp, buffer, snapshot);
-		if (valid)
-			*tid = ctid;
-
-		/*
-		 * If there's a valid t_ctid link, follow it, else we're done.
-		 */
-		if ((tp.t_data->t_infomask & HEAP_XMAX_INVALID) ||
-			HeapTupleHeaderIsOnlyLocked(tp.t_data) ||
-			HeapTupleHeaderIndicatesMovedPartitions(tp.t_data) ||
-			ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid))
-		{
-			UnlockReleaseBuffer(buffer);
-			break;
-		}
-
-		ctid = tp.t_data->t_ctid;
-		priorXmax = HeapTupleHeaderGetUpdateXid(tp.t_data);
-		UnlockReleaseBuffer(buffer);
-	}							/* end of loop */
-}
-
 
 /*
  * UpdateXmaxHintBits - update tuple hint bits after xmax transaction ends
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 56b35622f1..84d32bc4ff 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -199,6 +199,123 @@ heapam_fetch_row_version(Relation relation,
 	return false;
 }
 
+/*
+ *	heap_get_latest_tid -  get the latest tid of a specified tuple
+ *
+ * Actually, this gets the latest version that is visible according to the
+ * scan's snapshot.  Create a scan using SnapshotDirty to get the very latest,
+ * possibly uncommitted version.
+ *
+ * *tid is both an input and an output parameter: it is updated to
+ * show the latest version of the row.  Note that it will not be changed
+ * if no version of the row passes the snapshot test.
+ */
+static void
+heap_get_latest_tid(TableScanDesc sscan,
+					ItemPointer tid)
+{
+	Relation	relation = sscan->rs_rd;
+	Snapshot	snapshot = sscan->rs_snapshot;
+	ItemPointerData ctid;
+	TransactionId priorXmax;
+
+	/*
+	 * table_get_latest_tid verified that the passed in tid is valid.  Assume
+	 * that t_ctid links are valid however - there shouldn't be invalid ones
+	 * in the table.
+	 */
+	Assert(ItemPointerIsValid(tid));
+
+	/*
+	 * Loop to chase down t_ctid links.  At top of loop, ctid is the tuple we
+	 * need to examine, and *tid is the TID we will return if ctid turns out
+	 * to be bogus.
+	 *
+	 * Note that we will loop until we reach the end of the t_ctid chain.
+	 * Depending on the snapshot passed, there might be at most one visible
+	 * version of the row, but we don't try to optimize for that.
+	 */
+	ctid = *tid;
+	priorXmax = InvalidTransactionId;	/* cannot check first XMIN */
+	for (;;)
+	{
+		Buffer		buffer;
+		Page		page;
+		OffsetNumber offnum;
+		ItemId		lp;
+		HeapTupleData tp;
+		bool		valid;
+
+		/*
+		 * Read, pin, and lock the page.
+		 */
+		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&ctid));
+		LockBuffer(buffer, BUFFER_LOCK_SHARE);
+		page = BufferGetPage(buffer);
+		TestForOldSnapshot(snapshot, relation, page);
+
+		/*
+		 * Check for bogus item number.  This is not treated as an error
+		 * condition because it can happen while following a t_ctid link. We
+		 * just assume that the prior tid is OK and return it unchanged.
+		 */
+		offnum = ItemPointerGetOffsetNumber(&ctid);
+		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
+		{
+			UnlockReleaseBuffer(buffer);
+			break;
+		}
+		lp = PageGetItemId(page, offnum);
+		if (!ItemIdIsNormal(lp))
+		{
+			UnlockReleaseBuffer(buffer);
+			break;
+		}
+
+		/* OK to access the tuple */
+		tp.t_self = ctid;
+		tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+		tp.t_len = ItemIdGetLength(lp);
+		tp.t_tableOid = RelationGetRelid(relation);
+
+		/*
+		 * After following a t_ctid link, we might arrive at an unrelated
+		 * tuple.  Check for XMIN match.
+		 */
+		if (TransactionIdIsValid(priorXmax) &&
+			!TransactionIdEquals(priorXmax, HeapTupleHeaderGetXmin(tp.t_data)))
+		{
+			UnlockReleaseBuffer(buffer);
+			break;
+		}
+
+		/*
+		 * Check tuple visibility; if visible, set it as the new result
+		 * candidate.
+		 */
+		valid = HeapTupleSatisfiesVisibility(&tp, snapshot, buffer);
+		HeapCheckForSerializableConflictOut(valid, relation, &tp, buffer, snapshot);
+		if (valid)
+			*tid = ctid;
+
+		/*
+		 * If there's a valid t_ctid link, follow it, else we're done.
+		 */
+		if ((tp.t_data->t_infomask & HEAP_XMAX_INVALID) ||
+			HeapTupleHeaderIsOnlyLocked(tp.t_data) ||
+			HeapTupleHeaderIndicatesMovedPartitions(tp.t_data) ||
+			ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid))
+		{
+			UnlockReleaseBuffer(buffer);
+			break;
+		}
+
+		ctid = tp.t_data->t_ctid;
+		priorXmax = HeapTupleHeaderGetUpdateXid(tp.t_data);
+		UnlockReleaseBuffer(buffer);
+	}							/* end of loop */
+}
+
 static bool
 heapam_tuple_tid_valid(TableScanDesc scan, ItemPointer tid)
 {
-- 
2.27.0.rc0

Attachment: signature.asc
Description: PGP signature

Reply via email to