public inbox for [email protected]  
help / color / mirror / Atom feed
Re: [HACKERS] Pluggable storage
21+ messages / 6 participants
[nested] [flat]

* Re: [HACKERS] Pluggable storage
@ 2017-12-12 04:06 Haribabu Kommi <[email protected]>
  2017-12-27 03:54 ` Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
  0 siblings, 1 reply; 21+ messages in thread

From: Haribabu Kommi @ 2017-12-12 04:06 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: Robert Haas <[email protected]>; Amit Kapila <[email protected]>; Alexander Korotkov <[email protected]>; Thomas Munro <[email protected]>; Alvaro Herrera <[email protected]>; pgsql-hackers

On Wed, Nov 29, 2017 at 3:50 PM, Michael Paquier <[email protected]>
wrote:

> On Tue, Nov 14, 2017 at 5:09 PM, Michael Paquier
> <[email protected]> wrote:
> > On Tue, Nov 7, 2017 at 6:34 PM, Haribabu Kommi <[email protected]>
> wrote:
> >> On Tue, Oct 31, 2017 at 8:59 PM, Haribabu Kommi <
> [email protected]>
> >> wrote:
> >>> Additional changes that are done in the patches compared to earlier
> >>> patches apart from rebase.
> >>
> >> Rebased patches are attached.
> >
> > This set of patches needs again a... Rebase.
>
> No rebased versions have showed up for two weeks. For now I am marking
> this patch as returned with feedback.
>

I restructured that patch files to avoid showing unnecessary modifications,
and also it will be easy for adding of new API's based on the all the
functions
that are exposed by heapam module easily compared earlier.

Attached are the latest set of patches. I will work on the remaining pending
items.

Regards,
Hari Babu
Fujitsu Australia


Attachments:

  [application/octet-stream] 0008-Remove-HeapScanDesc-usage-outside-heap.patch (95.6K, ../../CAJrrPGdsQSRuq7N3W63CWXVkM_Acgc8HBphDvzOq1ik=Mt6j+Q@mail.gmail.com/3-0008-Remove-HeapScanDesc-usage-outside-heap.patch)
  download | inline diff:
From 50490821517164ac55699789df7e733f8ffb95c3 Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Mon, 11 Dec 2017 22:00:09 +1100
Subject: [PATCH 8/8] Remove HeapScanDesc usage outside heap

HeapScanDesc is divided into two scan descriptors.
StorageScanDesc and HeapPageScanDesc.

StorageScanDesc has common members that are should
be available across all the storage routines and
HeapPageScanDesc is avaiable only for the storage
routine that supports Heap storage with page format.
The HeapPageScanDesc is used internally by the heapam
storage routine and also this is exposed to Bitmap Heap
and Sample scan's as they depend on the Heap page format.

while generating the Bitmap Heap and Sample scan's,
the planner now checks whether the storage routine
supports returning HeapPageScanDesc or not? Based on
this decision, the planner plans above two plans.
---
 contrib/pgrowlocks/pgrowlocks.c            |   4 +-
 contrib/pgstattuple/pgstattuple.c          |  10 +-
 contrib/tsm_system_rows/tsm_system_rows.c  |  18 +-
 contrib/tsm_system_time/tsm_system_time.c  |   8 +-
 src/backend/access/heap/heapam.c           | 424 +++++++++++++++--------------
 src/backend/access/heap/heapam_storage.c   |  54 ++++
 src/backend/access/index/genam.c           |   4 +-
 src/backend/access/storage/storageam.c     |  51 +++-
 src/backend/access/tablesample/system.c    |   2 +-
 src/backend/bootstrap/bootstrap.c          |   4 +-
 src/backend/catalog/aclchk.c               |   4 +-
 src/backend/catalog/index.c                |   8 +-
 src/backend/catalog/partition.c            |   2 +-
 src/backend/catalog/pg_conversion.c        |   2 +-
 src/backend/catalog/pg_db_role_setting.c   |   2 +-
 src/backend/catalog/pg_publication.c       |   2 +-
 src/backend/catalog/pg_subscription.c      |   2 +-
 src/backend/commands/cluster.c             |   4 +-
 src/backend/commands/copy.c                |   2 +-
 src/backend/commands/dbcommands.c          |   6 +-
 src/backend/commands/indexcmds.c           |   2 +-
 src/backend/commands/tablecmds.c           |  10 +-
 src/backend/commands/tablespace.c          |  10 +-
 src/backend/commands/typecmds.c            |   4 +-
 src/backend/commands/vacuum.c              |   4 +-
 src/backend/executor/nodeBitmapHeapscan.c  |  69 +++--
 src/backend/executor/nodeSamplescan.c      |  50 ++--
 src/backend/executor/nodeSeqscan.c         |   5 +-
 src/backend/optimizer/util/plancat.c       |   4 +-
 src/backend/postmaster/autovacuum.c        |   4 +-
 src/backend/postmaster/pgstat.c            |   2 +-
 src/backend/replication/logical/launcher.c |   2 +-
 src/backend/rewrite/rewriteDefine.c        |   2 +-
 src/backend/utils/init/postinit.c          |   2 +-
 src/include/access/heapam.h                |  36 +--
 src/include/access/relscan.h               |  47 ++--
 src/include/access/storage_common.h        |   1 -
 src/include/access/storageam.h             |  44 +--
 src/include/access/storageamapi.h          |  42 +--
 src/include/nodes/execnodes.h              |   4 +-
 40 files changed, 536 insertions(+), 421 deletions(-)

diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index bc8b423975..762d969ecd 100644
--- a/contrib/pgrowlocks/pgrowlocks.c
+++ b/contrib/pgrowlocks/pgrowlocks.c
@@ -56,7 +56,7 @@ PG_FUNCTION_INFO_V1(pgrowlocks);
 typedef struct
 {
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	int			ncolumns;
 } MyData;
 
@@ -71,7 +71,7 @@ Datum
 pgrowlocks(PG_FUNCTION_ARGS)
 {
 	FuncCallContext *funcctx;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	TupleDesc	tupdesc;
 	AttInMetadata *attinmeta;
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index c4b10d6efc..32ac121e92 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -314,7 +314,8 @@ pgstat_relation(Relation rel, FunctionCallInfo fcinfo)
 static Datum
 pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
+	HeapPageScanDesc pagescan;
 	HeapTuple	tuple;
 	BlockNumber nblocks;
 	BlockNumber block = 0;		/* next block to count free space in */
@@ -328,7 +329,8 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 	scan = storage_beginscan_strat(rel, SnapshotAny, 0, NULL, true, false);
 	InitDirtySnapshot(SnapshotDirty);
 
-	nblocks = scan->rs_nblocks; /* # blocks to be scanned */
+	pagescan = storageam_get_heappagescandesc(scan);
+	nblocks = pagescan->rs_nblocks; /* # blocks to be scanned */
 
 	/* scan the relation */
 	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
@@ -364,7 +366,7 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 			CHECK_FOR_INTERRUPTS();
 
 			buffer = ReadBufferExtended(rel, MAIN_FORKNUM, block,
-										RBM_NORMAL, scan->rs_strategy);
+										RBM_NORMAL, pagescan->rs_strategy);
 			LockBuffer(buffer, BUFFER_LOCK_SHARE);
 			stat.free_space += PageGetHeapFreeSpace((Page) BufferGetPage(buffer));
 			UnlockReleaseBuffer(buffer);
@@ -377,7 +379,7 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 		CHECK_FOR_INTERRUPTS();
 
 		buffer = ReadBufferExtended(rel, MAIN_FORKNUM, block,
-									RBM_NORMAL, scan->rs_strategy);
+									RBM_NORMAL, pagescan->rs_strategy);
 		LockBuffer(buffer, BUFFER_LOCK_SHARE);
 		stat.free_space += PageGetHeapFreeSpace((Page) BufferGetPage(buffer));
 		UnlockReleaseBuffer(buffer);
diff --git a/contrib/tsm_system_rows/tsm_system_rows.c b/contrib/tsm_system_rows/tsm_system_rows.c
index 544458ec91..14120291d0 100644
--- a/contrib/tsm_system_rows/tsm_system_rows.c
+++ b/contrib/tsm_system_rows/tsm_system_rows.c
@@ -71,7 +71,7 @@ static BlockNumber system_rows_nextsampleblock(SampleScanState *node);
 static OffsetNumber system_rows_nextsampletuple(SampleScanState *node,
 							BlockNumber blockno,
 							OffsetNumber maxoffset);
-static bool SampleOffsetVisible(OffsetNumber tupoffset, HeapScanDesc scan);
+static bool SampleOffsetVisible(OffsetNumber tupoffset, HeapPageScanDesc scan);
 static uint32 random_relative_prime(uint32 n, SamplerRandomState randstate);
 
 
@@ -209,7 +209,7 @@ static BlockNumber
 system_rows_nextsampleblock(SampleScanState *node)
 {
 	SystemRowsSamplerData *sampler = (SystemRowsSamplerData *) node->tsm_state;
-	HeapScanDesc scan = node->ss.ss_currentScanDesc;
+	HeapPageScanDesc pagescan = node->pagescan;
 
 	/* First call within scan? */
 	if (sampler->doneblocks == 0)
@@ -221,14 +221,14 @@ system_rows_nextsampleblock(SampleScanState *node)
 			SamplerRandomState randstate;
 
 			/* If relation is empty, there's nothing to scan */
-			if (scan->rs_nblocks == 0)
+			if (pagescan->rs_nblocks == 0)
 				return InvalidBlockNumber;
 
 			/* We only need an RNG during this setup step */
 			sampler_random_init_state(sampler->seed, randstate);
 
 			/* Compute nblocks/firstblock/step only once per query */
-			sampler->nblocks = scan->rs_nblocks;
+			sampler->nblocks = pagescan->rs_nblocks;
 
 			/* Choose random starting block within the relation */
 			/* (Actually this is the predecessor of the first block visited) */
@@ -258,7 +258,7 @@ system_rows_nextsampleblock(SampleScanState *node)
 	{
 		/* Advance lb, using uint64 arithmetic to forestall overflow */
 		sampler->lb = ((uint64) sampler->lb + sampler->step) % sampler->nblocks;
-	} while (sampler->lb >= scan->rs_nblocks);
+	} while (sampler->lb >= pagescan->rs_nblocks);
 
 	return sampler->lb;
 }
@@ -278,7 +278,7 @@ system_rows_nextsampletuple(SampleScanState *node,
 							OffsetNumber maxoffset)
 {
 	SystemRowsSamplerData *sampler = (SystemRowsSamplerData *) node->tsm_state;
-	HeapScanDesc scan = node->ss.ss_currentScanDesc;
+	HeapPageScanDesc pagescan = node->pagescan;
 	OffsetNumber tupoffset = sampler->lt;
 
 	/* Quit if we've returned all needed tuples */
@@ -291,7 +291,7 @@ system_rows_nextsampletuple(SampleScanState *node,
 	 */
 
 	/* We rely on the data accumulated in pagemode access */
-	Assert(scan->rs_pageatatime);
+	Assert(pagescan->rs_pageatatime);
 	for (;;)
 	{
 		/* Advance to next possible offset on page */
@@ -308,7 +308,7 @@ system_rows_nextsampletuple(SampleScanState *node,
 		}
 
 		/* Found a candidate? */
-		if (SampleOffsetVisible(tupoffset, scan))
+		if (SampleOffsetVisible(tupoffset, pagescan))
 		{
 			sampler->donetuples++;
 			break;
@@ -327,7 +327,7 @@ system_rows_nextsampletuple(SampleScanState *node,
  * so just look at the info it left in rs_vistuples[].
  */
 static bool
-SampleOffsetVisible(OffsetNumber tupoffset, HeapScanDesc scan)
+SampleOffsetVisible(OffsetNumber tupoffset, HeapPageScanDesc scan)
 {
 	int			start = 0,
 				end = scan->rs_ntuples - 1;
diff --git a/contrib/tsm_system_time/tsm_system_time.c b/contrib/tsm_system_time/tsm_system_time.c
index af8d025414..aa7252215a 100644
--- a/contrib/tsm_system_time/tsm_system_time.c
+++ b/contrib/tsm_system_time/tsm_system_time.c
@@ -219,7 +219,7 @@ static BlockNumber
 system_time_nextsampleblock(SampleScanState *node)
 {
 	SystemTimeSamplerData *sampler = (SystemTimeSamplerData *) node->tsm_state;
-	HeapScanDesc scan = node->ss.ss_currentScanDesc;
+	HeapPageScanDesc pagescan = node->pagescan;
 	instr_time	cur_time;
 
 	/* First call within scan? */
@@ -232,14 +232,14 @@ system_time_nextsampleblock(SampleScanState *node)
 			SamplerRandomState randstate;
 
 			/* If relation is empty, there's nothing to scan */
-			if (scan->rs_nblocks == 0)
+			if (pagescan->rs_nblocks == 0)
 				return InvalidBlockNumber;
 
 			/* We only need an RNG during this setup step */
 			sampler_random_init_state(sampler->seed, randstate);
 
 			/* Compute nblocks/firstblock/step only once per query */
-			sampler->nblocks = scan->rs_nblocks;
+			sampler->nblocks = pagescan->rs_nblocks;
 
 			/* Choose random starting block within the relation */
 			/* (Actually this is the predecessor of the first block visited) */
@@ -275,7 +275,7 @@ system_time_nextsampleblock(SampleScanState *node)
 	{
 		/* Advance lb, using uint64 arithmetic to forestall overflow */
 		sampler->lb = ((uint64) sampler->lb + sampler->step) % sampler->nblocks;
-	} while (sampler->lb >= scan->rs_nblocks);
+	} while (sampler->lb >= pagescan->rs_nblocks);
 
 	return sampler->lb;
 }
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 4565e1c933..4d4a632354 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -220,9 +220,9 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
 	 * lock that ensures the interesting tuple(s) won't change.)
 	 */
 	if (scan->rs_parallel != NULL)
-		scan->rs_nblocks = scan->rs_parallel->phs_nblocks;
+		scan->rs_pagescan.rs_nblocks = scan->rs_parallel->phs_nblocks;
 	else
-		scan->rs_nblocks = RelationGetNumberOfBlocks(scan->rs_rd);
+		scan->rs_pagescan.rs_nblocks = RelationGetNumberOfBlocks(scan->rs_scan.rs_rd);
 
 	/*
 	 * If the table is large relative to NBuffers, use a bulk-read access
@@ -236,8 +236,8 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
 	 * Note that heap_parallelscan_initialize has a very similar test; if you
 	 * change this, consider changing that one, too.
 	 */
-	if (!RelationUsesLocalBuffers(scan->rs_rd) &&
-		scan->rs_nblocks > NBuffers / 4)
+	if (!RelationUsesLocalBuffers(scan->rs_scan.rs_rd) &&
+		scan->rs_pagescan.rs_nblocks > NBuffers / 4)
 	{
 		allow_strat = scan->rs_allow_strat;
 		allow_sync = scan->rs_allow_sync;
@@ -248,20 +248,20 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
 	if (allow_strat)
 	{
 		/* During a rescan, keep the previous strategy object. */
-		if (scan->rs_strategy == NULL)
-			scan->rs_strategy = GetAccessStrategy(BAS_BULKREAD);
+		if (scan->rs_pagescan.rs_strategy == NULL)
+			scan->rs_pagescan.rs_strategy = GetAccessStrategy(BAS_BULKREAD);
 	}
 	else
 	{
-		if (scan->rs_strategy != NULL)
-			FreeAccessStrategy(scan->rs_strategy);
-		scan->rs_strategy = NULL;
+		if (scan->rs_pagescan.rs_strategy != NULL)
+			FreeAccessStrategy(scan->rs_pagescan.rs_strategy);
+		scan->rs_pagescan.rs_strategy = NULL;
 	}
 
 	if (scan->rs_parallel != NULL)
 	{
 		/* For parallel scan, believe whatever ParallelHeapScanDesc says. */
-		scan->rs_syncscan = scan->rs_parallel->phs_syncscan;
+		scan->rs_pagescan.rs_syncscan = scan->rs_parallel->phs_syncscan;
 	}
 	else if (keep_startblock)
 	{
@@ -270,25 +270,25 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
 		 * so that rewinding a cursor doesn't generate surprising results.
 		 * Reset the active syncscan setting, though.
 		 */
-		scan->rs_syncscan = (allow_sync && synchronize_seqscans);
+		scan->rs_pagescan.rs_syncscan = (allow_sync && synchronize_seqscans);
 	}
 	else if (allow_sync && synchronize_seqscans)
 	{
-		scan->rs_syncscan = true;
-		scan->rs_startblock = ss_get_location(scan->rs_rd, scan->rs_nblocks);
+		scan->rs_pagescan.rs_syncscan = true;
+		scan->rs_pagescan.rs_startblock = ss_get_location(scan->rs_scan.rs_rd, scan->rs_pagescan.rs_nblocks);
 	}
 	else
 	{
-		scan->rs_syncscan = false;
-		scan->rs_startblock = 0;
+		scan->rs_pagescan.rs_syncscan = false;
+		scan->rs_pagescan.rs_startblock = 0;
 	}
 
-	scan->rs_numblocks = InvalidBlockNumber;
-	scan->rs_inited = false;
+	scan->rs_pagescan.rs_numblocks = InvalidBlockNumber;
+	scan->rs_scan.rs_inited = false;
 	scan->rs_ctup.t_data = NULL;
 	ItemPointerSetInvalid(&scan->rs_ctup.t_self);
-	scan->rs_cbuf = InvalidBuffer;
-	scan->rs_cblock = InvalidBlockNumber;
+	scan->rs_scan.rs_cbuf = InvalidBuffer;
+	scan->rs_scan.rs_cblock = InvalidBlockNumber;
 
 	/* page-at-a-time fields are always invalid when not rs_inited */
 
@@ -296,7 +296,7 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
 	 * copy the scan key, if appropriate
 	 */
 	if (key != NULL)
-		memcpy(scan->rs_key, key, scan->rs_nkeys * sizeof(ScanKeyData));
+		memcpy(scan->rs_scan.rs_key, key, scan->rs_scan.rs_nkeys * sizeof(ScanKeyData));
 
 	/*
 	 * Currently, we don't have a stats counter for bitmap heap scans (but the
@@ -304,7 +304,7 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
 	 * update stats for tuple fetches there)
 	 */
 	if (!scan->rs_bitmapscan && !scan->rs_samplescan)
-		pgstat_count_heap_scan(scan->rs_rd);
+		pgstat_count_heap_scan(scan->rs_scan.rs_rd);
 }
 
 /*
@@ -314,16 +314,19 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
  * numBlks is number of pages to scan (InvalidBlockNumber means "all")
  */
 void
-heap_setscanlimits(HeapScanDesc scan, BlockNumber startBlk, BlockNumber numBlks)
+heap_setscanlimits(StorageScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
 {
-	Assert(!scan->rs_inited);	/* else too late to change */
-	Assert(!scan->rs_syncscan); /* else rs_startblock is significant */
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
+	Assert(!scan->rs_scan.rs_inited);	/* else too late to change */
+	Assert(!scan->rs_pagescan.rs_syncscan); /* else rs_startblock is
+											 * significant */
 
 	/* Check startBlk is valid (but allow case of zero blocks...) */
-	Assert(startBlk == 0 || startBlk < scan->rs_nblocks);
+	Assert(startBlk == 0 || startBlk < scan->rs_pagescan.rs_nblocks);
 
-	scan->rs_startblock = startBlk;
-	scan->rs_numblocks = numBlks;
+	scan->rs_pagescan.rs_startblock = startBlk;
+	scan->rs_pagescan.rs_numblocks = numBlks;
 }
 
 /*
@@ -334,8 +337,9 @@ heap_setscanlimits(HeapScanDesc scan, BlockNumber startBlk, BlockNumber numBlks)
  * which tuples on the page are visible.
  */
 void
-heapgetpage(HeapScanDesc scan, BlockNumber page)
+heapgetpage(StorageScanDesc sscan, BlockNumber page)
 {
+	HeapScanDesc scan = (HeapScanDesc) sscan;
 	Buffer		buffer;
 	Snapshot	snapshot;
 	Page		dp;
@@ -345,13 +349,13 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 	ItemId		lpp;
 	bool		all_visible;
 
-	Assert(page < scan->rs_nblocks);
+	Assert(page < scan->rs_pagescan.rs_nblocks);
 
 	/* release previous scan buffer, if any */
-	if (BufferIsValid(scan->rs_cbuf))
+	if (BufferIsValid(scan->rs_scan.rs_cbuf))
 	{
-		ReleaseBuffer(scan->rs_cbuf);
-		scan->rs_cbuf = InvalidBuffer;
+		ReleaseBuffer(scan->rs_scan.rs_cbuf);
+		scan->rs_scan.rs_cbuf = InvalidBuffer;
 	}
 
 	/*
@@ -362,20 +366,20 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 	CHECK_FOR_INTERRUPTS();
 
 	/* read page using selected strategy */
-	scan->rs_cbuf = ReadBufferExtended(scan->rs_rd, MAIN_FORKNUM, page,
-									   RBM_NORMAL, scan->rs_strategy);
-	scan->rs_cblock = page;
+	scan->rs_scan.rs_cbuf = ReadBufferExtended(scan->rs_scan.rs_rd, MAIN_FORKNUM, page,
+											   RBM_NORMAL, scan->rs_pagescan.rs_strategy);
+	scan->rs_scan.rs_cblock = page;
 
-	if (!scan->rs_pageatatime)
+	if (!scan->rs_pagescan.rs_pageatatime)
 		return;
 
-	buffer = scan->rs_cbuf;
-	snapshot = scan->rs_snapshot;
+	buffer = scan->rs_scan.rs_cbuf;
+	snapshot = scan->rs_scan.rs_snapshot;
 
 	/*
 	 * Prune and repair fragmentation for the whole page, if possible.
 	 */
-	heap_page_prune_opt(scan->rs_rd, buffer);
+	heap_page_prune_opt(scan->rs_scan.rs_rd, buffer);
 
 	/*
 	 * We must hold share lock on the buffer content while examining tuple
@@ -385,7 +389,7 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 	LockBuffer(buffer, BUFFER_LOCK_SHARE);
 
 	dp = BufferGetPage(buffer);
-	TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+	TestForOldSnapshot(snapshot, scan->rs_scan.rs_rd, dp);
 	lines = PageGetMaxOffsetNumber(dp);
 	ntup = 0;
 
@@ -420,7 +424,7 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 			HeapTupleData loctup;
 			bool		valid;
 
-			loctup.t_tableOid = RelationGetRelid(scan->rs_rd);
+			loctup.t_tableOid = RelationGetRelid(scan->rs_scan.rs_rd);
 			loctup.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
 			loctup.t_len = ItemIdGetLength(lpp);
 			ItemPointerSet(&(loctup.t_self), page, lineoff);
@@ -428,20 +432,20 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 			if (all_visible)
 				valid = true;
 			else
-				valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine, &loctup, snapshot, buffer);
+				valid = HeapTupleSatisfiesVisibility(scan->rs_scan.rs_rd->rd_stamroutine, &loctup, snapshot, buffer);
 
-			CheckForSerializableConflictOut(valid, scan->rs_rd, &loctup,
+			CheckForSerializableConflictOut(valid, scan->rs_scan.rs_rd, &loctup,
 											buffer, snapshot);
 
 			if (valid)
-				scan->rs_vistuples[ntup++] = lineoff;
+				scan->rs_pagescan.rs_vistuples[ntup++] = lineoff;
 		}
 	}
 
 	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
 
 	Assert(ntup <= MaxHeapTuplesPerPage);
-	scan->rs_ntuples = ntup;
+	scan->rs_pagescan.rs_ntuples = ntup;
 }
 
 /* ----------------
@@ -474,7 +478,7 @@ heapgettup(HeapScanDesc scan,
 		   ScanKey key)
 {
 	HeapTuple	tuple = &(scan->rs_ctup);
-	Snapshot	snapshot = scan->rs_snapshot;
+	Snapshot	snapshot = scan->rs_scan.rs_snapshot;
 	bool		backward = ScanDirectionIsBackward(dir);
 	BlockNumber page;
 	bool		finished;
@@ -489,14 +493,14 @@ heapgettup(HeapScanDesc scan,
 	 */
 	if (ScanDirectionIsForward(dir))
 	{
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
 			/*
 			 * return null immediately if relation is empty
 			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			if (scan->rs_pagescan.rs_nblocks == 0 || scan->rs_pagescan.rs_numblocks == 0)
 			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
+				Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 				tuple->t_data = NULL;
 				return;
 			}
@@ -509,29 +513,29 @@ heapgettup(HeapScanDesc scan,
 				/* Other processes might have already finished the scan. */
 				if (page == InvalidBlockNumber)
 				{
-					Assert(!BufferIsValid(scan->rs_cbuf));
+					Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 					tuple->t_data = NULL;
 					return;
 				}
 			}
 			else
-				page = scan->rs_startblock; /* first page */
-			heapgetpage(scan, page);
+				page = scan->rs_pagescan.rs_startblock; /* first page */
+			heapgetpage((StorageScanDesc) scan, page);
 			lineoff = FirstOffsetNumber;	/* first offnum */
-			scan->rs_inited = true;
+			scan->rs_scan.rs_inited = true;
 		}
 		else
 		{
 			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
+			page = scan->rs_scan.rs_cblock; /* current page */
 			lineoff =			/* next offnum */
 				OffsetNumberNext(ItemPointerGetOffsetNumber(&(tuple->t_self)));
 		}
 
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_SHARE);
 
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_scan.rs_rd, dp);
 		lines = PageGetMaxOffsetNumber(dp);
 		/* page and lineoff now reference the physically next tid */
 
@@ -542,14 +546,14 @@ heapgettup(HeapScanDesc scan,
 		/* backward parallel scan not supported */
 		Assert(scan->rs_parallel == NULL);
 
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
 			/*
 			 * return null immediately if relation is empty
 			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			if (scan->rs_pagescan.rs_nblocks == 0 || scan->rs_pagescan.rs_numblocks == 0)
 			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
+				Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 				tuple->t_data = NULL;
 				return;
 			}
@@ -560,30 +564,30 @@ heapgettup(HeapScanDesc scan,
 			 * time, and much more likely that we'll just bollix things for
 			 * forward scanners.
 			 */
-			scan->rs_syncscan = false;
+			scan->rs_pagescan.rs_syncscan = false;
 			/* start from last page of the scan */
-			if (scan->rs_startblock > 0)
-				page = scan->rs_startblock - 1;
+			if (scan->rs_pagescan.rs_startblock > 0)
+				page = scan->rs_pagescan.rs_startblock - 1;
 			else
-				page = scan->rs_nblocks - 1;
-			heapgetpage(scan, page);
+				page = scan->rs_pagescan.rs_nblocks - 1;
+			heapgetpage((StorageScanDesc) scan, page);
 		}
 		else
 		{
 			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
+			page = scan->rs_scan.rs_cblock; /* current page */
 		}
 
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_SHARE);
 
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_scan.rs_rd, dp);
 		lines = PageGetMaxOffsetNumber(dp);
 
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
 			lineoff = lines;	/* final offnum */
-			scan->rs_inited = true;
+			scan->rs_scan.rs_inited = true;
 		}
 		else
 		{
@@ -599,20 +603,20 @@ heapgettup(HeapScanDesc scan,
 		/*
 		 * ``no movement'' scan direction: refetch prior tuple
 		 */
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
-			Assert(!BufferIsValid(scan->rs_cbuf));
+			Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 			tuple->t_data = NULL;
 			return;
 		}
 
 		page = ItemPointerGetBlockNumber(&(tuple->t_self));
-		if (page != scan->rs_cblock)
-			heapgetpage(scan, page);
+		if (page != scan->rs_scan.rs_cblock)
+			heapgetpage((StorageScanDesc) scan, page);
 
 		/* Since the tuple was previously fetched, needn't lock page here */
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_scan.rs_rd, dp);
 		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
 		lpp = PageGetItemId(dp, lineoff);
 		Assert(ItemIdIsNormal(lpp));
@@ -643,21 +647,21 @@ heapgettup(HeapScanDesc scan,
 				/*
 				 * if current tuple qualifies, return it.
 				 */
-				valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine,
+				valid = HeapTupleSatisfiesVisibility(scan->rs_scan.rs_rd->rd_stamroutine,
 													 tuple,
 													 snapshot,
-													 scan->rs_cbuf);
+													 scan->rs_scan.rs_cbuf);
 
-				CheckForSerializableConflictOut(valid, scan->rs_rd, tuple,
-												scan->rs_cbuf, snapshot);
+				CheckForSerializableConflictOut(valid, scan->rs_scan.rs_rd, tuple,
+												scan->rs_scan.rs_cbuf, snapshot);
 
 				if (valid && key != NULL)
-					HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
+					HeapKeyTest(tuple, RelationGetDescr(scan->rs_scan.rs_rd),
 								nkeys, key, valid);
 
 				if (valid)
 				{
-					LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+					LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_UNLOCK);
 					return;
 				}
 			}
@@ -682,17 +686,17 @@ heapgettup(HeapScanDesc scan,
 		 * if we get here, it means we've exhausted the items on this page and
 		 * it's time to move to the next.
 		 */
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_UNLOCK);
 
 		/*
 		 * advance to next/prior page and detect end of scan
 		 */
 		if (backward)
 		{
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+			finished = (page == scan->rs_pagescan.rs_startblock) ||
+				(scan->rs_pagescan.rs_numblocks != InvalidBlockNumber ? --scan->rs_pagescan.rs_numblocks == 0 : false);
 			if (page == 0)
-				page = scan->rs_nblocks;
+				page = scan->rs_pagescan.rs_nblocks;
 			page--;
 		}
 		else if (scan->rs_parallel != NULL)
@@ -703,10 +707,10 @@ heapgettup(HeapScanDesc scan,
 		else
 		{
 			page++;
-			if (page >= scan->rs_nblocks)
+			if (page >= scan->rs_pagescan.rs_nblocks)
 				page = 0;
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+			finished = (page == scan->rs_pagescan.rs_startblock) ||
+				(scan->rs_pagescan.rs_numblocks != InvalidBlockNumber ? --scan->rs_pagescan.rs_numblocks == 0 : false);
 
 			/*
 			 * Report our new scan position for synchronization purposes. We
@@ -720,8 +724,8 @@ heapgettup(HeapScanDesc scan,
 			 * a little bit backwards on every invocation, which is confusing.
 			 * We don't guarantee any specific ordering in general, though.
 			 */
-			if (scan->rs_syncscan)
-				ss_report_location(scan->rs_rd, page);
+			if (scan->rs_pagescan.rs_syncscan)
+				ss_report_location(scan->rs_scan.rs_rd, page);
 		}
 
 		/*
@@ -729,21 +733,21 @@ heapgettup(HeapScanDesc scan,
 		 */
 		if (finished)
 		{
-			if (BufferIsValid(scan->rs_cbuf))
-				ReleaseBuffer(scan->rs_cbuf);
-			scan->rs_cbuf = InvalidBuffer;
-			scan->rs_cblock = InvalidBlockNumber;
+			if (BufferIsValid(scan->rs_scan.rs_cbuf))
+				ReleaseBuffer(scan->rs_scan.rs_cbuf);
+			scan->rs_scan.rs_cbuf = InvalidBuffer;
+			scan->rs_scan.rs_cblock = InvalidBlockNumber;
 			tuple->t_data = NULL;
-			scan->rs_inited = false;
+			scan->rs_scan.rs_inited = false;
 			return;
 		}
 
-		heapgetpage(scan, page);
+		heapgetpage((StorageScanDesc) scan, page);
 
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_SHARE);
 
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_scan.rs_rd, dp);
 		lines = PageGetMaxOffsetNumber((Page) dp);
 		linesleft = lines;
 		if (backward)
@@ -794,14 +798,14 @@ heapgettup_pagemode(HeapScanDesc scan,
 	 */
 	if (ScanDirectionIsForward(dir))
 	{
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
 			/*
 			 * return null immediately if relation is empty
 			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			if (scan->rs_pagescan.rs_nblocks == 0 || scan->rs_pagescan.rs_numblocks == 0)
 			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
+				Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 				tuple->t_data = NULL;
 				return;
 			}
@@ -814,28 +818,28 @@ heapgettup_pagemode(HeapScanDesc scan,
 				/* Other processes might have already finished the scan. */
 				if (page == InvalidBlockNumber)
 				{
-					Assert(!BufferIsValid(scan->rs_cbuf));
+					Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 					tuple->t_data = NULL;
 					return;
 				}
 			}
 			else
-				page = scan->rs_startblock; /* first page */
-			heapgetpage(scan, page);
+				page = scan->rs_pagescan.rs_startblock; /* first page */
+			heapgetpage((StorageScanDesc) scan, page);
 			lineindex = 0;
-			scan->rs_inited = true;
+			scan->rs_scan.rs_inited = true;
 		}
 		else
 		{
 			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
-			lineindex = scan->rs_cindex + 1;
+			page = scan->rs_scan.rs_cblock; /* current page */
+			lineindex = scan->rs_pagescan.rs_cindex + 1;
 		}
 
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_SHARE);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(scan->rs_scan.rs_snapshot, scan->rs_scan.rs_rd, dp);
+		lines = scan->rs_pagescan.rs_ntuples;
 		/* page and lineindex now reference the next visible tid */
 
 		linesleft = lines - lineindex;
@@ -845,14 +849,14 @@ heapgettup_pagemode(HeapScanDesc scan,
 		/* backward parallel scan not supported */
 		Assert(scan->rs_parallel == NULL);
 
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
 			/*
 			 * return null immediately if relation is empty
 			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			if (scan->rs_pagescan.rs_nblocks == 0 || scan->rs_pagescan.rs_numblocks == 0)
 			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
+				Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 				tuple->t_data = NULL;
 				return;
 			}
@@ -863,33 +867,33 @@ heapgettup_pagemode(HeapScanDesc scan,
 			 * time, and much more likely that we'll just bollix things for
 			 * forward scanners.
 			 */
-			scan->rs_syncscan = false;
+			scan->rs_pagescan.rs_syncscan = false;
 			/* start from last page of the scan */
-			if (scan->rs_startblock > 0)
-				page = scan->rs_startblock - 1;
+			if (scan->rs_pagescan.rs_startblock > 0)
+				page = scan->rs_pagescan.rs_startblock - 1;
 			else
-				page = scan->rs_nblocks - 1;
-			heapgetpage(scan, page);
+				page = scan->rs_pagescan.rs_nblocks - 1;
+			heapgetpage((StorageScanDesc) scan, page);
 		}
 		else
 		{
 			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
+			page = scan->rs_scan.rs_cblock; /* current page */
 		}
 
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_SHARE);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(scan->rs_scan.rs_snapshot, scan->rs_scan.rs_rd, dp);
+		lines = scan->rs_pagescan.rs_ntuples;
 
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
 			lineindex = lines - 1;
-			scan->rs_inited = true;
+			scan->rs_scan.rs_inited = true;
 		}
 		else
 		{
-			lineindex = scan->rs_cindex - 1;
+			lineindex = scan->rs_pagescan.rs_cindex - 1;
 		}
 		/* page and lineindex now reference the previous visible tid */
 
@@ -900,20 +904,20 @@ heapgettup_pagemode(HeapScanDesc scan,
 		/*
 		 * ``no movement'' scan direction: refetch prior tuple
 		 */
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
-			Assert(!BufferIsValid(scan->rs_cbuf));
+			Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 			tuple->t_data = NULL;
 			return;
 		}
 
 		page = ItemPointerGetBlockNumber(&(tuple->t_self));
-		if (page != scan->rs_cblock)
-			heapgetpage(scan, page);
+		if (page != scan->rs_scan.rs_cblock)
+			heapgetpage((StorageScanDesc) scan, page);
 
 		/* Since the tuple was previously fetched, needn't lock page here */
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(scan->rs_scan.rs_snapshot, scan->rs_scan.rs_rd, dp);
 		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
 		lpp = PageGetItemId(dp, lineoff);
 		Assert(ItemIdIsNormal(lpp));
@@ -922,8 +926,8 @@ heapgettup_pagemode(HeapScanDesc scan,
 		tuple->t_len = ItemIdGetLength(lpp);
 
 		/* check that rs_cindex is in sync */
-		Assert(scan->rs_cindex < scan->rs_ntuples);
-		Assert(lineoff == scan->rs_vistuples[scan->rs_cindex]);
+		Assert(scan->rs_pagescan.rs_cindex < scan->rs_pagescan.rs_ntuples);
+		Assert(lineoff == scan->rs_pagescan.rs_vistuples[scan->rs_pagescan.rs_cindex]);
 
 		return;
 	}
@@ -936,7 +940,7 @@ heapgettup_pagemode(HeapScanDesc scan,
 	{
 		while (linesleft > 0)
 		{
-			lineoff = scan->rs_vistuples[lineindex];
+			lineoff = scan->rs_pagescan.rs_vistuples[lineindex];
 			lpp = PageGetItemId(dp, lineoff);
 			Assert(ItemIdIsNormal(lpp));
 
@@ -947,7 +951,7 @@ heapgettup_pagemode(HeapScanDesc scan,
 			/*
 			 * if current tuple qualifies, return it.
 			 */
-			if (HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine, tuple, scan->rs_snapshot, scan->rs_cbuf))
+			if (HeapTupleSatisfiesVisibility(scan->rs_scan.rs_rd->rd_stamroutine, tuple, scan->rs_scan.rs_snapshot, scan->rs_scan.rs_cbuf))
 			{
 				/*
 				 * if current tuple qualifies, return it.
@@ -956,19 +960,19 @@ heapgettup_pagemode(HeapScanDesc scan,
 				{
 					bool		valid;
 
-					HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
+					HeapKeyTest(tuple, RelationGetDescr(scan->rs_scan.rs_rd),
 								nkeys, key, valid);
 					if (valid)
 					{
-						scan->rs_cindex = lineindex;
-						LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+						scan->rs_pagescan.rs_cindex = lineindex;
+						LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_UNLOCK);
 						return;
 					}
 				}
 				else
 				{
-					scan->rs_cindex = lineindex;
-					LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+					scan->rs_pagescan.rs_cindex = lineindex;
+					LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_UNLOCK);
 					return;
 				}
 			}
@@ -987,7 +991,7 @@ heapgettup_pagemode(HeapScanDesc scan,
 		 * if we get here, it means we've exhausted the items on this page and
 		 * it's time to move to the next.
 		 */
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_UNLOCK);
 
 		/*
 		 * if we get here, it means we've exhausted the items on this page and
@@ -995,10 +999,10 @@ heapgettup_pagemode(HeapScanDesc scan,
 		 */
 		if (backward)
 		{
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+			finished = (page == scan->rs_pagescan.rs_startblock) ||
+				(scan->rs_pagescan.rs_numblocks != InvalidBlockNumber ? --scan->rs_pagescan.rs_numblocks == 0 : false);
 			if (page == 0)
-				page = scan->rs_nblocks;
+				page = scan->rs_pagescan.rs_nblocks;
 			page--;
 		}
 		else if (scan->rs_parallel != NULL)
@@ -1009,10 +1013,10 @@ heapgettup_pagemode(HeapScanDesc scan,
 		else
 		{
 			page++;
-			if (page >= scan->rs_nblocks)
+			if (page >= scan->rs_pagescan.rs_nblocks)
 				page = 0;
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+			finished = (page == scan->rs_pagescan.rs_startblock) ||
+				(scan->rs_pagescan.rs_numblocks != InvalidBlockNumber ? --scan->rs_pagescan.rs_numblocks == 0 : false);
 
 			/*
 			 * Report our new scan position for synchronization purposes. We
@@ -1026,8 +1030,8 @@ heapgettup_pagemode(HeapScanDesc scan,
 			 * a little bit backwards on every invocation, which is confusing.
 			 * We don't guarantee any specific ordering in general, though.
 			 */
-			if (scan->rs_syncscan)
-				ss_report_location(scan->rs_rd, page);
+			if (scan->rs_pagescan.rs_syncscan)
+				ss_report_location(scan->rs_scan.rs_rd, page);
 		}
 
 		/*
@@ -1035,21 +1039,21 @@ heapgettup_pagemode(HeapScanDesc scan,
 		 */
 		if (finished)
 		{
-			if (BufferIsValid(scan->rs_cbuf))
-				ReleaseBuffer(scan->rs_cbuf);
-			scan->rs_cbuf = InvalidBuffer;
-			scan->rs_cblock = InvalidBlockNumber;
+			if (BufferIsValid(scan->rs_scan.rs_cbuf))
+				ReleaseBuffer(scan->rs_scan.rs_cbuf);
+			scan->rs_scan.rs_cbuf = InvalidBuffer;
+			scan->rs_scan.rs_cblock = InvalidBlockNumber;
 			tuple->t_data = NULL;
-			scan->rs_inited = false;
+			scan->rs_scan.rs_inited = false;
 			return;
 		}
 
-		heapgetpage(scan, page);
+		heapgetpage((StorageScanDesc) scan, page);
 
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_SHARE);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(scan->rs_scan.rs_snapshot, scan->rs_scan.rs_rd, dp);
+		lines = scan->rs_pagescan.rs_ntuples;
 		linesleft = lines;
 		if (backward)
 			lineindex = lines - 1;
@@ -1376,7 +1380,7 @@ heap_openrv_extended(const RangeVar *relation, LOCKMODE lockmode,
 	return r;
 }
 
-HeapScanDesc
+StorageScanDesc
 heap_beginscan(Relation relation, Snapshot snapshot,
 			   int nkeys, ScanKey key,
 			   ParallelHeapScanDesc parallel_scan,
@@ -1403,12 +1407,12 @@ heap_beginscan(Relation relation, Snapshot snapshot,
 	 */
 	scan = (HeapScanDesc) palloc(sizeof(HeapScanDescData));
 
-	scan->rs_rd = relation;
-	scan->rs_snapshot = snapshot;
-	scan->rs_nkeys = nkeys;
+	scan->rs_scan.rs_rd = relation;
+	scan->rs_scan.rs_snapshot = snapshot;
+	scan->rs_scan.rs_nkeys = nkeys;
 	scan->rs_bitmapscan = is_bitmapscan;
 	scan->rs_samplescan = is_samplescan;
-	scan->rs_strategy = NULL;	/* set in initscan */
+	scan->rs_pagescan.rs_strategy = NULL;	/* set in initscan */
 	scan->rs_allow_strat = allow_strat;
 	scan->rs_allow_sync = allow_sync;
 	scan->rs_temp_snap = temp_snap;
@@ -1417,7 +1421,7 @@ heap_beginscan(Relation relation, Snapshot snapshot,
 	/*
 	 * we can use page-at-a-time mode if it's an MVCC-safe snapshot
 	 */
-	scan->rs_pageatatime = allow_pagemode && IsMVCCSnapshot(snapshot);
+	scan->rs_pagescan.rs_pageatatime = allow_pagemode && IsMVCCSnapshot(snapshot);
 
 	/*
 	 * For a seqscan in a serializable transaction, acquire a predicate lock
@@ -1441,13 +1445,13 @@ heap_beginscan(Relation relation, Snapshot snapshot,
 	 * initscan() and we don't want to allocate memory again
 	 */
 	if (nkeys > 0)
-		scan->rs_key = (ScanKey) palloc(sizeof(ScanKeyData) * nkeys);
+		scan->rs_scan.rs_key = (ScanKey) palloc(sizeof(ScanKeyData) * nkeys);
 	else
-		scan->rs_key = NULL;
+		scan->rs_scan.rs_key = NULL;
 
 	initscan(scan, key, false);
 
-	return scan;
+	return (StorageScanDesc) scan;
 }
 
 /* ----------------
@@ -1455,21 +1459,23 @@ heap_beginscan(Relation relation, Snapshot snapshot,
  * ----------------
  */
 void
-heap_rescan(HeapScanDesc scan, ScanKey key, bool set_params,
+heap_rescan(StorageScanDesc sscan, ScanKey key, bool set_params,
 			bool allow_strat, bool allow_sync, bool allow_pagemode)
 {
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
 	if (set_params)
 	{
 		scan->rs_allow_strat = allow_strat;
 		scan->rs_allow_sync = allow_sync;
-		scan->rs_pageatatime = allow_pagemode && IsMVCCSnapshot(scan->rs_snapshot);
+		scan->rs_pagescan.rs_pageatatime = allow_pagemode && IsMVCCSnapshot(scan->rs_scan.rs_snapshot);
 	}
 
 	/*
 	 * unpin scan buffers
 	 */
-	if (BufferIsValid(scan->rs_cbuf))
-		ReleaseBuffer(scan->rs_cbuf);
+	if (BufferIsValid(scan->rs_scan.rs_cbuf))
+		ReleaseBuffer(scan->rs_scan.rs_cbuf);
 
 	/*
 	 * reinitialize scan descriptor
@@ -1500,29 +1506,31 @@ heap_rescan(HeapScanDesc scan, ScanKey key, bool set_params,
  * ----------------
  */
 void
-heap_endscan(HeapScanDesc scan)
+heap_endscan(StorageScanDesc sscan)
 {
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
 	/* Note: no locking manipulations needed */
 
 	/*
 	 * unpin scan buffers
 	 */
-	if (BufferIsValid(scan->rs_cbuf))
-		ReleaseBuffer(scan->rs_cbuf);
+	if (BufferIsValid(scan->rs_scan.rs_cbuf))
+		ReleaseBuffer(scan->rs_scan.rs_cbuf);
 
 	/*
 	 * decrement relation reference count and free scan descriptor storage
 	 */
-	RelationDecrementReferenceCount(scan->rs_rd);
+	RelationDecrementReferenceCount(scan->rs_scan.rs_rd);
 
-	if (scan->rs_key)
-		pfree(scan->rs_key);
+	if (scan->rs_scan.rs_key)
+		pfree(scan->rs_scan.rs_key);
 
-	if (scan->rs_strategy != NULL)
-		FreeAccessStrategy(scan->rs_strategy);
+	if (scan->rs_pagescan.rs_strategy != NULL)
+		FreeAccessStrategy(scan->rs_pagescan.rs_strategy);
 
 	if (scan->rs_temp_snap)
-		UnregisterSnapshot(scan->rs_snapshot);
+		UnregisterSnapshot(scan->rs_scan.rs_snapshot);
 
 	pfree(scan);
 }
@@ -1618,7 +1626,7 @@ retry:
 		else
 		{
 			SpinLockRelease(&parallel_scan->phs_mutex);
-			sync_startpage = ss_get_location(scan->rs_rd, scan->rs_nblocks);
+			sync_startpage = ss_get_location(scan->rs_scan.rs_rd, scan->rs_pagescan.rs_nblocks);
 			goto retry;
 		}
 	}
@@ -1660,10 +1668,10 @@ heap_parallelscan_nextpage(HeapScanDesc scan)
 	 * starting block number, modulo nblocks.
 	 */
 	nallocated = pg_atomic_fetch_add_u64(&parallel_scan->phs_nallocated, 1);
-	if (nallocated >= scan->rs_nblocks)
+	if (nallocated >= scan->rs_pagescan.rs_nblocks)
 		page = InvalidBlockNumber;	/* all blocks have been allocated */
 	else
-		page = (nallocated + parallel_scan->phs_startblock) % scan->rs_nblocks;
+		page = (nallocated + parallel_scan->phs_startblock) % scan->rs_pagescan.rs_nblocks;
 
 	/*
 	 * Report scan location.  Normally, we report the current page number.
@@ -1672,12 +1680,12 @@ heap_parallelscan_nextpage(HeapScanDesc scan)
 	 * doesn't slew backwards.  We only report the position at the end of the
 	 * scan once, though: subsequent callers will report nothing.
 	 */
-	if (scan->rs_syncscan)
+	if (scan->rs_pagescan.rs_syncscan)
 	{
 		if (page != InvalidBlockNumber)
-			ss_report_location(scan->rs_rd, page);
-		else if (nallocated == scan->rs_nblocks)
-			ss_report_location(scan->rs_rd, parallel_scan->phs_startblock);
+			ss_report_location(scan->rs_scan.rs_rd, page);
+		else if (nallocated == scan->rs_pagescan.rs_nblocks)
+			ss_report_location(scan->rs_scan.rs_rd, parallel_scan->phs_startblock);
 	}
 
 	return page;
@@ -1690,12 +1698,14 @@ heap_parallelscan_nextpage(HeapScanDesc scan)
  * ----------------
  */
 void
-heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
+heap_update_snapshot(StorageScanDesc sscan, Snapshot snapshot)
 {
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
 	Assert(IsMVCCSnapshot(snapshot));
 
 	RegisterSnapshot(snapshot);
-	scan->rs_snapshot = snapshot;
+	scan->rs_scan.rs_snapshot = snapshot;
 	scan->rs_temp_snap = true;
 }
 
@@ -1723,17 +1733,19 @@ heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
 #endif							/* !defined(HEAPDEBUGALL) */
 
 StorageTuple
-heap_getnext(HeapScanDesc scan, ScanDirection direction)
+heap_getnext(StorageScanDesc sscan, ScanDirection direction)
 {
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
 	/* Note: no locking manipulations needed */
 
 	HEAPDEBUG_1;				/* heap_getnext( info ) */
 
-	if (scan->rs_pageatatime)
+	if (scan->rs_pagescan.rs_pageatatime)
 		heapgettup_pagemode(scan, direction,
-							scan->rs_nkeys, scan->rs_key);
+							scan->rs_scan.rs_nkeys, scan->rs_scan.rs_key);
 	else
-		heapgettup(scan, direction, scan->rs_nkeys, scan->rs_key);
+		heapgettup(scan, direction, scan->rs_scan.rs_nkeys, scan->rs_scan.rs_key);
 
 	if (scan->rs_ctup.t_data == NULL)
 	{
@@ -1747,7 +1759,7 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 	 */
 	HEAPDEBUG_3;				/* heap_getnext returning tuple */
 
-	pgstat_count_heap_getnext(scan->rs_rd);
+	pgstat_count_heap_getnext(scan->rs_scan.rs_rd);
 
 	return heap_copytuple(&(scan->rs_ctup));
 }
@@ -1755,7 +1767,7 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 #ifdef HEAPAMSLOTDEBUGALL
 #define HEAPAMSLOTDEBUG_1 \
 	elog(DEBUG2, "heapam_getnext([%s,nkeys=%d],dir=%d) called", \
-		 RelationGetRelationName(scan->rs_rd), scan->rs_nkeys, (int) direction)
+		 RelationGetRelationName(scan->rs_scan.rs_rd), scan->rs_scan.rs_nkeys, (int) direction)
 #define HEAPAMSLOTDEBUG_2 \
 	elog(DEBUG2, "heapam_getnext returning EOS")
 #define HEAPAMSLOTDEBUG_3 \
@@ -1767,7 +1779,7 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 #endif
 
 TupleTableSlot *
-heap_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
+heap_getnextslot(StorageScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
 {
 	HeapScanDesc scan = (HeapScanDesc) sscan;
 
@@ -1775,11 +1787,11 @@ heap_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *sl
 
 	HEAPAMSLOTDEBUG_1;			/* heap_getnext( info ) */
 
-	if (scan->rs_pageatatime)
+	if (scan->rs_pagescan.rs_pageatatime)
 		heapgettup_pagemode(scan, direction,
-							scan->rs_nkeys, scan->rs_key);
+							scan->rs_scan.rs_nkeys, scan->rs_scan.rs_key);
 	else
-		heapgettup(scan, direction, scan->rs_nkeys, scan->rs_key);
+		heapgettup(scan, direction, scan->rs_scan.rs_nkeys, scan->rs_scan.rs_key);
 
 	if (scan->rs_ctup.t_data == NULL)
 	{
@@ -1794,7 +1806,7 @@ heap_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *sl
 	 */
 	HEAPAMSLOTDEBUG_3;			/* heap_getnext returning tuple */
 
-	pgstat_count_heap_getnext(scan->rs_rd);
+	pgstat_count_heap_getnext(scan->rs_scan.rs_rd);
 	return ExecStoreTuple(heap_copytuple(&(scan->rs_ctup)),
 						  slot, InvalidBuffer, true);
 }
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index 4ee3def639..bccc1f8700 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -20,7 +20,10 @@
  */
 #include "postgres.h"
 
+#include "access/heapam.h"
+#include "access/relscan.h"
 #include "access/storageamapi.h"
+#include "pgstat.h"
 #include "utils/builtins.h"
 #include "utils/rel.h"
 
@@ -241,6 +244,44 @@ heapam_form_tuple_by_datum(Datum data, Oid tableoid)
 	return heap_form_tuple_by_datum(data, tableoid);
 }
 
+static ParallelHeapScanDesc
+heapam_get_parallelheapscandesc(StorageScanDesc sscan)
+{
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
+	return scan->rs_parallel;
+}
+
+static HeapPageScanDesc
+heapam_get_heappagescandesc(StorageScanDesc sscan)
+{
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
+	return &scan->rs_pagescan;
+}
+
+static StorageTuple
+heapam_fetch_tuple_from_offset(StorageScanDesc sscan, BlockNumber blkno, OffsetNumber offset)
+{
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+	Page		dp;
+	ItemId		lp;
+
+	dp = (Page) BufferGetPage(scan->rs_scan.rs_cbuf);
+	lp = PageGetItemId(dp, offset);
+	Assert(ItemIdIsNormal(lp));
+
+	scan->rs_ctup.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lp);
+	scan->rs_ctup.t_len = ItemIdGetLength(lp);
+	scan->rs_ctup.t_tableOid = scan->rs_scan.rs_rd->rd_id;
+	ItemPointerSet(&scan->rs_ctup.t_self, blkno, offset);
+
+	pgstat_count_heap_fetch(scan->rs_scan.rs_rd);
+
+	return &(scan->rs_ctup);
+}
+
+
 Datum
 heapam_storage_handler(PG_FUNCTION_ARGS)
 {
@@ -261,6 +302,19 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 	amroutine->scan_rescan = heap_rescan;
 	amroutine->scan_update_snapshot = heap_update_snapshot;
 	amroutine->hot_search_buffer = heap_hot_search_buffer;
+	amroutine->scan_fetch_tuple_from_offset = heapam_fetch_tuple_from_offset;
+
+	/*
+	 * The following routine needs to be provided when the storage support
+	 * parallel sequential scan
+	 */
+	amroutine->scan_get_parallelheapscandesc = heapam_get_parallelheapscandesc;
+
+	/*
+	 * The following routine needs to be provided when the storage support
+	 * BitmapHeap and Sample Scans
+	 */
+	amroutine->scan_get_heappagescandesc = heapam_get_heappagescandesc;
 
 	amroutine->tuple_fetch = heapam_fetch;
 	amroutine->tuple_insert = heapam_heap_insert;
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 26a9ccb657..ee9352df06 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -478,10 +478,10 @@ systable_recheck_tuple(SysScanDesc sysscan, HeapTuple tup)
 	}
 	else
 	{
-		HeapScanDesc scan = sysscan->scan;
+		StorageScanDesc scan = sysscan->scan;
 
 		Assert(IsMVCCSnapshot(scan->rs_snapshot));
-		Assert(tup == &scan->rs_ctup);
+		/* hari Assert(tup == &scan->rs_ctup); */
 		Assert(BufferIsValid(scan->rs_cbuf));
 		/* must hold a buffer lock to call HeapTupleSatisfiesVisibility */
 		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
diff --git a/src/backend/access/storage/storageam.c b/src/backend/access/storage/storageam.c
index c326a54f70..dfd28b55cd 100644
--- a/src/backend/access/storage/storageam.c
+++ b/src/backend/access/storage/storageam.c
@@ -56,7 +56,7 @@ storage_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * stuple,
  *		Caller must hold a suitable lock on the correct relation.
  * ----------------
  */
-HeapScanDesc
+StorageScanDesc
 storage_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan)
 {
 	Snapshot	snapshot;
@@ -69,6 +69,25 @@ storage_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan
 												true, true, true, false, false, true);
 }
 
+ParallelHeapScanDesc
+storageam_get_parallelheapscandesc(StorageScanDesc sscan)
+{
+	return sscan->rs_rd->rd_stamroutine->scan_get_parallelheapscandesc(sscan);
+}
+
+HeapPageScanDesc
+storageam_get_heappagescandesc(StorageScanDesc sscan)
+{
+	/*
+	 * Planner should have already validated whether the current storage
+	 * supports Page scans are not? This function will be called only from
+	 * Bitmap Heap scan and sample scan
+	 */
+	Assert(sscan->rs_rd->rd_stamroutine->scan_get_heappagescandesc != NULL);
+
+	return sscan->rs_rd->rd_stamroutine->scan_get_heappagescandesc(sscan);
+}
+
 /*
  * heap_setscanlimits - restrict range of a heapscan
  *
@@ -76,7 +95,7 @@ storage_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan
  * numBlks is number of pages to scan (InvalidBlockNumber means "all")
  */
 void
-storage_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
+storage_setscanlimits(StorageScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
 {
 	sscan->rs_rd->rd_stamroutine->scansetlimits(sscan, startBlk, numBlks);
 }
@@ -106,7 +125,7 @@ storage_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numB
  * also allows control of whether page-mode visibility checking is used.
  * ----------------
  */
-HeapScanDesc
+StorageScanDesc
 storage_beginscan(Relation relation, Snapshot snapshot,
 				  int nkeys, ScanKey key)
 {
@@ -114,7 +133,7 @@ storage_beginscan(Relation relation, Snapshot snapshot,
 												true, true, true, false, false, false);
 }
 
-HeapScanDesc
+StorageScanDesc
 storage_beginscan_catalog(Relation relation, int nkeys, ScanKey key)
 {
 	Oid			relid = RelationGetRelid(relation);
@@ -124,7 +143,7 @@ storage_beginscan_catalog(Relation relation, int nkeys, ScanKey key)
 												true, true, true, false, false, true);
 }
 
-HeapScanDesc
+StorageScanDesc
 storage_beginscan_strat(Relation relation, Snapshot snapshot,
 						int nkeys, ScanKey key,
 						bool allow_strat, bool allow_sync)
@@ -134,7 +153,7 @@ storage_beginscan_strat(Relation relation, Snapshot snapshot,
 												false, false, false);
 }
 
-HeapScanDesc
+StorageScanDesc
 storage_beginscan_bm(Relation relation, Snapshot snapshot,
 					 int nkeys, ScanKey key)
 {
@@ -142,7 +161,7 @@ storage_beginscan_bm(Relation relation, Snapshot snapshot,
 												false, false, true, true, false, false);
 }
 
-HeapScanDesc
+StorageScanDesc
 storage_beginscan_sampling(Relation relation, Snapshot snapshot,
 						   int nkeys, ScanKey key,
 						   bool allow_strat, bool allow_sync, bool allow_pagemode)
@@ -157,7 +176,7 @@ storage_beginscan_sampling(Relation relation, Snapshot snapshot,
  * ----------------
  */
 void
-storage_rescan(HeapScanDesc scan,
+storage_rescan(StorageScanDesc scan,
 			   ScanKey key)
 {
 	scan->rs_rd->rd_stamroutine->scan_rescan(scan, key, false, false, false, false);
@@ -173,7 +192,7 @@ storage_rescan(HeapScanDesc scan,
  * ----------------
  */
 void
-storage_rescan_set_params(HeapScanDesc scan, ScanKey key,
+storage_rescan_set_params(StorageScanDesc scan, ScanKey key,
 						  bool allow_strat, bool allow_sync, bool allow_pagemode)
 {
 	scan->rs_rd->rd_stamroutine->scan_rescan(scan, key, true,
@@ -188,7 +207,7 @@ storage_rescan_set_params(HeapScanDesc scan, ScanKey key,
  * ----------------
  */
 void
-storage_endscan(HeapScanDesc scan)
+storage_endscan(StorageScanDesc scan)
 {
 	scan->rs_rd->rd_stamroutine->scan_end(scan);
 }
@@ -201,23 +220,29 @@ storage_endscan(HeapScanDesc scan)
  * ----------------
  */
 void
-storage_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
+storage_update_snapshot(StorageScanDesc scan, Snapshot snapshot)
 {
 	scan->rs_rd->rd_stamroutine->scan_update_snapshot(scan, snapshot);
 }
 
 StorageTuple
-storage_getnext(HeapScanDesc sscan, ScanDirection direction)
+storage_getnext(StorageScanDesc sscan, ScanDirection direction)
 {
 	return sscan->rs_rd->rd_stamroutine->scan_getnext(sscan, direction);
 }
 
 TupleTableSlot *
-storage_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
+storage_getnextslot(StorageScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
 {
 	return sscan->rs_rd->rd_stamroutine->scan_getnextslot(sscan, direction, slot);
 }
 
+StorageTuple
+storage_fetch_tuple_from_offset(StorageScanDesc sscan, BlockNumber blkno, OffsetNumber offset)
+{
+	return sscan->rs_rd->rd_stamroutine->scan_fetch_tuple_from_offset(sscan, blkno, offset);
+}
+
 /*
  * Insert a tuple from a slot into storage AM routine
  */
diff --git a/src/backend/access/tablesample/system.c b/src/backend/access/tablesample/system.c
index e270cbc4a0..8793b95c08 100644
--- a/src/backend/access/tablesample/system.c
+++ b/src/backend/access/tablesample/system.c
@@ -183,7 +183,7 @@ static BlockNumber
 system_nextsampleblock(SampleScanState *node)
 {
 	SystemSamplerData *sampler = (SystemSamplerData *) node->tsm_state;
-	HeapScanDesc scan = node->ss.ss_currentScanDesc;
+	HeapPageScanDesc scan = node->pagescan;
 	BlockNumber nextblock = sampler->nextblock;
 	uint32		hashinput[2];
 
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index a73a363a49..da3d48b9cc 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -577,7 +577,7 @@ boot_openrel(char *relname)
 	int			i;
 	struct typmap **app;
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tup;
 
 	if (strlen(relname) >= NAMEDATALEN)
@@ -893,7 +893,7 @@ gettype(char *type)
 {
 	int			i;
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tup;
 	struct typmap **app;
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 0b3be57a88..c058a1bccd 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -822,7 +822,7 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 					ScanKeyData key[2];
 					int			keycount;
 					Relation	rel;
-					HeapScanDesc scan;
+					StorageScanDesc scan;
 					HeapTuple	tuple;
 
 					keycount = 0;
@@ -880,7 +880,7 @@ getRelationsInNamespace(Oid namespaceId, char relkind)
 	List	   *relations = NIL;
 	ScanKeyData key[2];
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 
 	ScanKeyInit(&key[0],
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 16bb70ef45..1c508767cf 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1900,7 +1900,7 @@ index_update_stats(Relation rel,
 		ReindexIsProcessingHeap(RelationRelationId))
 	{
 		/* don't assume syscache will work */
-		HeapScanDesc pg_class_scan;
+		StorageScanDesc pg_class_scan;
 		ScanKeyData key[1];
 
 		ScanKeyInit(&key[0],
@@ -2213,7 +2213,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 {
 	bool		is_system_catalog;
 	bool		checking_uniqueness;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	heapTuple;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
@@ -2652,7 +2652,7 @@ IndexCheckExclusion(Relation heapRelation,
 					Relation indexRelation,
 					IndexInfo *indexInfo)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	heapTuple;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
@@ -2966,7 +2966,7 @@ validate_index_heapscan(Relation heapRelation,
 						Snapshot snapshot,
 						v_i_state *state)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	heapTuple;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index d6f1a9cad5..708f8f39e8 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1265,7 +1265,7 @@ check_default_allows_bound(Relation parent, Relation default_rel,
 		Snapshot	snapshot;
 		TupleDesc	tupdesc;
 		ExprContext *econtext;
-		HeapScanDesc scan;
+		StorageScanDesc scan;
 		MemoryContext oldCxt;
 		TupleTableSlot *tupslot;
 
diff --git a/src/backend/catalog/pg_conversion.c b/src/backend/catalog/pg_conversion.c
index 1d048e6394..842abcc8b5 100644
--- a/src/backend/catalog/pg_conversion.c
+++ b/src/backend/catalog/pg_conversion.c
@@ -151,7 +151,7 @@ RemoveConversionById(Oid conversionOid)
 {
 	Relation	rel;
 	HeapTuple	tuple;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	ScanKeyData scanKeyData;
 
 	ScanKeyInit(&scanKeyData,
diff --git a/src/backend/catalog/pg_db_role_setting.c b/src/backend/catalog/pg_db_role_setting.c
index 517e3101cd..63324cfc8e 100644
--- a/src/backend/catalog/pg_db_role_setting.c
+++ b/src/backend/catalog/pg_db_role_setting.c
@@ -171,7 +171,7 @@ void
 DropSetting(Oid databaseid, Oid roleid)
 {
 	Relation	relsetting;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	ScanKeyData keys[2];
 	HeapTuple	tup;
 	int			numkeys = 0;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 145e3c1d65..5e3915b438 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -314,7 +314,7 @@ GetAllTablesPublicationRelations(void)
 {
 	Relation	classRel;
 	ScanKeyData key[1];
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	List	   *result = NIL;
 
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index a51f2e4dfc..050dfa3e4c 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -377,7 +377,7 @@ void
 RemoveSubscriptionRel(Oid subid, Oid relid)
 {
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	ScanKeyData skey[2];
 	HeapTuple	tup;
 	int			nkeys = 0;
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index e0f6973a3f..ccdbe70ff6 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -746,7 +746,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	Datum	   *values;
 	bool	   *isnull;
 	IndexScanDesc indexScan;
-	HeapScanDesc heapScan;
+	StorageScanDesc heapScan;
 	bool		use_wal;
 	bool		is_system_catalog;
 	TransactionId OldestXmin;
@@ -1638,7 +1638,7 @@ static List *
 get_tables_to_cluster(MemoryContext cluster_context)
 {
 	Relation	indRelation;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	ScanKeyData entry;
 	HeapTuple	indexTuple;
 	Form_pg_index index;
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index f2209d9f81..42803c687b 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2032,7 +2032,7 @@ CopyTo(CopyState cstate)
 	{
 		Datum	   *values;
 		bool	   *nulls;
-		HeapScanDesc scandesc;
+		StorageScanDesc scandesc;
 		HeapTuple	tuple;
 
 		values = (Datum *) palloc(num_phys_attrs * sizeof(Datum));
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 87d651002e..4a224b5bbc 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -99,7 +99,7 @@ static int	errdetail_busy_db(int notherbackends, int npreparedxacts);
 Oid
 createdb(ParseState *pstate, const CreatedbStmt *stmt)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	Relation	rel;
 	Oid			src_dboid;
 	Oid			src_owner;
@@ -1872,7 +1872,7 @@ static void
 remove_dbtablespaces(Oid db_id)
 {
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 
 	rel = heap_open(TableSpaceRelationId, AccessShareLock);
@@ -1939,7 +1939,7 @@ check_db_file_conflict(Oid db_id)
 {
 	bool		result = false;
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 
 	rel = heap_open(TableSpaceRelationId, AccessShareLock);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 400f936a2f..2152aa86e5 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1891,7 +1891,7 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 {
 	Oid			objectOid;
 	Relation	relationRelation;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	ScanKeyData scan_keys[1];
 	HeapTuple	tuple;
 	MemoryContext private_context;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3d4fa831b3..28874a27ae 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4492,7 +4492,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		bool	   *isnull;
 		TupleTableSlot *oldslot;
 		TupleTableSlot *newslot;
-		HeapScanDesc scan;
+		StorageScanDesc scan;
 		HeapTuple	tuple;
 		MemoryContext oldCxt;
 		List	   *dropped_attrs = NIL;
@@ -5065,7 +5065,7 @@ find_typed_table_dependencies(Oid typeOid, const char *typeName, DropBehavior be
 {
 	Relation	classRel;
 	ScanKeyData key[1];
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	List	   *result = NIL;
 
@@ -8219,7 +8219,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	Expr	   *origexpr;
 	ExprState  *exprstate;
 	TupleDesc	tupdesc;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	ExprContext *econtext;
 	MemoryContext oldcxt;
@@ -8302,7 +8302,7 @@ validateForeignKeyConstraint(char *conname,
 							 Oid pkindOid,
 							 Oid constraintOid)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	Trigger		trig;
 	Snapshot	snapshot;
@@ -10809,7 +10809,7 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 	ListCell   *l;
 	ScanKeyData key[1];
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	Oid			orig_tablespaceoid;
 	Oid			new_tablespaceoid;
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 6804182db7..0e2b93a5df 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -402,7 +402,7 @@ DropTableSpace(DropTableSpaceStmt *stmt)
 {
 #ifdef HAVE_SYMLINK
 	char	   *tablespacename = stmt->tablespacename;
-	HeapScanDesc scandesc;
+	StorageScanDesc scandesc;
 	Relation	rel;
 	HeapTuple	tuple;
 	ScanKeyData entry[1];
@@ -913,7 +913,7 @@ RenameTableSpace(const char *oldname, const char *newname)
 	Oid			tspId;
 	Relation	rel;
 	ScanKeyData entry[1];
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tup;
 	HeapTuple	newtuple;
 	Form_pg_tablespace newform;
@@ -988,7 +988,7 @@ AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt)
 {
 	Relation	rel;
 	ScanKeyData entry[1];
-	HeapScanDesc scandesc;
+	StorageScanDesc scandesc;
 	HeapTuple	tup;
 	Oid			tablespaceoid;
 	Datum		datum;
@@ -1382,7 +1382,7 @@ get_tablespace_oid(const char *tablespacename, bool missing_ok)
 {
 	Oid			result;
 	Relation	rel;
-	HeapScanDesc scandesc;
+	StorageScanDesc scandesc;
 	HeapTuple	tuple;
 	ScanKeyData entry[1];
 
@@ -1428,7 +1428,7 @@ get_tablespace_name(Oid spc_oid)
 {
 	char	   *result;
 	Relation	rel;
-	HeapScanDesc scandesc;
+	StorageScanDesc scandesc;
 	HeapTuple	tuple;
 	ScanKeyData entry[1];
 
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 8a339d16be..e539570d9c 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2382,7 +2382,7 @@ AlterDomainNotNull(List *names, bool notNull)
 			RelToCheck *rtc = (RelToCheck *) lfirst(rt);
 			Relation	testrel = rtc->rel;
 			TupleDesc	tupdesc = RelationGetDescr(testrel);
-			HeapScanDesc scan;
+			StorageScanDesc scan;
 			HeapTuple	tuple;
 			Snapshot	snapshot;
 
@@ -2778,7 +2778,7 @@ validateDomainConstraint(Oid domainoid, char *ccbin)
 		RelToCheck *rtc = (RelToCheck *) lfirst(rt);
 		Relation	testrel = rtc->rel;
 		TupleDesc	tupdesc = RelationGetDescr(testrel);
-		HeapScanDesc scan;
+		StorageScanDesc scan;
 		HeapTuple	tuple;
 		Snapshot	snapshot;
 
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 3a8dc8eb0c..42e462861b 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -529,7 +529,7 @@ get_all_vacuum_rels(void)
 {
 	List	   *vacrels = NIL;
 	Relation	pgclass;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 
 	pgclass = heap_open(RelationRelationId, AccessShareLock);
@@ -1183,7 +1183,7 @@ vac_truncate_clog(TransactionId frozenXID,
 {
 	TransactionId nextXID = ReadNewTransactionId();
 	Relation	relation;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	Oid			oldestxid_datoid;
 	Oid			minmulti_datoid;
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index 0c5ae8fa41..fc6c4f909a 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -55,14 +55,14 @@
 
 
 static TupleTableSlot *BitmapHeapNext(BitmapHeapScanState *node);
-static void bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres);
+static void bitgetpage(BitmapHeapScanState *node, TBMIterateResult *tbmres);
 static inline void BitmapDoneInitializingSharedState(
 								  ParallelBitmapHeapState *pstate);
 static inline void BitmapAdjustPrefetchIterator(BitmapHeapScanState *node,
 							 TBMIterateResult *tbmres);
 static inline void BitmapAdjustPrefetchTarget(BitmapHeapScanState *node);
 static inline void BitmapPrefetch(BitmapHeapScanState *node,
-			   HeapScanDesc scan);
+			   StorageScanDesc scan);
 static bool BitmapShouldInitializeSharedState(
 								  ParallelBitmapHeapState *pstate);
 
@@ -77,7 +77,8 @@ static TupleTableSlot *
 BitmapHeapNext(BitmapHeapScanState *node)
 {
 	ExprContext *econtext;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
+	HeapPageScanDesc pagescan;
 	TIDBitmap  *tbm;
 	TBMIterator *tbmiterator = NULL;
 	TBMSharedIterator *shared_tbmiterator = NULL;
@@ -93,6 +94,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 	econtext = node->ss.ps.ps_ExprContext;
 	slot = node->ss.ss_ScanTupleSlot;
 	scan = node->ss.ss_currentScanDesc;
+	pagescan = node->pagescan;
 	tbm = node->tbm;
 	if (pstate == NULL)
 		tbmiterator = node->tbmiterator;
@@ -192,8 +194,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 
 	for (;;)
 	{
-		Page		dp;
-		ItemId		lp;
+		StorageTuple tuple;
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -220,7 +221,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 			 * least AccessShareLock on the table before performing any of the
 			 * indexscans, but let's be safe.)
 			 */
-			if (tbmres->blockno >= scan->rs_nblocks)
+			if (tbmres->blockno >= pagescan->rs_nblocks)
 			{
 				node->tbmres = tbmres = NULL;
 				continue;
@@ -243,14 +244,14 @@ BitmapHeapNext(BitmapHeapScanState *node)
 				 * The number of tuples on this page is put into
 				 * scan->rs_ntuples; note we don't fill scan->rs_vistuples.
 				 */
-				scan->rs_ntuples = tbmres->ntuples;
+				pagescan->rs_ntuples = tbmres->ntuples;
 			}
 			else
 			{
 				/*
 				 * Fetch the current heap page and identify candidate tuples.
 				 */
-				bitgetpage(scan, tbmres);
+				bitgetpage(node, tbmres);
 			}
 
 			if (tbmres->ntuples >= 0)
@@ -261,7 +262,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 			/*
 			 * Set rs_cindex to first slot to examine
 			 */
-			scan->rs_cindex = 0;
+			pagescan->rs_cindex = 0;
 
 			/* Adjust the prefetch target */
 			BitmapAdjustPrefetchTarget(node);
@@ -271,7 +272,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 			/*
 			 * Continuing in previously obtained page; advance rs_cindex
 			 */
-			scan->rs_cindex++;
+			pagescan->rs_cindex++;
 
 #ifdef USE_PREFETCH
 
@@ -298,7 +299,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 		/*
 		 * Out of range?  If so, nothing more to look at on this page
 		 */
-		if (scan->rs_cindex < 0 || scan->rs_cindex >= scan->rs_ntuples)
+		if (pagescan->rs_cindex < 0 || pagescan->rs_cindex >= pagescan->rs_ntuples)
 		{
 			node->tbmres = tbmres = NULL;
 			continue;
@@ -325,23 +326,14 @@ BitmapHeapNext(BitmapHeapScanState *node)
 			/*
 			 * Okay to fetch the tuple.
 			 */
-			targoffset = scan->rs_vistuples[scan->rs_cindex];
-			dp = (Page) BufferGetPage(scan->rs_cbuf);
-			lp = PageGetItemId(dp, targoffset);
-			Assert(ItemIdIsNormal(lp));
-
-			scan->rs_ctup.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lp);
-			scan->rs_ctup.t_len = ItemIdGetLength(lp);
-			scan->rs_ctup.t_tableOid = scan->rs_rd->rd_id;
-			ItemPointerSet(&scan->rs_ctup.t_self, tbmres->blockno, targoffset);
-
-			pgstat_count_heap_fetch(scan->rs_rd);
+			targoffset = pagescan->rs_vistuples[pagescan->rs_cindex];
+			tuple = storage_fetch_tuple_from_offset(scan, tbmres->blockno, targoffset);
 
 			/*
 			 * Set up the result slot to point to this tuple.  Note that the
 			 * slot acquires a pin on the buffer.
 			 */
-			ExecStoreTuple(&scan->rs_ctup,
+			ExecStoreTuple(tuple,
 						   slot,
 						   scan->rs_cbuf,
 						   false);
@@ -383,8 +375,10 @@ BitmapHeapNext(BitmapHeapScanState *node)
  * interesting according to the bitmap, and visible according to the snapshot.
  */
 static void
-bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
+bitgetpage(BitmapHeapScanState *node, TBMIterateResult *tbmres)
 {
+	StorageScanDesc scan = node->ss.ss_currentScanDesc;
+	HeapPageScanDesc pagescan = node->pagescan;
 	BlockNumber page = tbmres->blockno;
 	Buffer		buffer;
 	Snapshot	snapshot;
@@ -393,7 +387,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 	/*
 	 * Acquire pin on the target heap page, trading in any pin we held before.
 	 */
-	Assert(page < scan->rs_nblocks);
+	Assert(page < pagescan->rs_nblocks);
 
 	scan->rs_cbuf = ReleaseAndReadBuffer(scan->rs_cbuf,
 										 scan->rs_rd,
@@ -436,7 +430,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 			ItemPointerSet(&tid, page, offnum);
 			if (storage_hot_search_buffer(&tid, scan->rs_rd, buffer, snapshot,
 										  &heapTuple, NULL, true))
-				scan->rs_vistuples[ntup++] = ItemPointerGetOffsetNumber(&tid);
+				pagescan->rs_vistuples[ntup++] = ItemPointerGetOffsetNumber(&tid);
 		}
 	}
 	else
@@ -452,23 +446,20 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 		for (offnum = FirstOffsetNumber; offnum <= maxoff; offnum = OffsetNumberNext(offnum))
 		{
 			ItemId		lp;
-			HeapTupleData loctup;
+			StorageTuple loctup;
 			bool		valid;
 
 			lp = PageGetItemId(dp, offnum);
 			if (!ItemIdIsNormal(lp))
 				continue;
-			loctup.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lp);
-			loctup.t_len = ItemIdGetLength(lp);
-			loctup.t_tableOid = scan->rs_rd->rd_id;
-			ItemPointerSet(&loctup.t_self, page, offnum);
-			valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine, &loctup, snapshot, buffer);
+			loctup = storage_fetch_tuple_from_offset(scan, page, offnum);
+			valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine, loctup, snapshot, buffer);
 			if (valid)
 			{
-				scan->rs_vistuples[ntup++] = offnum;
-				PredicateLockTuple(scan->rs_rd, &loctup, snapshot);
+				pagescan->rs_vistuples[ntup++] = offnum;
+				PredicateLockTuple(scan->rs_rd, loctup, snapshot);
 			}
-			CheckForSerializableConflictOut(valid, scan->rs_rd, &loctup,
+			CheckForSerializableConflictOut(valid, scan->rs_rd, loctup,
 											buffer, snapshot);
 		}
 	}
@@ -476,7 +467,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
 
 	Assert(ntup <= MaxHeapTuplesPerPage);
-	scan->rs_ntuples = ntup;
+	pagescan->rs_ntuples = ntup;
 }
 
 /*
@@ -602,7 +593,7 @@ BitmapAdjustPrefetchTarget(BitmapHeapScanState *node)
  * BitmapPrefetch - Prefetch, if prefetch_pages are behind prefetch_target
  */
 static inline void
-BitmapPrefetch(BitmapHeapScanState *node, HeapScanDesc scan)
+BitmapPrefetch(BitmapHeapScanState *node, StorageScanDesc scan)
 {
 #ifdef USE_PREFETCH
 	ParallelBitmapHeapState *pstate = node->pstate;
@@ -793,7 +784,7 @@ void
 ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 {
 	Relation	relation;
-	HeapScanDesc scanDesc;
+	StorageScanDesc scanDesc;
 
 	/*
 	 * extract information from the node
@@ -958,6 +949,8 @@ ExecInitBitmapHeapScan(BitmapHeapScan *node, EState *estate, int eflags)
 															0,
 															NULL);
 
+	scanstate->pagescan = storageam_get_heappagescandesc(scanstate->ss.ss_currentScanDesc);
+
 	/*
 	 * get the scan type from the relation descriptor.
 	 */
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index 7e990dc35e..064b655aea 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -31,9 +31,7 @@ static TupleTableSlot *SampleNext(SampleScanState *node);
 static void tablesample_init(SampleScanState *scanstate);
 static StorageTuple tablesample_getnext(SampleScanState *scanstate);
 static bool SampleTupleVisible(StorageTuple tuple, OffsetNumber tupoffset,
-				   HeapScanDesc scan);
-
-/* hari */
+				   SampleScanState *scanstate);
 
 /* ----------------------------------------------------------------
  *						Scan Support
@@ -357,6 +355,7 @@ tablesample_init(SampleScanState *scanstate)
 									   scanstate->use_bulkread,
 									   allow_sync,
 									   scanstate->use_pagemode);
+		scanstate->pagescan = storageam_get_heappagescandesc(scanstate->ss.ss_currentScanDesc);
 	}
 	else
 	{
@@ -382,10 +381,11 @@ static StorageTuple
 tablesample_getnext(SampleScanState *scanstate)
 {
 	TsmRoutine *tsm = scanstate->tsmroutine;
-	HeapScanDesc scan = scanstate->ss.ss_currentScanDesc;
-	HeapTuple	tuple = &(scan->rs_ctup);
+	StorageScanDesc scan = scanstate->ss.ss_currentScanDesc;
+	HeapPageScanDesc pagescan = scanstate->pagescan;
+	StorageTuple tuple;
 	Snapshot	snapshot = scan->rs_snapshot;
-	bool		pagemode = scan->rs_pageatatime;
+	bool		pagemode = pagescan->rs_pageatatime;
 	BlockNumber blockno;
 	Page		page;
 	bool		all_visible;
@@ -396,10 +396,9 @@ tablesample_getnext(SampleScanState *scanstate)
 		/*
 		 * return null immediately if relation is empty
 		 */
-		if (scan->rs_nblocks == 0)
+		if (pagescan->rs_nblocks == 0)
 		{
 			Assert(!BufferIsValid(scan->rs_cbuf));
-			tuple->t_data = NULL;
 			return NULL;
 		}
 		if (tsm->NextSampleBlock)
@@ -407,13 +406,12 @@ tablesample_getnext(SampleScanState *scanstate)
 			blockno = tsm->NextSampleBlock(scanstate);
 			if (!BlockNumberIsValid(blockno))
 			{
-				tuple->t_data = NULL;
 				return NULL;
 			}
 		}
 		else
-			blockno = scan->rs_startblock;
-		Assert(blockno < scan->rs_nblocks);
+			blockno = pagescan->rs_startblock;
+		Assert(blockno < pagescan->rs_nblocks);
 		heapgetpage(scan, blockno);
 		scan->rs_inited = true;
 	}
@@ -456,14 +454,12 @@ tablesample_getnext(SampleScanState *scanstate)
 			if (!ItemIdIsNormal(itemid))
 				continue;
 
-			tuple->t_data = (HeapTupleHeader) PageGetItem(page, itemid);
-			tuple->t_len = ItemIdGetLength(itemid);
-			ItemPointerSet(&(tuple->t_self), blockno, tupoffset);
+			tuple = storage_fetch_tuple_from_offset(scan, blockno, tupoffset);
 
 			if (all_visible)
 				visible = true;
 			else
-				visible = SampleTupleVisible(tuple, tupoffset, scan);
+				visible = SampleTupleVisible(tuple, tupoffset, scanstate);
 
 			/* in pagemode, heapgetpage did this for us */
 			if (!pagemode)
@@ -494,14 +490,14 @@ tablesample_getnext(SampleScanState *scanstate)
 		if (tsm->NextSampleBlock)
 		{
 			blockno = tsm->NextSampleBlock(scanstate);
-			Assert(!scan->rs_syncscan);
+			Assert(!pagescan->rs_syncscan);
 			finished = !BlockNumberIsValid(blockno);
 		}
 		else
 		{
 			/* Without NextSampleBlock, just do a plain forward seqscan. */
 			blockno++;
-			if (blockno >= scan->rs_nblocks)
+			if (blockno >= pagescan->rs_nblocks)
 				blockno = 0;
 
 			/*
@@ -514,10 +510,10 @@ tablesample_getnext(SampleScanState *scanstate)
 			 * a little bit backwards on every invocation, which is confusing.
 			 * We don't guarantee any specific ordering in general, though.
 			 */
-			if (scan->rs_syncscan)
+			if (pagescan->rs_syncscan)
 				ss_report_location(scan->rs_rd, blockno);
 
-			finished = (blockno == scan->rs_startblock);
+			finished = (blockno == pagescan->rs_startblock);
 		}
 
 		/*
@@ -529,12 +525,11 @@ tablesample_getnext(SampleScanState *scanstate)
 				ReleaseBuffer(scan->rs_cbuf);
 			scan->rs_cbuf = InvalidBuffer;
 			scan->rs_cblock = InvalidBlockNumber;
-			tuple->t_data = NULL;
 			scan->rs_inited = false;
 			return NULL;
 		}
 
-		Assert(blockno < scan->rs_nblocks);
+		Assert(blockno < pagescan->rs_nblocks);
 		heapgetpage(scan, blockno);
 
 		/* Re-establish state for new page */
@@ -549,16 +544,19 @@ tablesample_getnext(SampleScanState *scanstate)
 	/* Count successfully-fetched tuples as heap fetches */
 	pgstat_count_heap_getnext(scan->rs_rd);
 
-	return &(scan->rs_ctup);
+	return tuple;
 }
 
 /*
  * Check visibility of the tuple.
  */
 static bool
-SampleTupleVisible(StorageTuple tuple, OffsetNumber tupoffset, HeapScanDesc scan) //hari
+SampleTupleVisible(StorageTuple tuple, OffsetNumber tupoffset, SampleScanState *scanstate)
 {
-	if (scan->rs_pageatatime)
+	StorageScanDesc scan = scanstate->ss.ss_currentScanDesc;
+	HeapPageScanDesc pagescan = scanstate->pagescan;
+
+	if (pagescan->rs_pageatatime)
 	{
 		/*
 		 * In pageatatime mode, heapgetpage() already did visibility checks,
@@ -570,12 +568,12 @@ SampleTupleVisible(StorageTuple tuple, OffsetNumber tupoffset, HeapScanDesc scan
 		 * gain to justify the restriction.
 		 */
 		int			start = 0,
-					end = scan->rs_ntuples - 1;
+					end = pagescan->rs_ntuples - 1;
 
 		while (start <= end)
 		{
 			int			mid = (start + end) / 2;
-			OffsetNumber curoffset = scan->rs_vistuples[mid];
+			OffsetNumber curoffset = pagescan->rs_vistuples[mid];
 
 			if (tupoffset == curoffset)
 				return true;
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index 7cfc2107f6..32484ea84c 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -317,9 +317,10 @@ void
 ExecSeqScanReInitializeDSM(SeqScanState *node,
 						   ParallelContext *pcxt)
 {
-	HeapScanDesc scan = node->ss.ss_currentScanDesc;
+	ParallelHeapScanDesc pscan;
 
-	heap_parallelscan_reinitialize(scan->rs_parallel);
+	pscan = storageam_get_parallelheapscandesc(node->ss.ss_currentScanDesc);
+	heap_parallelscan_reinitialize(pscan);
 }
 
 /* ----------------------------------------------------------------
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index f7438714c4..1d8caf3c79 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -21,6 +21,7 @@
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/nbtree.h"
+#include "access/storageamapi.h"
 #include "access/sysattr.h"
 #include "access/transam.h"
 #include "access/xlog.h"
@@ -253,7 +254,8 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 			info->amsearchnulls = amroutine->amsearchnulls;
 			info->amcanparallel = amroutine->amcanparallel;
 			info->amhasgettuple = (amroutine->amgettuple != NULL);
-			info->amhasgetbitmap = (amroutine->amgetbitmap != NULL);
+			info->amhasgetbitmap = ((amroutine->amgetbitmap != NULL)
+									&& (relation->rd_stamroutine->scan_get_heappagescandesc != NULL));
 			info->amcostestimate = amroutine->amcostestimate;
 			Assert(info->amcostestimate != NULL);
 
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 0d2e1733bf..ee005d4168 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1865,7 +1865,7 @@ get_database_list(void)
 {
 	List	   *dblist = NIL;
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tup;
 	MemoryContext resultcxt;
 
@@ -1931,7 +1931,7 @@ do_autovacuum(void)
 {
 	Relation	classRel;
 	HeapTuple	tuple;
-	HeapScanDesc relScan;
+	StorageScanDesc relScan;
 	Form_pg_database dbForm;
 	List	   *table_oids = NIL;
 	List	   *orphan_oids = NIL;
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 2b56b740a2..84f8411b97 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -1207,7 +1207,7 @@ pgstat_collect_oids(Oid catalogid)
 	HTAB	   *htab;
 	HASHCTL		hash_ctl;
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tup;
 	Snapshot	snapshot;
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 83ec2dfcbe..73bb1770f1 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -107,7 +107,7 @@ get_subscription_list(void)
 {
 	List	   *res = NIL;
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tup;
 	MemoryContext resultcxt;
 
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index c6791c758c..4140ff7603 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -420,7 +420,7 @@ DefineQueryRewrite(const char *rulename,
 		if (event_relation->rd_rel->relkind != RELKIND_VIEW &&
 			event_relation->rd_rel->relkind != RELKIND_MATVIEW)
 		{
-			HeapScanDesc scanDesc;
+			StorageScanDesc scanDesc;
 			Snapshot	snapshot;
 
 			if (event_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 03b7cc76d7..42a35824e5 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -1208,7 +1208,7 @@ static bool
 ThereIsAtLeastOneRole(void)
 {
 	Relation	pg_authid_rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	bool		result;
 
 	pg_authid_rel = heap_open(AuthIdRelationId, AccessShareLock);
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index be62547195..400b11dfd4 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -98,6 +98,8 @@ extern Relation heap_openrv_extended(const RangeVar *relation,
 #define heap_close(r,l)  relation_close(r,l)
 
 /* struct definitions appear in relscan.h */
+typedef struct HeapPageScanDescData *HeapPageScanDesc;
+typedef struct StorageScanDescData *StorageScanDesc;
 typedef struct HeapScanDescData *HeapScanDesc;
 typedef struct ParallelHeapScanDescData *ParallelHeapScanDesc;
 
@@ -107,25 +109,25 @@ typedef struct ParallelHeapScanDescData *ParallelHeapScanDesc;
  */
 #define HeapScanIsValid(scan) PointerIsValid(scan)
 
-extern HeapScanDesc heap_beginscan(Relation relation, Snapshot snapshot,
-			   int nkeys, ScanKey key,
-			   ParallelHeapScanDesc parallel_scan,
-			   bool allow_strat,
-			   bool allow_sync,
-			   bool allow_pagemode,
-			   bool is_bitmapscan,
-			   bool is_samplescan,
-			   bool temp_snap);
-extern void heap_setscanlimits(HeapScanDesc scan, BlockNumber startBlk,
+extern StorageScanDesc heap_beginscan(Relation relation, Snapshot snapshot,
+									  int nkeys, ScanKey key,
+									  ParallelHeapScanDesc parallel_scan,
+									  bool allow_strat,
+									  bool allow_sync,
+									  bool allow_pagemode,
+									  bool is_bitmapscan,
+									  bool is_samplescan,
+									  bool temp_snap);
+extern void heap_setscanlimits(StorageScanDesc scan, BlockNumber startBlk,
 				   BlockNumber endBlk);
-extern void heapgetpage(HeapScanDesc scan, BlockNumber page);
-extern void heap_rescan(HeapScanDesc scan, ScanKey key, bool set_params,
+extern void heapgetpage(StorageScanDesc scan, BlockNumber page);
+extern void heap_rescan(StorageScanDesc scan, ScanKey key, bool set_params,
 			bool allow_strat, bool allow_sync, bool allow_pagemode);
-extern void heap_rescan_set_params(HeapScanDesc scan, ScanKey key,
+extern void heap_rescan_set_params(StorageScanDesc scan, ScanKey key,
 					   bool allow_strat, bool allow_sync, bool allow_pagemode);
-extern void heap_endscan(HeapScanDesc scan);
-extern StorageTuple heap_getnext(HeapScanDesc scan, ScanDirection direction);
-extern TupleTableSlot *heap_getnextslot(HeapScanDesc sscan, ScanDirection direction,
+extern void heap_endscan(StorageScanDesc scan);
+extern StorageTuple heap_getnext(StorageScanDesc scan, ScanDirection direction);
+extern TupleTableSlot *heap_getnextslot(StorageScanDesc sscan, ScanDirection direction,
 				 TupleTableSlot *slot);
 extern Size heap_parallelscan_estimate(Snapshot snapshot);
 extern void heap_parallelscan_initialize(ParallelHeapScanDesc target,
@@ -180,7 +182,7 @@ extern void simple_heap_update(Relation relation, ItemPointer otid,
 				   HeapTuple tup);
 
 extern void heap_sync(Relation relation);
-extern void heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot);
+extern void heap_update_snapshot(StorageScanDesc scan, Snapshot snapshot);
 
 /* in heap/heapam_visibility.c */
 extern HTSU_Result HeapTupleSatisfiesUpdate(StorageTuple stup, CommandId curcid,
diff --git a/src/include/access/relscan.h b/src/include/access/relscan.h
index 147f862a2b..2651fd73d7 100644
--- a/src/include/access/relscan.h
+++ b/src/include/access/relscan.h
@@ -16,6 +16,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/storageam.h"
 #include "access/htup_details.h"
 #include "access/itup.h"
 #include "access/tupdesc.h"
@@ -42,40 +43,54 @@ typedef struct ParallelHeapScanDescData
 	char		phs_snapshot_data[FLEXIBLE_ARRAY_MEMBER];
 }			ParallelHeapScanDescData;
 
-typedef struct HeapScanDescData
+typedef struct StorageScanDescData
 {
 	/* scan parameters */
 	Relation	rs_rd;			/* heap relation descriptor */
 	Snapshot	rs_snapshot;	/* snapshot to see */
 	int			rs_nkeys;		/* number of scan keys */
 	ScanKey		rs_key;			/* array of scan key descriptors */
-	bool		rs_bitmapscan;	/* true if this is really a bitmap scan */
-	bool		rs_samplescan;	/* true if this is really a sample scan */
+
+	/* scan current state */
+	bool		rs_inited;		/* false = scan not init'd yet */
+	BlockNumber rs_cblock;		/* current block # in scan, if any */
+	Buffer		rs_cbuf;		/* current buffer in scan, if any */
+}			StorageScanDescData;
+
+typedef struct HeapPageScanDescData
+{
 	bool		rs_pageatatime; /* verify visibility page-at-a-time? */
-	bool		rs_allow_strat; /* allow or disallow use of access strategy */
-	bool		rs_allow_sync;	/* allow or disallow use of syncscan */
-	bool		rs_temp_snap;	/* unregister snapshot at scan end? */
 
 	/* state set up at initscan time */
 	BlockNumber rs_nblocks;		/* total number of blocks in rel */
 	BlockNumber rs_startblock;	/* block # to start at */
 	BlockNumber rs_numblocks;	/* max number of blocks to scan */
+
 	/* rs_numblocks is usually InvalidBlockNumber, meaning "scan whole rel" */
 	BufferAccessStrategy rs_strategy;	/* access strategy for reads */
 	bool		rs_syncscan;	/* report location to syncscan logic? */
 
-	/* scan current state */
-	bool		rs_inited;		/* false = scan not init'd yet */
-	HeapTupleData rs_ctup;		/* current tuple in scan, if any */
-	BlockNumber rs_cblock;		/* current block # in scan, if any */
-	Buffer		rs_cbuf;		/* current buffer in scan, if any */
-	/* NB: if rs_cbuf is not InvalidBuffer, we hold a pin on that buffer */
-	ParallelHeapScanDesc rs_parallel;	/* parallel scan information */
-
 	/* these fields only used in page-at-a-time mode and for bitmap scans */
 	int			rs_cindex;		/* current tuple's index in vistuples */
 	int			rs_ntuples;		/* number of visible tuples on page */
 	OffsetNumber rs_vistuples[MaxHeapTuplesPerPage];	/* their offsets */
+}			HeapPageScanDescData;
+
+typedef struct HeapScanDescData
+{
+	/* scan parameters */
+	StorageScanDescData rs_scan;	/* */
+	HeapPageScanDescData rs_pagescan;
+	bool		rs_bitmapscan;	/* true if this is really a bitmap scan */
+	bool		rs_samplescan;	/* true if this is really a sample scan */
+	bool		rs_allow_strat; /* allow or disallow use of access strategy */
+	bool		rs_allow_sync;	/* allow or disallow use of syncscan */
+	bool		rs_temp_snap;	/* unregister snapshot at scan end? */
+
+	HeapTupleData rs_ctup;		/* current tuple in scan, if any */
+
+	/* NB: if rs_cbuf is not InvalidBuffer, we hold a pin on that buffer */
+	ParallelHeapScanDesc rs_parallel;	/* parallel scan information */
 }			HeapScanDescData;
 
 /*
@@ -149,12 +164,12 @@ typedef struct ParallelIndexScanDescData
 	char		ps_snapshot_data[FLEXIBLE_ARRAY_MEMBER];
 }			ParallelIndexScanDescData;
 
-/* Struct for heap-or-index scans of system tables */
+/* Struct for storage-or-index scans of system tables */
 typedef struct SysScanDescData
 {
 	Relation	heap_rel;		/* catalog being scanned */
 	Relation	irel;			/* NULL if doing heap scan */
-	HeapScanDesc scan;			/* only valid in heap-scan case */
+	StorageScanDesc scan;		/* only valid in storage-scan case */
 	IndexScanDesc iscan;		/* only valid in index-scan case */
 	Snapshot	snapshot;		/* snapshot to unregister at end of scan */
 }			SysScanDescData;
diff --git a/src/include/access/storage_common.h b/src/include/access/storage_common.h
index 7d70de4e33..ff28b4dfec 100644
--- a/src/include/access/storage_common.h
+++ b/src/include/access/storage_common.h
@@ -27,7 +27,6 @@
 
 /* A physical tuple coming from a storage AM scan */
 typedef void *StorageTuple;
-typedef void *StorageScanDesc;
 
 /*
  * slot storage routine functions
diff --git a/src/include/access/storageam.h b/src/include/access/storageam.h
index e79a24055d..5edaf84f49 100644
--- a/src/include/access/storageam.h
+++ b/src/include/access/storageam.h
@@ -40,29 +40,31 @@ typedef List *(*InsertIndexTuples) (TupleTableSlot *slot, EState *estate, bool n
 									bool *specConflict, List *arbiterIndexes);
 
 
-extern HeapScanDesc storage_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan);
-
-extern void storage_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
-extern HeapScanDesc storage_beginscan(Relation relation, Snapshot snapshot,
-				  int nkeys, ScanKey key);
-extern HeapScanDesc storage_beginscan_catalog(Relation relation, int nkeys, ScanKey key);
-extern HeapScanDesc storage_beginscan_strat(Relation relation, Snapshot snapshot,
-						int nkeys, ScanKey key,
-						bool allow_strat, bool allow_sync);
-extern HeapScanDesc storage_beginscan_bm(Relation relation, Snapshot snapshot,
-					 int nkeys, ScanKey key);
-extern HeapScanDesc storage_beginscan_sampling(Relation relation, Snapshot snapshot,
-						   int nkeys, ScanKey key,
-						   bool allow_strat, bool allow_sync, bool allow_pagemode);
-
-extern void storage_endscan(HeapScanDesc scan);
-extern void storage_rescan(HeapScanDesc scan, ScanKey key);
-extern void storage_rescan_set_params(HeapScanDesc scan, ScanKey key,
+extern StorageScanDesc storage_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan);
+extern ParallelHeapScanDesc storageam_get_parallelheapscandesc(StorageScanDesc sscan);
+extern HeapPageScanDesc storageam_get_heappagescandesc(StorageScanDesc sscan);
+extern void storage_setscanlimits(StorageScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
+extern StorageScanDesc storage_beginscan(Relation relation, Snapshot snapshot,
+										 int nkeys, ScanKey key);
+extern StorageScanDesc storage_beginscan_catalog(Relation relation, int nkeys, ScanKey key);
+extern StorageScanDesc storage_beginscan_strat(Relation relation, Snapshot snapshot,
+											   int nkeys, ScanKey key,
+											   bool allow_strat, bool allow_sync);
+extern StorageScanDesc storage_beginscan_bm(Relation relation, Snapshot snapshot,
+											int nkeys, ScanKey key);
+extern StorageScanDesc storage_beginscan_sampling(Relation relation, Snapshot snapshot,
+												  int nkeys, ScanKey key,
+												  bool allow_strat, bool allow_sync, bool allow_pagemode);
+
+extern void storage_endscan(StorageScanDesc scan);
+extern void storage_rescan(StorageScanDesc scan, ScanKey key);
+extern void storage_rescan_set_params(StorageScanDesc scan, ScanKey key,
 						  bool allow_strat, bool allow_sync, bool allow_pagemode);
-extern void storage_update_snapshot(HeapScanDesc scan, Snapshot snapshot);
+extern void storage_update_snapshot(StorageScanDesc scan, Snapshot snapshot);
 
-extern StorageTuple storage_getnext(HeapScanDesc sscan, ScanDirection direction);
-extern TupleTableSlot *storage_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *slot);
+extern StorageTuple storage_getnext(StorageScanDesc sscan, ScanDirection direction);
+extern TupleTableSlot *storage_getnextslot(StorageScanDesc sscan, ScanDirection direction, TupleTableSlot *slot);
+extern StorageTuple storage_fetch_tuple_from_offset(StorageScanDesc sscan, BlockNumber blkno, OffsetNumber offset);
 
 extern void storage_get_latest_tid(Relation relation,
 					   Snapshot snapshot,
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
index bda6b46a94..721c901486 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -84,32 +84,37 @@ typedef void (*SpeculativeAbort_function) (Relation rel,
 typedef void (*RelationSync_function) (Relation relation);
 
 
-typedef HeapScanDesc (*ScanBegin_function) (Relation relation,
-											Snapshot snapshot,
-											int nkeys, ScanKey key,
-											ParallelHeapScanDesc parallel_scan,
-											bool allow_strat,
-											bool allow_sync,
-											bool allow_pagemode,
-											bool is_bitmapscan,
-											bool is_samplescan,
-											bool temp_snap);
-typedef void (*ScanSetlimits_function) (HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
+typedef StorageScanDesc(*ScanBegin_function) (Relation relation,
+											  Snapshot snapshot,
+											  int nkeys, ScanKey key,
+											  ParallelHeapScanDesc parallel_scan,
+											  bool allow_strat,
+											  bool allow_sync,
+											  bool allow_pagemode,
+											  bool is_bitmapscan,
+											  bool is_samplescan,
+											  bool temp_snap);
+typedef ParallelHeapScanDesc (*ScanGetParallelheapscandesc_function) (StorageScanDesc scan);
+typedef HeapPageScanDesc(*ScanGetHeappagescandesc_function) (StorageScanDesc scan);
+
+typedef void (*ScanSetlimits_function) (StorageScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
 
 /* must return a TupleTableSlot? */
-typedef StorageTuple(*ScanGetnext_function) (HeapScanDesc scan,
+typedef StorageTuple(*ScanGetnext_function) (StorageScanDesc scan,
 											 ScanDirection direction);
 
-typedef TupleTableSlot *(*ScanGetnextSlot_function) (HeapScanDesc scan,
+typedef TupleTableSlot *(*ScanGetnextSlot_function) (StorageScanDesc scan,
 													 ScanDirection direction, TupleTableSlot *slot);
+typedef StorageTuple(*ScanFetchTupleFromOffset_function) (StorageScanDesc scan,
+														  BlockNumber blkno, OffsetNumber offset);
 
-typedef void (*ScanEnd_function) (HeapScanDesc scan);
+typedef void (*ScanEnd_function) (StorageScanDesc scan);
 
 
-typedef void (*ScanGetpage_function) (HeapScanDesc scan, BlockNumber page);
-typedef void (*ScanRescan_function) (HeapScanDesc scan, ScanKey key, bool set_params,
+typedef void (*ScanGetpage_function) (StorageScanDesc scan, BlockNumber page);
+typedef void (*ScanRescan_function) (StorageScanDesc scan, ScanKey key, bool set_params,
 									 bool allow_strat, bool allow_sync, bool allow_pagemode);
-typedef void (*ScanUpdateSnapshot_function) (HeapScanDesc scan, Snapshot snapshot);
+typedef void (*ScanUpdateSnapshot_function) (StorageScanDesc scan, Snapshot snapshot);
 
 typedef bool (*HotSearchBuffer_function) (ItemPointer tid, Relation relation,
 										  Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
@@ -160,9 +165,12 @@ typedef struct StorageAmRoutine
 
 	/* Operations on relation scans */
 	ScanBegin_function scan_begin;
+	ScanGetParallelheapscandesc_function scan_get_parallelheapscandesc;
+	ScanGetHeappagescandesc_function scan_get_heappagescandesc;
 	ScanSetlimits_function scansetlimits;
 	ScanGetnext_function scan_getnext;
 	ScanGetnextSlot_function scan_getnextslot;
+	ScanFetchTupleFromOffset_function scan_fetch_tuple_from_offset;
 	ScanEnd_function scan_end;
 	ScanGetpage_function scan_getpage;
 	ScanRescan_function scan_rescan;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index a956138e4a..cdaea6c545 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1112,7 +1112,7 @@ typedef struct ScanState
 {
 	PlanState	ps;				/* its first field is NodeTag */
 	Relation	ss_currentRelation;
-	HeapScanDesc ss_currentScanDesc;
+	StorageScanDesc ss_currentScanDesc;
 	TupleTableSlot *ss_ScanTupleSlot;
 } ScanState;
 
@@ -1133,6 +1133,7 @@ typedef struct SeqScanState
 typedef struct SampleScanState
 {
 	ScanState	ss;
+	HeapPageScanDesc pagescan;
 	List	   *args;			/* expr states for TABLESAMPLE params */
 	ExprState  *repeatable;		/* expr state for REPEATABLE expr */
 	/* use struct pointer to avoid including tsmapi.h here */
@@ -1361,6 +1362,7 @@ typedef struct ParallelBitmapHeapState
 typedef struct BitmapHeapScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
+	HeapPageScanDesc pagescan;
 	ExprState  *bitmapqualorig;
 	TIDBitmap  *tbm;
 	TBMIterator *tbmiterator;
-- 
2.15.0.windows.1



  [application/octet-stream] 0001-Change-Create-Access-method-to-include-storage-handl.patch (9.3K, ../../CAJrrPGdsQSRuq7N3W63CWXVkM_Acgc8HBphDvzOq1ik=Mt6j+Q@mail.gmail.com/4-0001-Change-Create-Access-method-to-include-storage-handl.patch)
  download | inline diff:
From 61f7fa4ee40432796985ff9b7ea554c009336034 Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Tue, 29 Aug 2017 19:45:30 +1000
Subject: [PATCH 1/8] Change Create Access method to include storage handler

Add the support of storage handler as an access method
---
 src/backend/commands/amcmds.c            | 17 ++++++++++++++---
 src/backend/parser/gram.y                | 11 +++++++++--
 src/backend/utils/adt/pseudotypes.c      |  1 +
 src/include/catalog/pg_am.h              |  1 +
 src/include/catalog/pg_proc.h            |  4 ++++
 src/include/catalog/pg_type.h            |  2 ++
 src/test/regress/expected/opr_sanity.out | 19 ++++++++++++++++---
 src/test/regress/sql/opr_sanity.sql      | 16 +++++++++++++---
 8 files changed, 60 insertions(+), 11 deletions(-)

diff --git a/src/backend/commands/amcmds.c b/src/backend/commands/amcmds.c
index 7e0a9aa0fd..33079c1c16 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -29,7 +29,7 @@
 #include "utils/syscache.h"
 
 
-static Oid	lookup_index_am_handler_func(List *handler_name, char amtype);
+static Oid	lookup_am_handler_func(List *handler_name, char amtype);
 static const char *get_am_type_string(char amtype);
 
 
@@ -72,7 +72,7 @@ CreateAccessMethod(CreateAmStmt *stmt)
 	/*
 	 * Get the handler function oid, verifying the AM type while at it.
 	 */
-	amhandler = lookup_index_am_handler_func(stmt->handler_name, stmt->amtype);
+	amhandler = lookup_am_handler_func(stmt->handler_name, stmt->amtype);
 
 	/*
 	 * Insert tuple into pg_am.
@@ -225,6 +225,8 @@ get_am_type_string(char amtype)
 	{
 		case AMTYPE_INDEX:
 			return "INDEX";
+		case AMTYPE_STORAGE:
+			return "STORAGE";
 		default:
 			/* shouldn't happen */
 			elog(ERROR, "invalid access method type '%c'", amtype);
@@ -239,7 +241,7 @@ get_am_type_string(char amtype)
  * This function either return valid function Oid or throw an error.
  */
 static Oid
-lookup_index_am_handler_func(List *handler_name, char amtype)
+lookup_am_handler_func(List *handler_name, char amtype)
 {
 	Oid			handlerOid;
 	static const Oid funcargtypes[1] = {INTERNALOID};
@@ -263,6 +265,15 @@ lookup_index_am_handler_func(List *handler_name, char amtype)
 								NameListToString(handler_name),
 								"index_am_handler")));
 			break;
+			/* XXX refactor duplicate error */
+		case AMTYPE_STORAGE:
+			if (get_func_rettype(handlerOid) != STORAGE_AM_HANDLEROID)
+				ereport(ERROR,
+						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+						 errmsg("function %s must return type %s",
+								NameListToString(handler_name),
+								"storage_am_handler")));
+			break;
 		default:
 			elog(ERROR, "unrecognized access method type \"%c\"", amtype);
 	}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ebfc94f896..25f397be6a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -321,6 +321,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		OptSchemaName
 %type <list>	OptSchemaEltList
 
+%type <chr>		am_type
+
 %type <boolean> TriggerForSpec TriggerForType
 %type <ival>	TriggerActionTime
 %type <list>	TriggerEvents TriggerOneEvent
@@ -5279,16 +5281,21 @@ row_security_cmd:
  *
  *****************************************************************************/
 
-CreateAmStmt: CREATE ACCESS METHOD name TYPE_P INDEX HANDLER handler_name
+CreateAmStmt: CREATE ACCESS METHOD name TYPE_P am_type HANDLER handler_name
 				{
 					CreateAmStmt *n = makeNode(CreateAmStmt);
 					n->amname = $4;
 					n->handler_name = $8;
-					n->amtype = AMTYPE_INDEX;
+					n->amtype = $6;
 					$$ = (Node *) n;
 				}
 		;
 
+am_type:
+			INDEX			{ $$ = AMTYPE_INDEX; }
+		|	STORAGE			{ $$ = AMTYPE_STORAGE; }
+		;
+
 /*****************************************************************************
  *
  *		QUERIES :
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index be793539a3..0a7e0a33e8 100644
--- a/src/backend/utils/adt/pseudotypes.c
+++ b/src/backend/utils/adt/pseudotypes.c
@@ -418,3 +418,4 @@ PSEUDOTYPE_DUMMY_IO_FUNCS(internal);
 PSEUDOTYPE_DUMMY_IO_FUNCS(opaque);
 PSEUDOTYPE_DUMMY_IO_FUNCS(anyelement);
 PSEUDOTYPE_DUMMY_IO_FUNCS(anynonarray);
+PSEUDOTYPE_DUMMY_IO_FUNCS(storage_am_handler);
diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h
index e021f5b894..dd9c263ade 100644
--- a/src/include/catalog/pg_am.h
+++ b/src/include/catalog/pg_am.h
@@ -59,6 +59,7 @@ typedef FormData_pg_am *Form_pg_am;
  * ----------------
  */
 #define AMTYPE_INDEX					'i' /* index access method */
+#define AMTYPE_STORAGE                  's' /* storage access method */
 
 /* ----------------
  *		initial contents of pg_am
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index c969375981..1449a9cfd8 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3877,6 +3877,10 @@ DATA(insert OID = 326  (  index_am_handler_in	PGNSP PGUID 12 1 0 0 0 f f f f f f
 DESCR("I/O");
 DATA(insert OID = 327  (  index_am_handler_out	PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2275 "325" _null_ _null_ _null_ _null_ _null_ index_am_handler_out _null_ _null_ _null_ ));
 DESCR("I/O");
+DATA(insert OID = 3425  (  storage_am_handler_in	PGNSP PGUID 12 1 0 0 0 f f f f f f i s 1 0 3998 "2275" _null_ _null_ _null_ _null_ _null_ storage_am_handler_in _null_ _null_ _null_ ));
+DESCR("I/O");
+DATA(insert OID = 3426  (  storage_am_handler_out	PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2275 "3998" _null_ _null_ _null_ _null_ _null_ storage_am_handler_out _null_ _null_ _null_ ));
+DESCR("I/O");
 DATA(insert OID = 3311 (  tsm_handler_in	PGNSP PGUID 12 1 0 0 0 f f f f f f i s 1 0 3310 "2275" _null_ _null_ _null_ _null_ _null_ tsm_handler_in _null_ _null_ _null_ ));
 DESCR("I/O");
 DATA(insert OID = 3312 (  tsm_handler_out	PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2275 "3310" _null_ _null_ _null_ _null_ _null_ tsm_handler_out _null_ _null_ _null_ ));
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index e3551440a0..750b8b4533 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -708,6 +708,8 @@ DATA(insert OID = 3115 ( fdw_handler	PGNSP PGUID  4 t p P f t \054 0 0 0 fdw_han
 #define FDW_HANDLEROID	3115
 DATA(insert OID = 325 ( index_am_handler	PGNSP PGUID  4 t p P f t \054 0 0 0 index_am_handler_in index_am_handler_out - - - - - i p f 0 -1 0 0 _null_ _null_ _null_ ));
 #define INDEX_AM_HANDLEROID 325
+DATA(insert OID = 3998 ( storage_am_handler	PGNSP PGUID 4 t p P f t \054 0 0 0 storage_am_handler_in storage_am_handler_out - - - - - i p f 0 -1 0 0 _null_ _null_ _null_ ));
+#define STORAGE_AM_HANDLEROID	3998
 DATA(insert OID = 3310 ( tsm_handler	PGNSP PGUID  4 t p P f t \054 0 0 0 tsm_handler_in tsm_handler_out - - - - - i p f 0 -1 0 0 _null_ _null_ _null_ ));
 #define TSM_HANDLEROID	3310
 DATA(insert OID = 3831 ( anyrange		PGNSP PGUID  -1 f p P f t \054 0 0 0 anyrange_in anyrange_out - - - - - d x f 0 -1 0 0 _null_ _null_ _null_ ));
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 684f7f20a8..3113966415 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1713,11 +1713,24 @@ WHERE p1.amhandler = 0;
 -----+--------
 (0 rows)
 
--- Check for amhandler functions with the wrong signature
+-- Check for index amhandler functions with the wrong signature
 SELECT p1.oid, p1.amname, p2.oid, p2.proname
 FROM pg_am AS p1, pg_proc AS p2
-WHERE p2.oid = p1.amhandler AND
-    (p2.prorettype != 'index_am_handler'::regtype OR p2.proretset
+WHERE p2.oid = p1.amhandler AND p1.amtype = 'i' AND
+    (p2.prorettype != 'index_am_handler'::regtype
+     OR p2.proretset
+     OR p2.pronargs != 1
+     OR p2.proargtypes[0] != 'internal'::regtype);
+ oid | amname | oid | proname 
+-----+--------+-----+---------
+(0 rows)
+
+-- Check for storage amhandler functions with the wrong signature
+SELECT p1.oid, p1.amname, p2.oid, p2.proname
+FROM pg_am AS p1, pg_proc AS p2
+WHERE p2.oid = p1.amhandler AND p1.amtype = 's' AND
+    (p2.prorettype != 'storage_am_handler'::regtype
+     OR p2.proretset
      OR p2.pronargs != 1
      OR p2.proargtypes[0] != 'internal'::regtype);
  oid | amname | oid | proname 
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index e8fdf8454d..bb1570c94f 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -1155,15 +1155,25 @@ SELECT p1.oid, p1.amname
 FROM pg_am AS p1
 WHERE p1.amhandler = 0;
 
--- Check for amhandler functions with the wrong signature
+-- Check for index amhandler functions with the wrong signature
 
 SELECT p1.oid, p1.amname, p2.oid, p2.proname
 FROM pg_am AS p1, pg_proc AS p2
-WHERE p2.oid = p1.amhandler AND
-    (p2.prorettype != 'index_am_handler'::regtype OR p2.proretset
+WHERE p2.oid = p1.amhandler AND p1.amtype = 'i' AND
+    (p2.prorettype != 'index_am_handler'::regtype
+     OR p2.proretset
      OR p2.pronargs != 1
      OR p2.proargtypes[0] != 'internal'::regtype);
 
+-- Check for storage amhandler functions with the wrong signature
+
+SELECT p1.oid, p1.amname, p2.oid, p2.proname
+FROM pg_am AS p1, pg_proc AS p2
+WHERE p2.oid = p1.amhandler AND p1.amtype = 's' AND
+    (p2.prorettype != 'storage_am_handler'::regtype
+     OR p2.proretset
+     OR p2.pronargs != 1
+     OR p2.proargtypes[0] != 'internal'::regtype);
 
 -- **************** pg_amop ****************
 
-- 
2.15.0.windows.1



  [application/octet-stream] 0002-Storage-AM-folder-and-init-functions.patch (11.0K, ../../CAJrrPGdsQSRuq7N3W63CWXVkM_Acgc8HBphDvzOq1ik=Mt6j+Q@mail.gmail.com/5-0002-Storage-AM-folder-and-init-functions.patch)
  download | inline diff:
From faa7a8c667484ee8841b131e6b6b59a24be21d23 Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Wed, 30 Aug 2017 12:41:15 +1000
Subject: [PATCH 2/8] Storage AM folder and init functions

---
 src/backend/access/Makefile               |   2 +-
 src/backend/access/heap/Makefile          |   3 +-
 src/backend/access/heap/heapam_storage.c  |  33 ++++++++++
 src/backend/access/storage/Makefile       |  17 +++++
 src/backend/access/storage/storageam.c    |  15 +++++
 src/backend/access/storage/storageamapi.c | 103 ++++++++++++++++++++++++++++++
 src/include/access/storageamapi.h         |  39 +++++++++++
 src/include/catalog/pg_am.h               |   3 +
 src/include/catalog/pg_proc.h             |   5 ++
 src/include/nodes/nodes.h                 |   1 +
 10 files changed, 219 insertions(+), 2 deletions(-)
 create mode 100644 src/backend/access/heap/heapam_storage.c
 create mode 100644 src/backend/access/storage/Makefile
 create mode 100644 src/backend/access/storage/storageam.c
 create mode 100644 src/backend/access/storage/storageamapi.c
 create mode 100644 src/include/access/storageamapi.h

diff --git a/src/backend/access/Makefile b/src/backend/access/Makefile
index bd93a6a8d1..e72ad6c86c 100644
--- a/src/backend/access/Makefile
+++ b/src/backend/access/Makefile
@@ -9,6 +9,6 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 SUBDIRS	    = brin common gin gist hash heap index nbtree rmgrdesc spgist \
-			  tablesample transam
+			  storage tablesample transam
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/heap/Makefile b/src/backend/access/heap/Makefile
index b83d496bcd..816f03a86f 100644
--- a/src/backend/access/heap/Makefile
+++ b/src/backend/access/heap/Makefile
@@ -12,6 +12,7 @@ subdir = src/backend/access/heap
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = heapam.o hio.o pruneheap.o rewriteheap.o syncscan.o tuptoaster.o visibilitymap.o
+OBJS = heapam.o hio.o heapam_storage.o pruneheap.o rewriteheap.o \
+	syncscan.o tuptoaster.o visibilitymap.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
new file mode 100644
index 0000000000..792e9cb436
--- /dev/null
+++ b/src/backend/access/heap/heapam_storage.c
@@ -0,0 +1,33 @@
+/*-------------------------------------------------------------------------
+ *
+ * heapam_storage.c
+ *	  heap storage access method code
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/heap/heapam_storage.c
+ *
+ *
+ * NOTES
+ *	  This file contains the heap_ routines which implement
+ *	  the POSTGRES heap access method used for all POSTGRES
+ *	  relations.
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/storageamapi.h"
+#include "utils/builtins.h"
+
+
+Datum
+heapam_storage_handler(PG_FUNCTION_ARGS)
+{
+	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
+
+	PG_RETURN_POINTER(amroutine);
+}
diff --git a/src/backend/access/storage/Makefile b/src/backend/access/storage/Makefile
new file mode 100644
index 0000000000..2a05c7ce66
--- /dev/null
+++ b/src/backend/access/storage/Makefile
@@ -0,0 +1,17 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+#    Makefile for access/storage
+#
+# IDENTIFICATION
+#    src/backend/access/storage/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/access/storage
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = storageam.o storageamapi.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/storage/storageam.c b/src/backend/access/storage/storageam.c
new file mode 100644
index 0000000000..8541c75782
--- /dev/null
+++ b/src/backend/access/storage/storageam.c
@@ -0,0 +1,15 @@
+/*-------------------------------------------------------------------------
+ *
+ * storageam.c
+ *	  storage access method code
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/storage/storageam.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
diff --git a/src/backend/access/storage/storageamapi.c b/src/backend/access/storage/storageamapi.c
new file mode 100644
index 0000000000..bcbe14588b
--- /dev/null
+++ b/src/backend/access/storage/storageamapi.c
@@ -0,0 +1,103 @@
+/*----------------------------------------------------------------------
+ *
+ * storageamapi.c
+ *		Support routines for API for Postgres storage access methods
+ *
+ * FIXME: looks like this should be in amapi.c.
+ *
+ * Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * src/backend/access/heap/storageamapi.c
+ *----------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/storageamapi.h"
+#include "catalog/pg_am.h"
+#include "catalog/pg_proc.h"
+#include "utils/syscache.h"
+#include "utils/memutils.h"
+
+
+/*
+ * GetStorageAmRoutine
+ *		Call the specified access method handler routine to get its
+ *		StorageAmRoutine struct, which will be palloc'd in the caller's
+ *		memory context.
+ */
+StorageAmRoutine *
+GetStorageAmRoutine(Oid amhandler)
+{
+	Datum		datum;
+	StorageAmRoutine *routine;
+
+	datum = OidFunctionCall0(amhandler);
+	routine = (StorageAmRoutine *) DatumGetPointer(datum);
+
+	if (routine == NULL || !IsA(routine, StorageAmRoutine))
+		elog(ERROR, "storage access method handler %u did not return a StorageAmRoutine struct",
+			 amhandler);
+
+	return routine;
+}
+
+/* A crock */
+StorageAmRoutine *
+GetHeapamStorageAmRoutine(void)
+{
+	Datum		datum;
+	static StorageAmRoutine * HeapamStorageAmRoutine = NULL;
+
+	if (HeapamStorageAmRoutine == NULL)
+	{
+		MemoryContext oldcxt;
+
+		oldcxt = MemoryContextSwitchTo(TopMemoryContext);
+		datum = OidFunctionCall0(HEAPAM_STORAGE_AM_HANDLER_OID);
+		HeapamStorageAmRoutine = (StorageAmRoutine *) DatumGetPointer(datum);
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	return HeapamStorageAmRoutine;
+}
+
+/*
+ * GetStorageAmRoutineByAmId - look up the handler of the storage access
+ * method with the given OID, and get its StorageAmRoutine struct.
+ */
+StorageAmRoutine *
+GetStorageAmRoutineByAmId(Oid amoid)
+{
+	regproc		amhandler;
+	HeapTuple	tuple;
+	Form_pg_am	amform;
+
+	/* Get handler function OID for the access method */
+	tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(amoid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for access method %u",
+			 amoid);
+	amform = (Form_pg_am) GETSTRUCT(tuple);
+
+	/* Check that it is a storage access method */
+	if (amform->amtype != AMTYPE_STORAGE)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("access method \"%s\" is not of type %s",
+						NameStr(amform->amname), "STORAGE")));
+
+	amhandler = amform->amhandler;
+
+	/* Complain if handler OID is invalid */
+	if (!RegProcedureIsValid(amhandler))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("storage access method \"%s\" does not have a handler",
+						NameStr(amform->amname))));
+
+	ReleaseSysCache(tuple);
+
+	/* And finally, call the handler function to get the API struct. */
+	return GetStorageAmRoutine(amhandler);
+}
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
new file mode 100644
index 0000000000..6fae4eea5c
--- /dev/null
+++ b/src/include/access/storageamapi.h
@@ -0,0 +1,39 @@
+/*---------------------------------------------------------------------
+ *
+ * storageamapi.h
+ *		API for Postgres storage access methods
+ *
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ *
+ * src/include/access/storageamapi.h
+ *---------------------------------------------------------------------
+ */
+#ifndef STORAGEAMAPI_H
+#define STORAGEAMAPI_H
+
+#include "nodes/nodes.h"
+#include "fmgr.h"
+
+/* A physical tuple coming from a storage AM scan */
+typedef void *StorageTuple;
+
+/*
+ * API struct for a storage AM.  Note this must be stored in a single palloc'd
+ * chunk of memory.
+ *
+ * XXX currently all functions are together in a single struct.  Would it be
+ * worthwhile to split the slot-accessor functions to a different struct?
+ * That way, MinimalTuple could be handled without a complete StorageAmRoutine
+ * for them -- it'd only have a few functions in TupleTableSlotAmRoutine or so.
+ */
+typedef struct StorageAmRoutine
+{
+	NodeTag		type;
+
+}			StorageAmRoutine;
+
+extern StorageAmRoutine * GetStorageAmRoutine(Oid amhandler);
+extern StorageAmRoutine * GetStorageAmRoutineByAmId(Oid amoid);
+extern StorageAmRoutine * GetHeapamStorageAmRoutine(void);
+
+#endif							/* STORAGEAMAPI_H */
diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h
index dd9c263ade..2c3e33c104 100644
--- a/src/include/catalog/pg_am.h
+++ b/src/include/catalog/pg_am.h
@@ -84,5 +84,8 @@ DESCR("SP-GiST index access method");
 DATA(insert OID = 3580 (  brin		brinhandler i ));
 DESCR("block range index (BRIN) access method");
 #define BRIN_AM_OID 3580
+DATA(insert OID = 4001 (  heapam         heapam_storage_handler s ));
+DESCR("heapam storage access method");
+#define HEAPAM_STORAGE_AM_OID 4001
 
 #endif							/* PG_AM_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 1449a9cfd8..b26b5900de 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -558,6 +558,11 @@ DESCR("convert int4 to float4");
 DATA(insert OID = 319 (  int4			   PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1  0 23 "700" _null_ _null_ _null_ _null_ _null_	ftoi4 _null_ _null_ _null_ ));
 DESCR("convert float4 to int4");
 
+/* Storage access method handlers */
+DATA(insert OID = 4002 (  heapam_storage_handler		PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 3998 "2281" _null_ _null_ _null_ _null_ _null_	heapam_storage_handler _null_ _null_ _null_ ));
+DESCR("row-oriented storage access method handler");
+#define HEAPAM_STORAGE_AM_HANDLER_OID	4002
+
 /* Index access method handlers */
 DATA(insert OID = 330 (  bthandler		PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 325 "2281" _null_ _null_ _null_ _null_ _null_	bthandler _null_ _null_ _null_ ));
 DESCR("btree index access method handler");
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index c5b5115f5b..927126f729 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -499,6 +499,7 @@ typedef enum NodeTag
 	T_InlineCodeBlock,			/* in nodes/parsenodes.h */
 	T_FdwRoutine,				/* in foreign/fdwapi.h */
 	T_IndexAmRoutine,			/* in access/amapi.h */
+	T_StorageAmRoutine,			/* in access/storageamapi.h */
 	T_TsmRoutine,				/* in access/tsmapi.h */
 	T_ForeignKeyCacheInfo		/* in utils/rel.h */
 } NodeTag;
-- 
2.15.0.windows.1



  [application/octet-stream] 0003-Adding-storageam-hanlder-to-relation-structure.patch (7.0K, ../../CAJrrPGdsQSRuq7N3W63CWXVkM_Acgc8HBphDvzOq1ik=Mt6j+Q@mail.gmail.com/6-0003-Adding-storageam-hanlder-to-relation-structure.patch)
  download | inline diff:
From 4166f6f7423bb5a0aa712d47669a4bbe0da9f083 Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Wed, 30 Aug 2017 12:49:46 +1000
Subject: [PATCH 3/8] Adding storageam hanlder to relation structure

And also the necessary functions to initialize
the storageam handler
---
 src/backend/utils/cache/relcache.c | 119 ++++++++++++++++++++++++++++++++++++-
 src/include/utils/rel.h            |  12 ++++
 src/include/utils/relcache.h       |   2 +
 3 files changed, 130 insertions(+), 3 deletions(-)

diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 12a5f157c0..dd70507c73 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -35,6 +35,7 @@
 #include "access/multixact.h"
 #include "access/nbtree.h"
 #include "access/reloptions.h"
+#include "access/storageamapi.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "access/xlog.h"
@@ -1371,10 +1372,27 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	}
 
 	/*
-	 * if it's an index, initialize index-related information
+	 * initialize access method information
 	 */
-	if (OidIsValid(relation->rd_rel->relam))
-		RelationInitIndexAccessInfo(relation);
+	switch (relation->rd_rel->relkind)
+	{
+		case RELKIND_INDEX:
+			Assert(relation->rd_rel->relkind != InvalidOid);
+			RelationInitIndexAccessInfo(relation);
+			break;
+		case RELKIND_RELATION:
+		case RELKIND_SEQUENCE:
+		case RELKIND_TOASTVALUE:
+		case RELKIND_VIEW:		/* Not exactly the storage, but underlying
+								 * tuple access, it is required */
+		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
+			RelationInitStorageAccessInfo(relation);
+			break;
+		default:
+			/* nothing to do in other cases */
+			break;
+	}
 
 	/* extract reloptions if any */
 	RelationParseRelOptions(relation, pg_class_tuple);
@@ -1871,6 +1889,71 @@ LookupOpclassInfo(Oid operatorClassOid,
 	return opcentry;
 }
 
+/*
+ * Fill in the StorageAmRoutine for a relation
+ *
+ * relation's rd_amhandler and rd_indexcxt (XXX?) must be valid already.
+ */
+static void
+InitStorageAmRoutine(Relation relation)
+{
+	StorageAmRoutine *cached,
+			   *tmp;
+
+	/*
+	 * Call the amhandler in current, short-lived memory context, just in case
+	 * it leaks anything (it probably won't, but let's be paranoid).
+	 */
+	tmp = GetStorageAmRoutine(relation->rd_amhandler);
+
+	/* XXX do we need a separate memory context for this? */
+	/* OK, now transfer the data into cache context */
+	cached = (StorageAmRoutine *) MemoryContextAlloc(CacheMemoryContext,
+													 sizeof(StorageAmRoutine));
+	memcpy(cached, tmp, sizeof(StorageAmRoutine));
+	relation->rd_stamroutine = cached;
+
+	pfree(tmp);
+}
+
+/*
+ * Initialize storage-access-method support data for a heap relation
+ */
+void
+RelationInitStorageAccessInfo(Relation relation)
+{
+	HeapTuple	tuple;
+	Form_pg_am	aform;
+
+	/*
+	 * Relations that don't have a catalogued storage access method use the
+	 * standard heapam module; otherwise a catalog lookup is in order.
+	 */
+	if (!OidIsValid(relation->rd_rel->relam))
+	{
+		relation->rd_amhandler = HEAPAM_STORAGE_AM_HANDLER_OID;
+	}
+	else
+	{
+		/*
+		 * Look up the storage access method, save the OID of its handler
+		 * function.
+		 */
+		tuple = SearchSysCache1(AMOID,
+								ObjectIdGetDatum(relation->rd_rel->relam));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for access method %u",
+				 relation->rd_rel->relam);
+		aform = (Form_pg_am) GETSTRUCT(tuple);
+		relation->rd_amhandler = aform->amhandler;
+		ReleaseSysCache(tuple);
+	}
+
+	/*
+	 * Now we can fetch the storage AM's API struct
+	 */
+	InitStorageAmRoutine(relation);
+}
 
 /*
  *		formrdesc
@@ -2029,6 +2112,11 @@ formrdesc(const char *relationName, Oid relationReltype,
 	 */
 	RelationInitPhysicalAddr(relation);
 
+	/*
+	 * initialize the storage am handler
+	 */
+	relation->rd_stamroutine = GetHeapamStorageAmRoutine();
+
 	/*
 	 * initialize the rel-has-index flag, using hardwired knowledge
 	 */
@@ -2357,6 +2445,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		pfree(relation->rd_pubactions);
 	if (relation->rd_options)
 		pfree(relation->rd_options);
+	if (relation->rd_stamroutine)
+		pfree(relation->rd_stamroutine);
 	if (relation->rd_indextuple)
 		pfree(relation->rd_indextuple);
 	if (relation->rd_indexcxt)
@@ -3371,6 +3461,14 @@ RelationBuildLocalRelation(const char *relname,
 
 	RelationInitPhysicalAddr(rel);
 
+	if (relkind == RELKIND_RELATION ||
+		relkind == RELKIND_MATVIEW ||
+		relkind == RELKIND_VIEW ||	/* Not exactly the storage, but underlying
+									 * tuple access, it is required */
+		relkind == RELKIND_PARTITIONED_TABLE ||
+		relkind == RELKIND_TOASTVALUE)
+		RelationInitStorageAccessInfo(rel);
+
 	/*
 	 * Okay to insert into the relcache hash table.
 	 *
@@ -3892,6 +3990,18 @@ RelationCacheInitializePhase3(void)
 			restart = true;
 		}
 
+		if (relation->rd_stamroutine == NULL &&
+			(relation->rd_rel->relkind == RELKIND_RELATION ||
+			 relation->rd_rel->relkind == RELKIND_MATVIEW ||
+			 relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+			 relation->rd_rel->relkind == RELKIND_TOASTVALUE))
+		{
+			RelationInitStorageAccessInfo(relation);
+			Assert(relation->rd_stamroutine != NULL);
+
+			restart = true;
+		}
+
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -5610,6 +5720,9 @@ load_relcache_init_file(bool shared)
 			if (rel->rd_isnailed)
 				nailed_rels++;
 
+			/* Load storage AM stuff */
+			RelationInitStorageAccessInfo(rel);
+
 			Assert(rel->rd_index == NULL);
 			Assert(rel->rd_indextuple == NULL);
 			Assert(rel->rd_indexcxt == NULL);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 68fd6fbd54..72a15db9a4 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -160,6 +160,12 @@ typedef struct RelationData
 	/* use "struct" here to avoid needing to include htup.h: */
 	struct HeapTupleData *rd_indextuple;	/* all of pg_index tuple */
 
+	/*
+	 * Underlying storage support
+	 */
+	Oid			rd_storageam;	/* OID of storage AM handler function */
+	struct StorageAmRoutine *rd_stamroutine;	/* storage AM's API struct */
+
 	/*
 	 * index access support info (used only for an index relation)
 	 *
@@ -436,6 +442,12 @@ typedef struct ViewOptions
  */
 #define RelationGetDescr(relation) ((relation)->rd_att)
 
+/*
+ * RelationGetStorageRoutine
+ *		Returns the storage AM routine for a relation.
+ */
+#define RelationGetStorageRoutine(relation) ((relation)->rd_stamroutine)
+
 /*
  * RelationGetRelationName
  *		Returns the rel's name.
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 29c6d9bae3..8f7529a7c5 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -76,6 +76,8 @@ extern void RelationInitIndexAccessInfo(Relation relation);
 struct PublicationActions;
 extern struct PublicationActions *GetRelationPublicationActions(Relation relation);
 
+extern void RelationInitStorageAccessInfo(Relation relation);
+
 /*
  * Routines to support ereport() reports of relation-related errors
  */
-- 
2.15.0.windows.1



  [application/octet-stream] 0004-Adding-tuple-visibility-function-to-storage-AM.patch (67.0K, ../../CAJrrPGdsQSRuq7N3W63CWXVkM_Acgc8HBphDvzOq1ik=Mt6j+Q@mail.gmail.com/7-0004-Adding-tuple-visibility-function-to-storage-AM.patch)
  download | inline diff:
From 662700ceb5bbe8f0c46448fdddbc18a603d54a90 Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Fri, 1 Dec 2017 14:49:18 +1100
Subject: [PATCH 4/8] Adding tuple visibility function to storage AM

Tuple visibility functions are now part of the
heap storage AM routine. The visibilty execution
procedure is changed accoridngly.

The snapshot satifies function is changed to an
enum to represent what type of snapshot is it
and this enum value is used to call the corresponding
visibilty function from the storage AM when the
visibilty of the tuple is required.

The common code is that is part of both server
and pluggable storages is now moved into storage_common.c
and storage_common.h files.
---
 contrib/pg_visibility/pg_visibility.c              |  11 +-
 contrib/pgrowlocks/pgrowlocks.c                    |   7 +-
 contrib/pgstattuple/pgstatapprox.c                 |   7 +-
 contrib/pgstattuple/pgstattuple.c                  |   3 +-
 src/backend/access/heap/Makefile                   |   2 +-
 src/backend/access/heap/heapam.c                   |  61 ++-
 src/backend/access/heap/heapam_storage.c           |  10 +
 .../tqual.c => access/heap/heapam_visibility.c}    | 448 +++++----------------
 src/backend/access/heap/pruneheap.c                |   4 +-
 src/backend/access/index/genam.c                   |   4 +-
 src/backend/access/storage/Makefile                |   2 +-
 src/backend/access/storage/storage_common.c        | 277 +++++++++++++
 src/backend/catalog/index.c                        |   6 +-
 src/backend/commands/analyze.c                     |   6 +-
 src/backend/commands/cluster.c                     |   3 +-
 src/backend/commands/vacuumlazy.c                  |   4 +-
 src/backend/executor/nodeBitmapHeapscan.c          |   2 +-
 src/backend/executor/nodeModifyTable.c             |   7 +-
 src/backend/executor/nodeSamplescan.c              |   3 +-
 src/backend/replication/logical/snapbuild.c        |   6 +-
 src/backend/storage/lmgr/predicate.c               |   2 +-
 src/backend/utils/adt/ri_triggers.c                |   2 +-
 src/backend/utils/time/Makefile                    |   2 +-
 src/backend/utils/time/snapmgr.c                   |  10 +-
 src/include/access/storage_common.h                |  98 +++++
 src/include/access/storageamapi.h                  |  12 +
 src/include/storage/bufmgr.h                       |   5 +-
 src/include/utils/snapshot.h                       |  14 +-
 src/include/utils/tqual.h                          |  54 +--
 29 files changed, 607 insertions(+), 465 deletions(-)
 rename src/backend/{utils/time/tqual.c => access/heap/heapam_visibility.c} (80%)
 create mode 100644 src/backend/access/storage/storage_common.c
 create mode 100644 src/include/access/storage_common.h

diff --git a/contrib/pg_visibility/pg_visibility.c b/contrib/pg_visibility/pg_visibility.c
index 2cc9575d9f..01ff3fed5d 100644
--- a/contrib/pg_visibility/pg_visibility.c
+++ b/contrib/pg_visibility/pg_visibility.c
@@ -11,6 +11,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/storageamapi.h"
 #include "access/visibilitymap.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage_xlog.h"
@@ -51,7 +52,7 @@ static vbits *collect_visibility_data(Oid relid, bool include_pd);
 static corrupt_items *collect_corrupt_items(Oid relid, bool all_visible,
 					  bool all_frozen);
 static void record_corrupt_item(corrupt_items *items, ItemPointer tid);
-static bool tuple_all_visible(HeapTuple tup, TransactionId OldestXmin,
+static bool tuple_all_visible(Relation rel, HeapTuple tup, TransactionId OldestXmin,
 				  Buffer buffer);
 static void check_relation_relkind(Relation rel);
 
@@ -656,7 +657,7 @@ collect_corrupt_items(Oid relid, bool all_visible, bool all_frozen)
 			 * the tuple to be all-visible.
 			 */
 			if (check_visible &&
-				!tuple_all_visible(&tuple, OldestXmin, buffer))
+				!tuple_all_visible(rel, &tuple, OldestXmin, buffer))
 			{
 				TransactionId RecomputedOldestXmin;
 
@@ -681,7 +682,7 @@ collect_corrupt_items(Oid relid, bool all_visible, bool all_frozen)
 				else
 				{
 					OldestXmin = RecomputedOldestXmin;
-					if (!tuple_all_visible(&tuple, OldestXmin, buffer))
+					if (!tuple_all_visible(rel, &tuple, OldestXmin, buffer))
 						record_corrupt_item(items, &tuple.t_self);
 				}
 			}
@@ -739,12 +740,12 @@ record_corrupt_item(corrupt_items *items, ItemPointer tid)
  * The buffer should contain the tuple and should be locked and pinned.
  */
 static bool
-tuple_all_visible(HeapTuple tup, TransactionId OldestXmin, Buffer buffer)
+tuple_all_visible(Relation rel, HeapTuple tup, TransactionId OldestXmin, Buffer buffer)
 {
 	HTSV_Result state;
 	TransactionId xmin;
 
-	state = HeapTupleSatisfiesVacuum(tup, OldestXmin, buffer);
+	state = rel->rd_stamroutine->snapshot_satisfiesVacuum(tup, OldestXmin, buffer);
 	if (state != HEAPTUPLE_LIVE)
 		return false;			/* all-visible implies live */
 
diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index eabca65bd2..830e74fd07 100644
--- a/contrib/pgrowlocks/pgrowlocks.c
+++ b/contrib/pgrowlocks/pgrowlocks.c
@@ -26,6 +26,7 @@
 
 #include "access/multixact.h"
 #include "access/relscan.h"
+#include "access/storageamapi.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
@@ -149,9 +150,9 @@ pgrowlocks(PG_FUNCTION_ARGS)
 		/* must hold a buffer lock to call HeapTupleSatisfiesUpdate */
 		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 
-		htsu = HeapTupleSatisfiesUpdate(tuple,
-										GetCurrentCommandId(false),
-										scan->rs_cbuf);
+		htsu = rel->rd_stamroutine->snapshot_satisfiesUpdate(tuple,
+															 GetCurrentCommandId(false),
+															 scan->rs_cbuf);
 		xmax = HeapTupleHeaderGetRawXmax(tuple->t_data);
 		infomask = tuple->t_data->t_infomask;
 
diff --git a/contrib/pgstattuple/pgstatapprox.c b/contrib/pgstattuple/pgstatapprox.c
index 5bf06138a5..284eabc970 100644
--- a/contrib/pgstattuple/pgstatapprox.c
+++ b/contrib/pgstattuple/pgstatapprox.c
@@ -12,12 +12,13 @@
  */
 #include "postgres.h"
 
-#include "access/visibilitymap.h"
 #include "access/transam.h"
+#include "access/visibilitymap.h"
 #include "access/xact.h"
 #include "access/multixact.h"
 #include "access/htup_details.h"
 #include "catalog/namespace.h"
+#include "commands/vacuum.h"
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
@@ -26,7 +27,7 @@
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
 #include "utils/tqual.h"
-#include "commands/vacuum.h"
+
 
 PG_FUNCTION_INFO_V1(pgstattuple_approx);
 PG_FUNCTION_INFO_V1(pgstattuple_approx_v1_5);
@@ -156,7 +157,7 @@ statapprox_heap(Relation rel, output_type *stat)
 			 * We count live and dead tuples, but we also need to add up
 			 * others in order to feed vac_estimate_reltuples.
 			 */
-			switch (HeapTupleSatisfiesVacuum(&tuple, OldestXmin, buf))
+			switch (rel->rd_stamroutine->snapshot_satisfiesVacuum(&tuple, OldestXmin, buf))
 			{
 				case HEAPTUPLE_RECENTLY_DEAD:
 					misc_count++;
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index 7ca1bb24d2..e098202f84 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -322,6 +322,7 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 	Buffer		buffer;
 	pgstattuple_type stat = {0};
 	SnapshotData SnapshotDirty;
+	StorageAmRoutine *method = rel->rd_stamroutine;
 
 	/* Disable syncscan because we assume we scan from block zero upwards */
 	scan = heap_beginscan_strat(rel, SnapshotAny, 0, NULL, true, false);
@@ -337,7 +338,7 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 		/* must hold a buffer lock to call HeapTupleSatisfiesVisibility */
 		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 
-		if (HeapTupleSatisfiesVisibility(tuple, &SnapshotDirty, scan->rs_cbuf))
+		if (HeapTupleSatisfiesVisibility(method, tuple, &SnapshotDirty, scan->rs_cbuf))
 		{
 			stat.tuple_len += tuple->t_len;
 			stat.tuple_count++;
diff --git a/src/backend/access/heap/Makefile b/src/backend/access/heap/Makefile
index 816f03a86f..f5c628395b 100644
--- a/src/backend/access/heap/Makefile
+++ b/src/backend/access/heap/Makefile
@@ -12,7 +12,7 @@ subdir = src/backend/access/heap
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = heapam.o hio.o heapam_storage.o pruneheap.o rewriteheap.o \
+OBJS = heapam.o hio.o heapam_storage.o heapam_visibility.o pruneheap.o rewriteheap.o \
 	syncscan.o tuptoaster.o visibilitymap.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 3acef279f4..d63afbaf69 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -45,6 +45,7 @@
 #include "access/multixact.h"
 #include "access/parallel.h"
 #include "access/relscan.h"
+#include "access/storageamapi.h"
 #include "access/sysattr.h"
 #include "access/transam.h"
 #include "access/tuptoaster.h"
@@ -438,7 +439,7 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 			if (all_visible)
 				valid = true;
 			else
-				valid = HeapTupleSatisfiesVisibility(&loctup, snapshot, buffer);
+				valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine, &loctup, snapshot, buffer);
 
 			CheckForSerializableConflictOut(valid, scan->rs_rd, &loctup,
 											buffer, snapshot);
@@ -653,7 +654,8 @@ heapgettup(HeapScanDesc scan,
 				/*
 				 * if current tuple qualifies, return it.
 				 */
-				valid = HeapTupleSatisfiesVisibility(tuple,
+				valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine,
+													 tuple,
 													 snapshot,
 													 scan->rs_cbuf);
 
@@ -841,6 +843,7 @@ heapgettup_pagemode(HeapScanDesc scan,
 			lineindex = scan->rs_cindex + 1;
 		}
 
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 		dp = BufferGetPage(scan->rs_cbuf);
 		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
 		lines = scan->rs_ntuples;
@@ -885,6 +888,7 @@ heapgettup_pagemode(HeapScanDesc scan,
 			page = scan->rs_cblock; /* current page */
 		}
 
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 		dp = BufferGetPage(scan->rs_cbuf);
 		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
 		lines = scan->rs_ntuples;
@@ -954,23 +958,31 @@ heapgettup_pagemode(HeapScanDesc scan,
 			/*
 			 * if current tuple qualifies, return it.
 			 */
-			if (key != NULL)
+			if (HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine, tuple, scan->rs_snapshot, scan->rs_cbuf))
 			{
-				bool		valid;
+				/*
+				 * if current tuple qualifies, return it.
+				 */
+				if (key != NULL)
+				{
+					bool		valid;
 
-				HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
-							nkeys, key, valid);
-				if (valid)
+					HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
+								nkeys, key, valid);
+					if (valid)
+					{
+						scan->rs_cindex = lineindex;
+						LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+						return;
+					}
+				}
+				else
 				{
 					scan->rs_cindex = lineindex;
+					LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
 					return;
 				}
 			}
-			else
-			{
-				scan->rs_cindex = lineindex;
-				return;
-			}
 
 			/*
 			 * otherwise move to the next item on the page
@@ -982,6 +994,12 @@ heapgettup_pagemode(HeapScanDesc scan,
 				++lineindex;
 		}
 
+		/*
+		 * if we get here, it means we've exhausted the items on this page and
+		 * it's time to move to the next.
+		 */
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+
 		/*
 		 * if we get here, it means we've exhausted the items on this page and
 		 * it's time to move to the next.
@@ -1039,6 +1057,7 @@ heapgettup_pagemode(HeapScanDesc scan,
 
 		heapgetpage(scan, page);
 
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 		dp = BufferGetPage(scan->rs_cbuf);
 		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
 		lines = scan->rs_ntuples;
@@ -1831,7 +1850,7 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 
 	pgstat_count_heap_getnext(scan->rs_rd);
 
-	return &(scan->rs_ctup);
+	return heap_copytuple(&(scan->rs_ctup));
 }
 
 /*
@@ -1950,7 +1969,7 @@ heap_fetch(Relation relation,
 	/*
 	 * check time qualification of tuple, then release lock
 	 */
-	valid = HeapTupleSatisfiesVisibility(tuple, snapshot, buffer);
+	valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, tuple, snapshot, buffer);
 
 	if (valid)
 		PredicateLockTuple(relation, tuple, snapshot);
@@ -2097,7 +2116,7 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
 			ItemPointerSet(&(heapTuple->t_self), BufferGetBlockNumber(buffer), offnum);
 
 			/* If it's visible per the snapshot, we must return it */
-			valid = HeapTupleSatisfiesVisibility(heapTuple, snapshot, buffer);
+			valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, heapTuple, snapshot, buffer);
 			CheckForSerializableConflictOut(valid, relation, heapTuple,
 											buffer, snapshot);
 			/* reset to original, non-redirected, tid */
@@ -2271,7 +2290,7 @@ heap_get_latest_tid(Relation relation,
 		 * Check time qualification of tuple; if visible, set it as the new
 		 * result candidate.
 		 */
-		valid = HeapTupleSatisfiesVisibility(&tp, snapshot, buffer);
+		valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, &tp, snapshot, buffer);
 		CheckForSerializableConflictOut(valid, relation, &tp, buffer, snapshot);
 		if (valid)
 			*tid = ctid;
@@ -3097,7 +3116,7 @@ heap_delete(Relation relation, ItemPointer tid,
 	tp.t_self = *tid;
 
 l1:
-	result = HeapTupleSatisfiesUpdate(&tp, cid, buffer);
+	result = relation->rd_stamroutine->snapshot_satisfiesUpdate(&tp, cid, buffer);
 
 	if (result == HeapTupleInvisible)
 	{
@@ -3208,7 +3227,7 @@ l1:
 	if (crosscheck != InvalidSnapshot && result == HeapTupleMayBeUpdated)
 	{
 		/* Perform additional check for transaction-snapshot mode RI updates */
-		if (!HeapTupleSatisfiesVisibility(&tp, crosscheck, buffer))
+		if (!HeapTupleSatisfiesVisibility(relation->rd_stamroutine, &tp, crosscheck, buffer))
 			result = HeapTupleUpdated;
 	}
 
@@ -3668,7 +3687,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 l2:
 	checked_lockers = false;
 	locker_remains = false;
-	result = HeapTupleSatisfiesUpdate(&oldtup, cid, buffer);
+	result = relation->rd_stamroutine->snapshot_satisfiesUpdate(&oldtup, cid, buffer);
 
 	/* see below about the "no wait" case */
 	Assert(result != HeapTupleBeingUpdated || wait);
@@ -3849,7 +3868,7 @@ l2:
 	if (crosscheck != InvalidSnapshot && result == HeapTupleMayBeUpdated)
 	{
 		/* Perform additional check for transaction-snapshot mode RI updates */
-		if (!HeapTupleSatisfiesVisibility(&oldtup, crosscheck, buffer))
+		if (!HeapTupleSatisfiesVisibility(relation->rd_stamroutine, &oldtup, crosscheck, buffer))
 			result = HeapTupleUpdated;
 	}
 
@@ -4600,7 +4619,7 @@ heap_lock_tuple(Relation relation, HeapTuple tuple,
 	tuple->t_tableOid = RelationGetRelid(relation);
 
 l3:
-	result = HeapTupleSatisfiesUpdate(tuple, cid, *buffer);
+	result = relation->rd_stamroutine->snapshot_satisfiesUpdate(tuple, cid, *buffer);
 
 	if (result == HeapTupleInvisible)
 	{
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index 792e9cb436..2d3ed44baf 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -23,11 +23,21 @@
 #include "access/storageamapi.h"
 #include "utils/builtins.h"
 
+extern bool HeapTupleSatisfies(StorageTuple stup, Snapshot snapshot, Buffer buffer);
+extern HTSU_Result HeapTupleSatisfiesUpdate(StorageTuple stup, CommandId curcid,
+						 Buffer buffer);
+extern HTSV_Result HeapTupleSatisfiesVacuum(StorageTuple stup, TransactionId OldestXmin,
+						 Buffer buffer);
 
 Datum
 heapam_storage_handler(PG_FUNCTION_ARGS)
 {
 	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
 
+	amroutine->snapshot_satisfies = HeapTupleSatisfies;
+
+	amroutine->snapshot_satisfiesUpdate = HeapTupleSatisfiesUpdate;
+	amroutine->snapshot_satisfiesVacuum = HeapTupleSatisfiesVacuum;
+
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/utils/time/tqual.c b/src/backend/access/heap/heapam_visibility.c
similarity index 80%
rename from src/backend/utils/time/tqual.c
rename to src/backend/access/heap/heapam_visibility.c
index a821e2eed1..3bfddae506 100644
--- a/src/backend/utils/time/tqual.c
+++ b/src/backend/access/heap/heapam_visibility.c
@@ -1,7 +1,33 @@
 /*-------------------------------------------------------------------------
  *
- * tqual.c
- *	  POSTGRES "time qualification" code, ie, tuple visibility rules.
+ * heapam_visibility.c
+ *	  heapam access method visibility functions
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/heap/heapam_visibility.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/heapam_xlog.h"
+#include "access/hio.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "storage/procarray.h"
+#include "utils/builtins.h"
+#include "utils/rel.h"
+#include "utils/tqual.h"
+
+/*-------------------------------------------------------------------------
+ *
+ * POSTGRES "time qualification" code, ie, tuple visibility rules.
  *
  * NOTE: all the HeapTupleSatisfies routines will update the tuple's
  * "hint" status bits if we see that the inserting or deleting transaction
@@ -45,108 +71,21 @@
  *		  like HeapTupleSatisfiesSelf(), but includes open transactions
  *	 HeapTupleSatisfiesVacuum()
  *		  visible to any running transaction, used by VACUUM
- *	 HeapTupleSatisfiesNonVacuumable()
- *		  Snapshot-style API for HeapTupleSatisfiesVacuum
+ *   HeapTupleSatisfiesNonVacuumable()
+ *        Snapshot-style API for HeapTupleSatisfiesVacuum
  *	 HeapTupleSatisfiesToast()
  *		  visible unless part of interrupted vacuum, used for TOAST
  *	 HeapTupleSatisfiesAny()
  *		  all tuples are visible
  *
- * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * IDENTIFICATION
- *	  src/backend/utils/time/tqual.c
- *
- *-------------------------------------------------------------------------
- */
-
-#include "postgres.h"
-
-#include "access/htup_details.h"
-#include "access/multixact.h"
-#include "access/subtrans.h"
-#include "access/transam.h"
-#include "access/xact.h"
-#include "access/xlog.h"
-#include "storage/bufmgr.h"
-#include "storage/procarray.h"
-#include "utils/builtins.h"
-#include "utils/combocid.h"
-#include "utils/snapmgr.h"
-#include "utils/tqual.h"
-
-
-/* Static variables representing various special snapshot semantics */
-SnapshotData SnapshotSelfData = {HeapTupleSatisfiesSelf};
-SnapshotData SnapshotAnyData = {HeapTupleSatisfiesAny};
-
-
-/*
- * SetHintBits()
- *
- * Set commit/abort hint bits on a tuple, if appropriate at this time.
- *
- * It is only safe to set a transaction-committed hint bit if we know the
- * transaction's commit record is guaranteed to be flushed to disk before the
- * buffer, or if the table is temporary or unlogged and will be obliterated by
- * a crash anyway.  We cannot change the LSN of the page here, because we may
- * hold only a share lock on the buffer, so we can only use the LSN to
- * interlock this if the buffer's LSN already is newer than the commit LSN;
- * otherwise we have to just refrain from setting the hint bit until some
- * future re-examination of the tuple.
- *
- * We can always set hint bits when marking a transaction aborted.  (Some
- * code in heapam.c relies on that!)
- *
- * Also, if we are cleaning up HEAP_MOVED_IN or HEAP_MOVED_OFF entries, then
- * we can always set the hint bits, since pre-9.0 VACUUM FULL always used
- * synchronous commits and didn't move tuples that weren't previously
- * hinted.  (This is not known by this subroutine, but is applied by its
- * callers.)  Note: old-style VACUUM FULL is gone, but we have to keep this
- * module's support for MOVED_OFF/MOVED_IN flag bits for as long as we
- * support in-place update from pre-9.0 databases.
- *
- * Normal commits may be asynchronous, so for those we need to get the LSN
- * of the transaction and then check whether this is flushed.
- *
- * The caller should pass xid as the XID of the transaction to check, or
- * InvalidTransactionId if no check is needed.
- */
-static inline void
-SetHintBits(HeapTupleHeader tuple, Buffer buffer,
-			uint16 infomask, TransactionId xid)
-{
-	if (TransactionIdIsValid(xid))
-	{
-		/* NB: xid must be known committed here! */
-		XLogRecPtr	commitLSN = TransactionIdGetCommitLSN(xid);
-
-		if (BufferIsPermanent(buffer) && XLogNeedsFlush(commitLSN) &&
-			BufferGetLSNAtomic(buffer) < commitLSN)
-		{
-			/* not flushed and no LSN interlock, so don't set hint */
-			return;
-		}
-	}
-
-	tuple->t_infomask |= infomask;
-	MarkBufferDirtyHint(buffer, true);
-}
-
-/*
- * HeapTupleSetHintBits --- exported version of SetHintBits()
- *
- * This must be separate because of C99's brain-dead notions about how to
- * implement inline functions.
+ * -------------------------------------------------------------------------
  */
-void
-HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
-					 uint16 infomask, TransactionId xid)
-{
-	SetHintBits(tuple, buffer, infomask, xid);
-}
 
+extern bool HeapTupleSatisfies(StorageTuple stup, Snapshot snapshot, Buffer buffer);
+extern HTSU_Result HeapTupleSatisfiesUpdate(StorageTuple stup, CommandId curcid,
+						 Buffer buffer);
+extern HTSV_Result HeapTupleSatisfiesVacuum(StorageTuple stup, TransactionId OldestXmin,
+						 Buffer buffer);
 
 /*
  * HeapTupleSatisfiesSelf
@@ -172,9 +111,10 @@ HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
  *			(Xmax != my-transaction &&			the row was deleted by another transaction
  *			 Xmax is not committed)))			that has not been committed
  */
-bool
-HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
+static bool
+HeapTupleSatisfiesSelf(StorageTuple stup, Snapshot snapshot, Buffer buffer)
 {
+	HeapTuple	htup = (HeapTuple) stup;
 	HeapTupleHeader tuple = htup->t_data;
 
 	Assert(ItemPointerIsValid(&htup->t_self));
@@ -342,8 +282,8 @@ HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
  * HeapTupleSatisfiesAny
  *		Dummy "satisfies" routine: any tuple satisfies SnapshotAny.
  */
-bool
-HeapTupleSatisfiesAny(HeapTuple htup, Snapshot snapshot, Buffer buffer)
+static bool
+HeapTupleSatisfiesAny(StorageTuple stup, Snapshot snapshot, Buffer buffer)
 {
 	return true;
 }
@@ -362,10 +302,11 @@ HeapTupleSatisfiesAny(HeapTuple htup, Snapshot snapshot, Buffer buffer)
  * Among other things, this means you can't do UPDATEs of rows in a TOAST
  * table.
  */
-bool
-HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot,
+static bool
+HeapTupleSatisfiesToast(StorageTuple stup, Snapshot snapshot,
 						Buffer buffer)
 {
+	HeapTuple	htup = (HeapTuple) stup;
 	HeapTupleHeader tuple = htup->t_data;
 
 	Assert(ItemPointerIsValid(&htup->t_self));
@@ -457,9 +398,10 @@ HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot,
  *	distinguish that case must test for it themselves.)
  */
 HTSU_Result
-HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
+HeapTupleSatisfiesUpdate(StorageTuple stup, CommandId curcid,
 						 Buffer buffer)
 {
+	HeapTuple	htup = (HeapTuple) stup;
 	HeapTupleHeader tuple = htup->t_data;
 
 	Assert(ItemPointerIsValid(&htup->t_self));
@@ -735,10 +677,11 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
  * on the insertion without aborting the whole transaction, the associated
  * token is also returned in snapshot->speculativeToken.
  */
-bool
-HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
+static bool
+HeapTupleSatisfiesDirty(StorageTuple stup, Snapshot snapshot,
 						Buffer buffer)
 {
+	HeapTuple	htup = (HeapTuple) stup;
 	HeapTupleHeader tuple = htup->t_data;
 
 	Assert(ItemPointerIsValid(&htup->t_self));
@@ -959,10 +902,11 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
  * inserting/deleting transaction was still running --- which was more cycles
  * and more contention on the PGXACT array.
  */
-bool
-HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
+static bool
+HeapTupleSatisfiesMVCC(StorageTuple stup, Snapshot snapshot,
 					   Buffer buffer)
 {
+	HeapTuple	htup = (HeapTuple) stup;
 	HeapTupleHeader tuple = htup->t_data;
 
 	Assert(ItemPointerIsValid(&htup->t_self));
@@ -1161,9 +1105,10 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
  * even if we see that the deleting transaction has committed.
  */
 HTSV_Result
-HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
+HeapTupleSatisfiesVacuum(StorageTuple stup, TransactionId OldestXmin,
 						 Buffer buffer)
 {
+	HeapTuple	htup = (HeapTuple) stup;
 	HeapTupleHeader tuple = htup->t_data;
 
 	Assert(ItemPointerIsValid(&htup->t_self));
@@ -1392,258 +1337,24 @@ HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
 	return HEAPTUPLE_DEAD;
 }
 
-
 /*
  * HeapTupleSatisfiesNonVacuumable
  *
- *	True if tuple might be visible to some transaction; false if it's
- *	surely dead to everyone, ie, vacuumable.
+ *     True if tuple might be visible to some transaction; false if it's
+ *     surely dead to everyone, ie, vacuumable.
  *
- *	This is an interface to HeapTupleSatisfiesVacuum that meets the
- *	SnapshotSatisfiesFunc API, so it can be used through a Snapshot.
- *	snapshot->xmin must have been set up with the xmin horizon to use.
+ *     This is an interface to HeapTupleSatisfiesVacuum that meets the
+ *     SnapshotSatisfiesFunc API, so it can be used through a Snapshot.
+ *     snapshot->xmin must have been set up with the xmin horizon to use.
  */
-bool
-HeapTupleSatisfiesNonVacuumable(HeapTuple htup, Snapshot snapshot,
+static bool
+HeapTupleSatisfiesNonVacuumable(StorageTuple htup, Snapshot snapshot,
 								Buffer buffer)
 {
 	return HeapTupleSatisfiesVacuum(htup, snapshot->xmin, buffer)
 		!= HEAPTUPLE_DEAD;
 }
 
-
-/*
- * HeapTupleIsSurelyDead
- *
- *	Cheaply determine whether a tuple is surely dead to all onlookers.
- *	We sometimes use this in lieu of HeapTupleSatisfiesVacuum when the
- *	tuple has just been tested by another visibility routine (usually
- *	HeapTupleSatisfiesMVCC) and, therefore, any hint bits that can be set
- *	should already be set.  We assume that if no hint bits are set, the xmin
- *	or xmax transaction is still running.  This is therefore faster than
- *	HeapTupleSatisfiesVacuum, because we don't consult PGXACT nor CLOG.
- *	It's okay to return false when in doubt, but we must return true only
- *	if the tuple is removable.
- */
-bool
-HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	/*
-	 * If the inserting transaction is marked invalid, then it aborted, and
-	 * the tuple is definitely dead.  If it's marked neither committed nor
-	 * invalid, then we assume it's still alive (since the presumption is that
-	 * all relevant hint bits were just set moments ago).
-	 */
-	if (!HeapTupleHeaderXminCommitted(tuple))
-		return HeapTupleHeaderXminInvalid(tuple) ? true : false;
-
-	/*
-	 * If the inserting transaction committed, but any deleting transaction
-	 * aborted, the tuple is still alive.
-	 */
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)
-		return false;
-
-	/*
-	 * If the XMAX is just a lock, the tuple is still alive.
-	 */
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-		return false;
-
-	/*
-	 * If the Xmax is a MultiXact, it might be dead or alive, but we cannot
-	 * know without checking pg_multixact.
-	 */
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-		return false;
-
-	/* If deleter isn't known to have committed, assume it's still running. */
-	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
-		return false;
-
-	/* Deleter committed, so tuple is dead if the XID is old enough. */
-	return TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin);
-}
-
-/*
- * XidInMVCCSnapshot
- *		Is the given XID still-in-progress according to the snapshot?
- *
- * Note: GetSnapshotData never stores either top xid or subxids of our own
- * backend into a snapshot, so these xids will not be reported as "running"
- * by this function.  This is OK for current uses, because we always check
- * TransactionIdIsCurrentTransactionId first, except when it's known the
- * XID could not be ours anyway.
- */
-bool
-XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot)
-{
-	uint32		i;
-
-	/*
-	 * Make a quick range check to eliminate most XIDs without looking at the
-	 * xip arrays.  Note that this is OK even if we convert a subxact XID to
-	 * its parent below, because a subxact with XID < xmin has surely also got
-	 * a parent with XID < xmin, while one with XID >= xmax must belong to a
-	 * parent that was not yet committed at the time of this snapshot.
-	 */
-
-	/* Any xid < xmin is not in-progress */
-	if (TransactionIdPrecedes(xid, snapshot->xmin))
-		return false;
-	/* Any xid >= xmax is in-progress */
-	if (TransactionIdFollowsOrEquals(xid, snapshot->xmax))
-		return true;
-
-	/*
-	 * Snapshot information is stored slightly differently in snapshots taken
-	 * during recovery.
-	 */
-	if (!snapshot->takenDuringRecovery)
-	{
-		/*
-		 * If the snapshot contains full subxact data, the fastest way to
-		 * check things is just to compare the given XID against both subxact
-		 * XIDs and top-level XIDs.  If the snapshot overflowed, we have to
-		 * use pg_subtrans to convert a subxact XID to its parent XID, but
-		 * then we need only look at top-level XIDs not subxacts.
-		 */
-		if (!snapshot->suboverflowed)
-		{
-			/* we have full data, so search subxip */
-			int32		j;
-
-			for (j = 0; j < snapshot->subxcnt; j++)
-			{
-				if (TransactionIdEquals(xid, snapshot->subxip[j]))
-					return true;
-			}
-
-			/* not there, fall through to search xip[] */
-		}
-		else
-		{
-			/*
-			 * Snapshot overflowed, so convert xid to top-level.  This is safe
-			 * because we eliminated too-old XIDs above.
-			 */
-			xid = SubTransGetTopmostTransaction(xid);
-
-			/*
-			 * If xid was indeed a subxact, we might now have an xid < xmin,
-			 * so recheck to avoid an array scan.  No point in rechecking
-			 * xmax.
-			 */
-			if (TransactionIdPrecedes(xid, snapshot->xmin))
-				return false;
-		}
-
-		for (i = 0; i < snapshot->xcnt; i++)
-		{
-			if (TransactionIdEquals(xid, snapshot->xip[i]))
-				return true;
-		}
-	}
-	else
-	{
-		int32		j;
-
-		/*
-		 * In recovery we store all xids in the subxact array because it is by
-		 * far the bigger array, and we mostly don't know which xids are
-		 * top-level and which are subxacts. The xip array is empty.
-		 *
-		 * We start by searching subtrans, if we overflowed.
-		 */
-		if (snapshot->suboverflowed)
-		{
-			/*
-			 * Snapshot overflowed, so convert xid to top-level.  This is safe
-			 * because we eliminated too-old XIDs above.
-			 */
-			xid = SubTransGetTopmostTransaction(xid);
-
-			/*
-			 * If xid was indeed a subxact, we might now have an xid < xmin,
-			 * so recheck to avoid an array scan.  No point in rechecking
-			 * xmax.
-			 */
-			if (TransactionIdPrecedes(xid, snapshot->xmin))
-				return false;
-		}
-
-		/*
-		 * We now have either a top-level xid higher than xmin or an
-		 * indeterminate xid. We don't know whether it's top level or subxact
-		 * but it doesn't matter. If it's present, the xid is visible.
-		 */
-		for (j = 0; j < snapshot->subxcnt; j++)
-		{
-			if (TransactionIdEquals(xid, snapshot->subxip[j]))
-				return true;
-		}
-	}
-
-	return false;
-}
-
-/*
- * Is the tuple really only locked?  That is, is it not updated?
- *
- * It's easy to check just infomask bits if the locker is not a multi; but
- * otherwise we need to verify that the updating transaction has not aborted.
- *
- * This function is here because it follows the same time qualification rules
- * laid out at the top of this file.
- */
-bool
-HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple)
-{
-	TransactionId xmax;
-
-	/* if there's no valid Xmax, then there's obviously no update either */
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)
-		return true;
-
-	if (tuple->t_infomask & HEAP_XMAX_LOCK_ONLY)
-		return true;
-
-	/* invalid xmax means no update */
-	if (!TransactionIdIsValid(HeapTupleHeaderGetRawXmax(tuple)))
-		return true;
-
-	/*
-	 * if HEAP_XMAX_LOCK_ONLY is not set and not a multi, then this must
-	 * necessarily have been updated
-	 */
-	if (!(tuple->t_infomask & HEAP_XMAX_IS_MULTI))
-		return false;
-
-	/* ... but if it's a multi, then perhaps the updating Xid aborted. */
-	xmax = HeapTupleGetUpdateXid(tuple);
-
-	/* not LOCKED_ONLY, so it has to have an xmax */
-	Assert(TransactionIdIsValid(xmax));
-
-	if (TransactionIdIsCurrentTransactionId(xmax))
-		return false;
-	if (TransactionIdIsInProgress(xmax))
-		return false;
-	if (TransactionIdDidCommit(xmax))
-		return false;
-
-	/*
-	 * not current, not in progress, not committed -- must have aborted or
-	 * crashed
-	 */
-	return true;
-}
-
 /*
  * check whether the transaction id 'xid' is in the pre-sorted array 'xip'.
  */
@@ -1668,10 +1379,11 @@ TransactionIdInArray(TransactionId xid, TransactionId *xip, Size num)
  * dangerous to do so as the semantics of doing so during timetravel are more
  * complicated than when dealing "only" with the present.
  */
-bool
-HeapTupleSatisfiesHistoricMVCC(HeapTuple htup, Snapshot snapshot,
+static bool
+HeapTupleSatisfiesHistoricMVCC(StorageTuple stup, Snapshot snapshot,
 							   Buffer buffer)
 {
+	HeapTuple	htup = (HeapTuple) stup;
 	HeapTupleHeader tuple = htup->t_data;
 	TransactionId xmin = HeapTupleHeaderGetXmin(tuple);
 	TransactionId xmax = HeapTupleHeaderGetRawXmax(tuple);
@@ -1805,3 +1517,35 @@ HeapTupleSatisfiesHistoricMVCC(HeapTuple htup, Snapshot snapshot,
 	else
 		return true;
 }
+
+bool
+HeapTupleSatisfies(StorageTuple stup, Snapshot snapshot, Buffer buffer)
+{
+	switch (snapshot->visibility_type)
+	{
+		case MVCC_VISIBILITY:
+			return HeapTupleSatisfiesMVCC(stup, snapshot, buffer);
+			break;
+		case SELF_VISIBILITY:
+			return HeapTupleSatisfiesSelf(stup, snapshot, buffer);
+			break;
+		case ANY_VISIBILITY:
+			return HeapTupleSatisfiesAny(stup, snapshot, buffer);
+			break;
+		case TOAST_VISIBILITY:
+			return HeapTupleSatisfiesToast(stup, snapshot, buffer);
+			break;
+		case DIRTY_VISIBILITY:
+			return HeapTupleSatisfiesDirty(stup, snapshot, buffer);
+			break;
+		case HISTORIC_MVCC_VISIBILITY:
+			return HeapTupleSatisfiesHistoricMVCC(stup, snapshot, buffer);
+			break;
+		case NON_VACUUMABLE_VISIBILTY:
+			return HeapTupleSatisfiesNonVacuumable(stup, snapshot, buffer);
+			break;
+		default:
+			Assert(0);
+			break;
+	}
+}
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 9f33e0ce07..ed40357976 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -402,7 +402,7 @@ heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum,
 			 * either here or while following a chain below.  Whichever path
 			 * gets there first will mark the tuple unused.
 			 */
-			if (HeapTupleSatisfiesVacuum(&tup, OldestXmin, buffer)
+			if (relation->rd_stamroutine->snapshot_satisfiesVacuum(&tup, OldestXmin, buffer)
 				== HEAPTUPLE_DEAD && !HeapTupleHeaderIsHotUpdated(htup))
 			{
 				heap_prune_record_unused(prstate, rootoffnum);
@@ -486,7 +486,7 @@ heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum,
 		 */
 		tupdead = recent_dead = false;
 
-		switch (HeapTupleSatisfiesVacuum(&tup, OldestXmin, buffer))
+		switch (relation->rd_stamroutine->snapshot_satisfiesVacuum(&tup, OldestXmin, buffer))
 		{
 			case HEAPTUPLE_DEAD:
 				tupdead = true;
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 05d7da001a..01321a2543 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -472,7 +472,7 @@ systable_recheck_tuple(SysScanDesc sysscan, HeapTuple tup)
 		Assert(BufferIsValid(scan->xs_cbuf));
 		/* must hold a buffer lock to call HeapTupleSatisfiesVisibility */
 		LockBuffer(scan->xs_cbuf, BUFFER_LOCK_SHARE);
-		result = HeapTupleSatisfiesVisibility(tup, freshsnap, scan->xs_cbuf);
+		result = HeapTupleSatisfiesVisibility(sysscan->heap_rel->rd_stamroutine, tup, freshsnap, scan->xs_cbuf);
 		LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK);
 	}
 	else
@@ -484,7 +484,7 @@ systable_recheck_tuple(SysScanDesc sysscan, HeapTuple tup)
 		Assert(BufferIsValid(scan->rs_cbuf));
 		/* must hold a buffer lock to call HeapTupleSatisfiesVisibility */
 		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-		result = HeapTupleSatisfiesVisibility(tup, freshsnap, scan->rs_cbuf);
+		result = HeapTupleSatisfiesVisibility(sysscan->heap_rel->rd_stamroutine, tup, freshsnap, scan->rs_cbuf);
 		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
 	}
 	return result;
diff --git a/src/backend/access/storage/Makefile b/src/backend/access/storage/Makefile
index 2a05c7ce66..321676820f 100644
--- a/src/backend/access/storage/Makefile
+++ b/src/backend/access/storage/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/storage
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = storageam.o storageamapi.o
+OBJS = storageam.o storageamapi.o storage_common.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/storage/storage_common.c b/src/backend/access/storage/storage_common.c
new file mode 100644
index 0000000000..3ede680c89
--- /dev/null
+++ b/src/backend/access/storage/storage_common.c
@@ -0,0 +1,277 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_common.c
+ *	  storage access method code that is common across all pluggable
+ *	  storage modules
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/storage/storage_common.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/storage_common.h"
+#include "access/subtrans.h"
+#include "access/transam.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "storage/bufmgr.h"
+#include "storage/procarray.h"
+
+/* Static variables representing various special snapshot semantics */
+SnapshotData SnapshotSelfData = {SELF_VISIBILITY};
+SnapshotData SnapshotAnyData = {ANY_VISIBILITY};
+
+/*
+ * HeapTupleSetHintBits --- exported version of SetHintBits()
+ *
+ * This must be separate because of C99's brain-dead notions about how to
+ * implement inline functions.
+ */
+void
+HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
+					 uint16 infomask, TransactionId xid)
+{
+	SetHintBits(tuple, buffer, infomask, xid);
+}
+
+
+/*
+ * Is the tuple really only locked?  That is, is it not updated?
+ *
+ * It's easy to check just infomask bits if the locker is not a multi; but
+ * otherwise we need to verify that the updating transaction has not aborted.
+ *
+ * This function is here because it follows the same time qualification rules
+ * laid out at the top of this file.
+ */
+bool
+HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple)
+{
+	TransactionId xmax;
+
+	/* if there's no valid Xmax, then there's obviously no update either */
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)
+		return true;
+
+	if (tuple->t_infomask & HEAP_XMAX_LOCK_ONLY)
+		return true;
+
+	/* invalid xmax means no update */
+	if (!TransactionIdIsValid(HeapTupleHeaderGetRawXmax(tuple)))
+		return true;
+
+	/*
+	 * if HEAP_XMAX_LOCK_ONLY is not set and not a multi, then this must
+	 * necessarily have been updated
+	 */
+	if (!(tuple->t_infomask & HEAP_XMAX_IS_MULTI))
+		return false;
+
+	/* ... but if it's a multi, then perhaps the updating Xid aborted. */
+	xmax = HeapTupleGetUpdateXid(tuple);
+
+	/* not LOCKED_ONLY, so it has to have an xmax */
+	Assert(TransactionIdIsValid(xmax));
+
+	if (TransactionIdIsCurrentTransactionId(xmax))
+		return false;
+	if (TransactionIdIsInProgress(xmax))
+		return false;
+	if (TransactionIdDidCommit(xmax))
+		return false;
+
+	/*
+	 * not current, not in progress, not committed -- must have aborted or
+	 * crashed
+	 */
+	return true;
+}
+
+
+/*
+ * HeapTupleIsSurelyDead
+ *
+ *	Cheaply determine whether a tuple is surely dead to all onlookers.
+ *	We sometimes use this in lieu of HeapTupleSatisfiesVacuum when the
+ *	tuple has just been tested by another visibility routine (usually
+ *	HeapTupleSatisfiesMVCC) and, therefore, any hint bits that can be set
+ *	should already be set.  We assume that if no hint bits are set, the xmin
+ *	or xmax transaction is still running.  This is therefore faster than
+ *	HeapTupleSatisfiesVacuum, because we don't consult PGXACT nor CLOG.
+ *	It's okay to return false when in doubt, but we must return TRUE only
+ *	if the tuple is removable.
+ */
+bool
+HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin)
+{
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	/*
+	 * If the inserting transaction is marked invalid, then it aborted, and
+	 * the tuple is definitely dead.  If it's marked neither committed nor
+	 * invalid, then we assume it's still alive (since the presumption is that
+	 * all relevant hint bits were just set moments ago).
+	 */
+	if (!HeapTupleHeaderXminCommitted(tuple))
+		return HeapTupleHeaderXminInvalid(tuple) ? true : false;
+
+	/*
+	 * If the inserting transaction committed, but any deleting transaction
+	 * aborted, the tuple is still alive.
+	 */
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)
+		return false;
+
+	/*
+	 * If the XMAX is just a lock, the tuple is still alive.
+	 */
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+		return false;
+
+	/*
+	 * If the Xmax is a MultiXact, it might be dead or alive, but we cannot
+	 * know without checking pg_multixact.
+	 */
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+		return false;
+
+	/* If deleter isn't known to have committed, assume it's still running. */
+	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
+		return false;
+
+	/* Deleter committed, so tuple is dead if the XID is old enough. */
+	return TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin);
+}
+
+/*
+ * XidInMVCCSnapshot
+ *		Is the given XID still-in-progress according to the snapshot?
+ *
+ * Note: GetSnapshotData never stores either top xid or subxids of our own
+ * backend into a snapshot, so these xids will not be reported as "running"
+ * by this function.  This is OK for current uses, because we always check
+ * TransactionIdIsCurrentTransactionId first, except when it's known the
+ * XID could not be ours anyway.
+ */
+bool
+XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot)
+{
+	uint32		i;
+
+	/*
+	 * Make a quick range check to eliminate most XIDs without looking at the
+	 * xip arrays.  Note that this is OK even if we convert a subxact XID to
+	 * its parent below, because a subxact with XID < xmin has surely also got
+	 * a parent with XID < xmin, while one with XID >= xmax must belong to a
+	 * parent that was not yet committed at the time of this snapshot.
+	 */
+
+	/* Any xid < xmin is not in-progress */
+	if (TransactionIdPrecedes(xid, snapshot->xmin))
+		return false;
+	/* Any xid >= xmax is in-progress */
+	if (TransactionIdFollowsOrEquals(xid, snapshot->xmax))
+		return true;
+
+	/*
+	 * Snapshot information is stored slightly differently in snapshots taken
+	 * during recovery.
+	 */
+	if (!snapshot->takenDuringRecovery)
+	{
+		/*
+		 * If the snapshot contains full subxact data, the fastest way to
+		 * check things is just to compare the given XID against both subxact
+		 * XIDs and top-level XIDs.  If the snapshot overflowed, we have to
+		 * use pg_subtrans to convert a subxact XID to its parent XID, but
+		 * then we need only look at top-level XIDs not subxacts.
+		 */
+		if (!snapshot->suboverflowed)
+		{
+			/* we have full data, so search subxip */
+			int32		j;
+
+			for (j = 0; j < snapshot->subxcnt; j++)
+			{
+				if (TransactionIdEquals(xid, snapshot->subxip[j]))
+					return true;
+			}
+
+			/* not there, fall through to search xip[] */
+		}
+		else
+		{
+			/*
+			 * Snapshot overflowed, so convert xid to top-level.  This is safe
+			 * because we eliminated too-old XIDs above.
+			 */
+			xid = SubTransGetTopmostTransaction(xid);
+
+			/*
+			 * If xid was indeed a subxact, we might now have an xid < xmin,
+			 * so recheck to avoid an array scan.  No point in rechecking
+			 * xmax.
+			 */
+			if (TransactionIdPrecedes(xid, snapshot->xmin))
+				return false;
+		}
+
+		for (i = 0; i < snapshot->xcnt; i++)
+		{
+			if (TransactionIdEquals(xid, snapshot->xip[i]))
+				return true;
+		}
+	}
+	else
+	{
+		int32		j;
+
+		/*
+		 * In recovery we store all xids in the subxact array because it is by
+		 * far the bigger array, and we mostly don't know which xids are
+		 * top-level and which are subxacts. The xip array is empty.
+		 *
+		 * We start by searching subtrans, if we overflowed.
+		 */
+		if (snapshot->suboverflowed)
+		{
+			/*
+			 * Snapshot overflowed, so convert xid to top-level.  This is safe
+			 * because we eliminated too-old XIDs above.
+			 */
+			xid = SubTransGetTopmostTransaction(xid);
+
+			/*
+			 * If xid was indeed a subxact, we might now have an xid < xmin,
+			 * so recheck to avoid an array scan.  No point in rechecking
+			 * xmax.
+			 */
+			if (TransactionIdPrecedes(xid, snapshot->xmin))
+				return false;
+		}
+
+		/*
+		 * We now have either a top-level xid higher than xmin or an
+		 * indeterminate xid. We don't know whether it's top level or subxact
+		 * but it doesn't matter. If it's present, the xid is visible.
+		 */
+		for (j = 0; j < snapshot->subxcnt; j++)
+		{
+			if (TransactionIdEquals(xid, snapshot->subxip[j]))
+				return true;
+		}
+	}
+
+	return false;
+}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 0125c18bc1..ead8d2abdf 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2225,6 +2225,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	TransactionId OldestXmin;
 	BlockNumber root_blkno = InvalidBlockNumber;
 	OffsetNumber root_offsets[MaxHeapTuplesPerPage];
+	StorageAmRoutine *method;
 
 	/*
 	 * sanity checks
@@ -2280,6 +2281,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 		OldestXmin = GetOldestXmin(heapRelation, PROCARRAY_FLAGS_VACUUM);
 	}
 
+	method = heapRelation->rd_stamroutine;
 	scan = heap_beginscan_strat(heapRelation,	/* relation */
 								snapshot,	/* snapshot */
 								0,	/* number of keys */
@@ -2360,8 +2362,8 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 			 */
 			LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 
-			switch (HeapTupleSatisfiesVacuum(heapTuple, OldestXmin,
-											 scan->rs_cbuf))
+			switch (method->snapshot_satisfiesVacuum(heapTuple, OldestXmin,
+													 scan->rs_cbuf))
 			{
 				case HEAPTUPLE_DEAD:
 					/* Definitely dead, we can ignore it */
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f952b3c732..4411c1d4de 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -1119,9 +1119,9 @@ acquire_sample_rows(Relation onerel, int elevel,
 			targtuple.t_data = (HeapTupleHeader) PageGetItem(targpage, itemid);
 			targtuple.t_len = ItemIdGetLength(itemid);
 
-			switch (HeapTupleSatisfiesVacuum(&targtuple,
-											 OldestXmin,
-											 targbuffer))
+			switch (onerel->rd_stamroutine->snapshot_satisfiesVacuum(&targtuple,
+																	 OldestXmin,
+																	 targbuffer))
 			{
 				case HEAPTUPLE_LIVE:
 					sample_it = true;
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 48f1e6e2ad..dbcc5bc172 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -21,6 +21,7 @@
 #include "access/multixact.h"
 #include "access/relscan.h"
 #include "access/rewriteheap.h"
+#include "access/storageamapi.h"
 #include "access/transam.h"
 #include "access/tuptoaster.h"
 #include "access/xact.h"
@@ -967,7 +968,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 
 		LockBuffer(buf, BUFFER_LOCK_SHARE);
 
-		switch (HeapTupleSatisfiesVacuum(tuple, OldestXmin, buf))
+		switch (OldHeap->rd_stamroutine->snapshot_satisfiesVacuum(tuple, OldestXmin, buf))
 		{
 			case HEAPTUPLE_DEAD:
 				/* Definitely dead */
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index 20ce431e46..04729e5c27 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -986,7 +986,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
 
 			tupgone = false;
 
-			switch (HeapTupleSatisfiesVacuum(&tuple, OldestXmin, buf))
+			switch (onerel->rd_stamroutine->snapshot_satisfiesVacuum(&tuple, OldestXmin, buf))
 			{
 				case HEAPTUPLE_DEAD:
 
@@ -2151,7 +2151,7 @@ heap_page_is_all_visible(Relation rel, Buffer buf,
 		tuple.t_len = ItemIdGetLength(itemid);
 		tuple.t_tableOid = RelationGetRelid(rel);
 
-		switch (HeapTupleSatisfiesVacuum(&tuple, OldestXmin, buf))
+		switch (rel->rd_stamroutine->snapshot_satisfiesVacuum(&tuple, OldestXmin, buf))
 		{
 			case HEAPTUPLE_LIVE:
 				{
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index eb5bbb57ef..2c5c95d425 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -461,7 +461,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 			loctup.t_len = ItemIdGetLength(lp);
 			loctup.t_tableOid = scan->rs_rd->rd_id;
 			ItemPointerSet(&loctup.t_self, page, offnum);
-			valid = HeapTupleSatisfiesVisibility(&loctup, snapshot, buffer);
+			valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine, &loctup, snapshot, buffer);
 			if (valid)
 			{
 				scan->rs_vistuples[ntup++] = offnum;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index afb83ed3ae..79c34a6e6c 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -191,6 +191,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
  */
 static void
 ExecCheckHeapTupleVisible(EState *estate,
+						  Relation rel,
 						  HeapTuple tuple,
 						  Buffer buffer)
 {
@@ -202,7 +203,7 @@ ExecCheckHeapTupleVisible(EState *estate,
 	 * Caller should be holding pin, but not lock.
 	 */
 	LockBuffer(buffer, BUFFER_LOCK_SHARE);
-	if (!HeapTupleSatisfiesVisibility(tuple, estate->es_snapshot, buffer))
+	if (!HeapTupleSatisfiesVisibility(rel->rd_stamroutine, tuple, estate->es_snapshot, buffer))
 	{
 		/*
 		 * We should not raise a serialization failure if the conflict is
@@ -237,7 +238,7 @@ ExecCheckTIDVisible(EState *estate,
 	tuple.t_self = *tid;
 	if (!heap_fetch(rel, SnapshotAny, &tuple, &buffer, false, NULL))
 		elog(ERROR, "failed to fetch conflicting tuple for ON CONFLICT");
-	ExecCheckHeapTupleVisible(estate, &tuple, buffer);
+	ExecCheckHeapTupleVisible(estate, rel, &tuple, buffer);
 	ReleaseBuffer(buffer);
 }
 
@@ -1313,7 +1314,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	 * snapshot.  This is in line with the way UPDATE deals with newer tuple
 	 * versions.
 	 */
-	ExecCheckHeapTupleVisible(estate, &tuple, buffer);
+	ExecCheckHeapTupleVisible(estate, relation, &tuple, buffer);
 
 	/* Store target's existing tuple in the state's dedicated slot */
 	ExecStoreTuple(&tuple, mtstate->mt_existing, buffer, false);
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index 9c74a836e4..6a118d1883 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -588,7 +588,8 @@ SampleTupleVisible(HeapTuple tuple, OffsetNumber tupoffset, HeapScanDesc scan)
 	else
 	{
 		/* Otherwise, we have to check the tuple individually. */
-		return HeapTupleSatisfiesVisibility(tuple,
+		return HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine,
+											tuple,
 											scan->rs_snapshot,
 											scan->rs_cbuf);
 	}
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index ad65b9831d..86efe3a66b 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -376,7 +376,7 @@ static void
 SnapBuildFreeSnapshot(Snapshot snap)
 {
 	/* make sure we don't get passed an external snapshot */
-	Assert(snap->satisfies == HeapTupleSatisfiesHistoricMVCC);
+	Assert(snap->visibility_type == HISTORIC_MVCC_VISIBILITY);
 
 	/* make sure nobody modified our snapshot */
 	Assert(snap->curcid == FirstCommandId);
@@ -434,7 +434,7 @@ void
 SnapBuildSnapDecRefcount(Snapshot snap)
 {
 	/* make sure we don't get passed an external snapshot */
-	Assert(snap->satisfies == HeapTupleSatisfiesHistoricMVCC);
+	Assert(snap->visibility_type == HISTORIC_MVCC_VISIBILITY);
 
 	/* make sure nobody modified our snapshot */
 	Assert(snap->curcid == FirstCommandId);
@@ -476,7 +476,7 @@ SnapBuildBuildSnapshot(SnapBuild *builder)
 
 	snapshot = MemoryContextAllocZero(builder->context, ssize);
 
-	snapshot->satisfies = HeapTupleSatisfiesHistoricMVCC;
+	snapshot->visibility_type = HISTORIC_MVCC_VISIBILITY;
 
 	/*
 	 * We misuse the original meaning of SnapshotData's xip and subxip fields
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index 251a359bff..4fbad9f0f6 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -3972,7 +3972,7 @@ CheckForSerializableConflictOut(bool visible, Relation relation,
 	 * tuple is visible to us, while HeapTupleSatisfiesVacuum checks what else
 	 * is going on with it.
 	 */
-	htsvResult = HeapTupleSatisfiesVacuum(tuple, TransactionXmin, buffer);
+	htsvResult = relation->rd_stamroutine->snapshot_satisfiesVacuum(tuple, TransactionXmin, buffer);
 	switch (htsvResult)
 	{
 		case HEAPTUPLE_LIVE:
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index b1ae9e5f96..640e9634b3 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -287,7 +287,7 @@ RI_FKey_check(TriggerData *trigdata)
 	 * should be holding pin, but not lock.
 	 */
 	LockBuffer(new_row_buf, BUFFER_LOCK_SHARE);
-	if (!HeapTupleSatisfiesVisibility(new_row, SnapshotSelf, new_row_buf))
+	if (!HeapTupleSatisfiesVisibility(trigdata->tg_relation->rd_stamroutine, new_row, SnapshotSelf, new_row_buf))
 	{
 		LockBuffer(new_row_buf, BUFFER_LOCK_UNLOCK);
 		return PointerGetDatum(NULL);
diff --git a/src/backend/utils/time/Makefile b/src/backend/utils/time/Makefile
index 5a6e6fa4c8..f17b1c5324 100644
--- a/src/backend/utils/time/Makefile
+++ b/src/backend/utils/time/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/utils/time
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = combocid.o tqual.o snapmgr.o
+OBJS = combocid.o snapmgr.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c
index 0b032905a5..b8f195593f 100644
--- a/src/backend/utils/time/snapmgr.c
+++ b/src/backend/utils/time/snapmgr.c
@@ -141,9 +141,9 @@ static volatile OldSnapshotControlData *oldSnapshotControl;
  * These SnapshotData structs are static to simplify memory allocation
  * (see the hack in GetSnapshotData to avoid repeated malloc/free).
  */
-static SnapshotData CurrentSnapshotData = {HeapTupleSatisfiesMVCC};
-static SnapshotData SecondarySnapshotData = {HeapTupleSatisfiesMVCC};
-SnapshotData CatalogSnapshotData = {HeapTupleSatisfiesMVCC};
+static SnapshotData CurrentSnapshotData = {MVCC_VISIBILITY};
+static SnapshotData SecondarySnapshotData = {MVCC_VISIBILITY};
+SnapshotData CatalogSnapshotData = {MVCC_VISIBILITY};
 
 /* Pointers to valid snapshots */
 static Snapshot CurrentSnapshot = NULL;
@@ -2046,7 +2046,7 @@ EstimateSnapshotSpace(Snapshot snap)
 	Size		size;
 
 	Assert(snap != InvalidSnapshot);
-	Assert(snap->satisfies == HeapTupleSatisfiesMVCC);
+	Assert(snap->visibility_type == MVCC_VISIBILITY);
 
 	/* We allocate any XID arrays needed in the same palloc block. */
 	size = add_size(sizeof(SerializedSnapshotData),
@@ -2143,7 +2143,7 @@ RestoreSnapshot(char *start_address)
 
 	/* Copy all required fields */
 	snapshot = (Snapshot) MemoryContextAlloc(TopTransactionContext, size);
-	snapshot->satisfies = HeapTupleSatisfiesMVCC;
+	snapshot->visibility_type = MVCC_VISIBILITY;
 	snapshot->xmin = serialized_snapshot.xmin;
 	snapshot->xmax = serialized_snapshot.xmax;
 	snapshot->xip = NULL;
diff --git a/src/include/access/storage_common.h b/src/include/access/storage_common.h
new file mode 100644
index 0000000000..791f42dd31
--- /dev/null
+++ b/src/include/access/storage_common.h
@@ -0,0 +1,98 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_common.h
+ *	  POSTGRES storage access method definitions shared across
+ *	  all pluggable storage methods and server.
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/storage_common.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_COMMON_H
+#define STORAGE_COMMON_H
+
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/transam.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "storage/bufpage.h"
+#include "storage/bufmgr.h"
+
+
+/* Result codes for HeapTupleSatisfiesVacuum */
+typedef enum
+{
+	HEAPTUPLE_DEAD,				/* tuple is dead and deletable */
+	HEAPTUPLE_LIVE,				/* tuple is live (committed, no deleter) */
+	HEAPTUPLE_RECENTLY_DEAD,	/* tuple is dead, but not deletable yet */
+	HEAPTUPLE_INSERT_IN_PROGRESS,	/* inserting xact is still in progress */
+	HEAPTUPLE_DELETE_IN_PROGRESS	/* deleting xact is still in progress */
+} HTSV_Result;
+
+
+/* in storage/storage_common.c */
+extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
+					 uint16 infomask, TransactionId xid);
+extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
+extern bool HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin);
+extern bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
+
+/*
+ * SetHintBits()
+ *
+ * Set commit/abort hint bits on a tuple, if appropriate at this time.
+ *
+ * It is only safe to set a transaction-committed hint bit if we know the
+ * transaction's commit record is guaranteed to be flushed to disk before the
+ * buffer, or if the table is temporary or unlogged and will be obliterated by
+ * a crash anyway.  We cannot change the LSN of the page here, because we may
+ * hold only a share lock on the buffer, so we can only use the LSN to
+ * interlock this if the buffer's LSN already is newer than the commit LSN;
+ * otherwise we have to just refrain from setting the hint bit until some
+ * future re-examination of the tuple.
+ *
+ * We can always set hint bits when marking a transaction aborted.  (Some
+ * code in heapam.c relies on that!)
+ *
+ * Also, if we are cleaning up HEAP_MOVED_IN or HEAP_MOVED_OFF entries, then
+ * we can always set the hint bits, since pre-9.0 VACUUM FULL always used
+ * synchronous commits and didn't move tuples that weren't previously
+ * hinted.  (This is not known by this subroutine, but is applied by its
+ * callers.)  Note: old-style VACUUM FULL is gone, but we have to keep this
+ * module's support for MOVED_OFF/MOVED_IN flag bits for as long as we
+ * support in-place update from pre-9.0 databases.
+ *
+ * Normal commits may be asynchronous, so for those we need to get the LSN
+ * of the transaction and then check whether this is flushed.
+ *
+ * The caller should pass xid as the XID of the transaction to check, or
+ * InvalidTransactionId if no check is needed.
+ */
+static inline void
+SetHintBits(HeapTupleHeader tuple, Buffer buffer,
+			uint16 infomask, TransactionId xid)
+{
+	if (TransactionIdIsValid(xid))
+	{
+		/* NB: xid must be known committed here! */
+		XLogRecPtr	commitLSN = TransactionIdGetCommitLSN(xid);
+
+		if (BufferIsPermanent(buffer) && XLogNeedsFlush(commitLSN) &&
+			BufferGetLSNAtomic(buffer) < commitLSN)
+		{
+			/* not flushed and no LSN interlock, so don't set hint */
+			return;
+		}
+	}
+
+	tuple->t_infomask |= infomask;
+	MarkBufferDirtyHint(buffer, true);
+}
+
+#endif							/* STORAGE_COMMON_H */
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
index 6fae4eea5c..f5d5edd704 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -11,12 +11,20 @@
 #ifndef STORAGEAMAPI_H
 #define STORAGEAMAPI_H
 
+#include "access/storage_common.h"
 #include "nodes/nodes.h"
+#include "utils/snapshot.h"
 #include "fmgr.h"
 
 /* A physical tuple coming from a storage AM scan */
 typedef void *StorageTuple;
 
+typedef bool (*SnapshotSatisfies_function) (StorageTuple htup, Snapshot snapshot, Buffer buffer);
+typedef HTSU_Result (*SnapshotSatisfiesUpdate_function) (StorageTuple htup, CommandId curcid, Buffer buffer);
+typedef HTSV_Result (*SnapshotSatisfiesVacuum_function) (StorageTuple htup, TransactionId OldestXmin, Buffer buffer);
+
+
+
 /*
  * API struct for a storage AM.  Note this must be stored in a single palloc'd
  * chunk of memory.
@@ -30,6 +38,10 @@ typedef struct StorageAmRoutine
 {
 	NodeTag		type;
 
+	SnapshotSatisfies_function snapshot_satisfies;
+	SnapshotSatisfiesUpdate_function snapshot_satisfiesUpdate;	/* HeapTupleSatisfiesUpdate */
+	SnapshotSatisfiesVacuum_function snapshot_satisfiesVacuum;	/* HeapTupleSatisfiesVacuum */
+
 }			StorageAmRoutine;
 
 extern StorageAmRoutine * GetStorageAmRoutine(Oid amhandler);
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 98b63fc5ba..1a403b8e21 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -20,7 +20,6 @@
 #include "storage/relfilenode.h"
 #include "utils/relcache.h"
 #include "utils/snapmgr.h"
-#include "utils/tqual.h"
 
 typedef void *Block;
 
@@ -268,8 +267,8 @@ TestForOldSnapshot(Snapshot snapshot, Relation relation, Page page)
 
 	if (old_snapshot_threshold >= 0
 		&& (snapshot) != NULL
-		&& ((snapshot)->satisfies == HeapTupleSatisfiesMVCC
-			|| (snapshot)->satisfies == HeapTupleSatisfiesToast)
+		&& ((snapshot)->visibility_type == MVCC_VISIBILITY
+			|| (snapshot)->visibility_type == TOAST_VISIBILITY)
 		&& !XLogRecPtrIsInvalid((snapshot)->lsn)
 		&& PageGetLSN(page) > (snapshot)->lsn)
 		TestForOldSnapshot_impl(snapshot, relation);
diff --git a/src/include/utils/snapshot.h b/src/include/utils/snapshot.h
index bf519778df..3896349b39 100644
--- a/src/include/utils/snapshot.h
+++ b/src/include/utils/snapshot.h
@@ -19,6 +19,18 @@
 #include "lib/pairingheap.h"
 #include "storage/buf.h"
 
+typedef enum tuple_visibility_type
+{
+	MVCC_VISIBILITY = 0,		/* HeapTupleSatisfiesMVCC */
+	SELF_VISIBILITY,			/* HeapTupleSatisfiesSelf */
+	ANY_VISIBILITY,				/* HeapTupleSatisfiesAny */
+	TOAST_VISIBILITY,			/* HeapTupleSatisfiesToast */
+	DIRTY_VISIBILITY,			/* HeapTupleSatisfiesDirty */
+	HISTORIC_MVCC_VISIBILITY,	/* HeapTupleSatisfiesHistoricMVCC */
+	NON_VACUUMABLE_VISIBILTY,	/* HeapTupleSatisfiesNonVacuumable */
+
+	END_OF_VISIBILITY
+}			tuple_visibility_type;
 
 typedef struct SnapshotData *Snapshot;
 
@@ -52,7 +64,7 @@ typedef bool (*SnapshotSatisfiesFunc) (HeapTuple htup,
  */
 typedef struct SnapshotData
 {
-	SnapshotSatisfiesFunc satisfies;	/* tuple test function */
+	tuple_visibility_type visibility_type;	/* tuple visibility test type */
 
 	/*
 	 * The remaining fields are used only for MVCC snapshots, and are normally
diff --git a/src/include/utils/tqual.h b/src/include/utils/tqual.h
index 96eaf01ca0..c2ec33e57c 100644
--- a/src/include/utils/tqual.h
+++ b/src/include/utils/tqual.h
@@ -16,6 +16,7 @@
 #define TQUAL_H
 
 #include "utils/snapshot.h"
+#include "access/storageamapi.h"
 #include "access/xlogdefs.h"
 
 
@@ -29,8 +30,8 @@ extern PGDLLIMPORT SnapshotData CatalogSnapshotData;
 
 /* This macro encodes the knowledge of which snapshots are MVCC-safe */
 #define IsMVCCSnapshot(snapshot)  \
-	((snapshot)->satisfies == HeapTupleSatisfiesMVCC || \
-	 (snapshot)->satisfies == HeapTupleSatisfiesHistoricMVCC)
+	((snapshot)->visibility_type == MVCC_VISIBILITY || \
+	 (snapshot)->visibility_type == HISTORIC_MVCC_VISIBILITY)
 
 /*
  * HeapTupleSatisfiesVisibility
@@ -42,47 +43,8 @@ extern PGDLLIMPORT SnapshotData CatalogSnapshotData;
  *	Hint bits in the HeapTuple's t_infomask may be updated as a side effect;
  *	if so, the indicated buffer is marked dirty.
  */
-#define HeapTupleSatisfiesVisibility(tuple, snapshot, buffer) \
-	((*(snapshot)->satisfies) (tuple, snapshot, buffer))
-
-/* Result codes for HeapTupleSatisfiesVacuum */
-typedef enum
-{
-	HEAPTUPLE_DEAD,				/* tuple is dead and deletable */
-	HEAPTUPLE_LIVE,				/* tuple is live (committed, no deleter) */
-	HEAPTUPLE_RECENTLY_DEAD,	/* tuple is dead, but not deletable yet */
-	HEAPTUPLE_INSERT_IN_PROGRESS,	/* inserting xact is still in progress */
-	HEAPTUPLE_DELETE_IN_PROGRESS	/* deleting xact is still in progress */
-} HTSV_Result;
-
-/* These are the "satisfies" test routines for the various snapshot types */
-extern bool HeapTupleSatisfiesMVCC(HeapTuple htup,
-					   Snapshot snapshot, Buffer buffer);
-extern bool HeapTupleSatisfiesSelf(HeapTuple htup,
-					   Snapshot snapshot, Buffer buffer);
-extern bool HeapTupleSatisfiesAny(HeapTuple htup,
-					  Snapshot snapshot, Buffer buffer);
-extern bool HeapTupleSatisfiesToast(HeapTuple htup,
-						Snapshot snapshot, Buffer buffer);
-extern bool HeapTupleSatisfiesDirty(HeapTuple htup,
-						Snapshot snapshot, Buffer buffer);
-extern bool HeapTupleSatisfiesNonVacuumable(HeapTuple htup,
-								Snapshot snapshot, Buffer buffer);
-extern bool HeapTupleSatisfiesHistoricMVCC(HeapTuple htup,
-							   Snapshot snapshot, Buffer buffer);
-
-/* Special "satisfies" routines with different APIs */
-extern HTSU_Result HeapTupleSatisfiesUpdate(HeapTuple htup,
-						 CommandId curcid, Buffer buffer);
-extern HTSV_Result HeapTupleSatisfiesVacuum(HeapTuple htup,
-						 TransactionId OldestXmin, Buffer buffer);
-extern bool HeapTupleIsSurelyDead(HeapTuple htup,
-					  TransactionId OldestXmin);
-extern bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
-
-extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
-					 uint16 infomask, TransactionId xid);
-extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
+#define HeapTupleSatisfiesVisibility(method, tuple, snapshot, buffer) \
+	(((method)->snapshot_satisfies) (tuple, snapshot, buffer))
 
 /*
  * To avoid leaking too much knowledge about reorderbuffer implementation
@@ -101,14 +63,14 @@ extern bool ResolveCminCmaxDuringDecoding(struct HTAB *tuplecid_data,
  * local variable of type SnapshotData, and initialize it with this macro.
  */
 #define InitDirtySnapshot(snapshotdata)  \
-	((snapshotdata).satisfies = HeapTupleSatisfiesDirty)
+	((snapshotdata).visibility_type = DIRTY_VISIBILITY)
 
 /*
  * Similarly, some initialization is required for a NonVacuumable snapshot.
  * The caller must supply the xmin horizon to use (e.g., RecentGlobalXmin).
  */
 #define InitNonVacuumableSnapshot(snapshotdata, xmin_horizon)  \
-	((snapshotdata).satisfies = HeapTupleSatisfiesNonVacuumable, \
+	((snapshotdata).visibility_type = NON_VACUUMABLE_VISIBILTY, \
 	 (snapshotdata).xmin = (xmin_horizon))
 
 /*
@@ -116,7 +78,7 @@ extern bool ResolveCminCmaxDuringDecoding(struct HTAB *tuplecid_data,
  * to set lsn and whenTaken correctly to support snapshot_too_old.
  */
 #define InitToastSnapshot(snapshotdata, l, w)  \
-	((snapshotdata).satisfies = HeapTupleSatisfiesToast, \
+	((snapshotdata).visibility_type = TOAST_VISIBILITY, \
 	 (snapshotdata).lsn = (l),					\
 	 (snapshotdata).whenTaken = (w))
 
-- 
2.15.0.windows.1



  [application/octet-stream] 0005-slot-hooks-are-added-to-storage-AM.patch (62.7K, ../../CAJrrPGdsQSRuq7N3W63CWXVkM_Acgc8HBphDvzOq1ik=Mt6j+Q@mail.gmail.com/8-0005-slot-hooks-are-added-to-storage-AM.patch)
  download | inline diff:
From 477e34008a735e9f340fe4913847d6f6117614c0 Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Mon, 4 Dec 2017 14:10:37 +1100
Subject: [PATCH 5/8] slot hooks are added to storage AM

The tuple is removed as part of the slot and added
an void pointer to store the tuple data that can
understand only by the storage AM routine.

The slot utility functions are reorganized to use
two storageAM routines to satify the current
functionality.

Currently the slot supports minimum tuple also.
---
 src/backend/access/common/heaptuple.c       | 302 +--------------------
 src/backend/access/heap/heapam_storage.c    |   2 +
 src/backend/access/storage/storage_common.c | 404 ++++++++++++++++++++++++++++
 src/backend/commands/copy.c                 |   2 +-
 src/backend/commands/createas.c             |   2 +-
 src/backend/commands/matview.c              |   2 +-
 src/backend/commands/trigger.c              |  15 +-
 src/backend/executor/execExprInterp.c       |  35 +--
 src/backend/executor/execReplication.c      |  90 ++-----
 src/backend/executor/execTuples.c           | 268 +++++++++---------
 src/backend/executor/nodeForeignscan.c      |   2 +-
 src/backend/executor/nodeModifyTable.c      |  24 +-
 src/backend/executor/tqueue.c               |   2 +-
 src/backend/replication/logical/worker.c    |   5 +-
 src/include/access/htup_details.h           |  15 +-
 src/include/access/storage_common.h         |  38 +++
 src/include/access/storageamapi.h           |   9 +-
 src/include/executor/tuptable.h             |  54 ++--
 18 files changed, 692 insertions(+), 579 deletions(-)

diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index a1a9d9905b..c29935b91a 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -57,6 +57,7 @@
 
 #include "postgres.h"
 
+#include "access/storageamapi.h"
 #include "access/sysattr.h"
 #include "access/tuptoaster.h"
 #include "executor/tuptable.h"
@@ -1021,111 +1022,6 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
 	}
 }
 
-/*
- * slot_deform_tuple
- *		Given a TupleTableSlot, extract data from the slot's physical tuple
- *		into its Datum/isnull arrays.  Data is extracted up through the
- *		natts'th column (caller must ensure this is a legal column number).
- *
- *		This is essentially an incremental version of heap_deform_tuple:
- *		on each call we extract attributes up to the one needed, without
- *		re-computing information about previously extracted attributes.
- *		slot->tts_nvalid is the number of attributes already extracted.
- */
-static void
-slot_deform_tuple(TupleTableSlot *slot, int natts)
-{
-	HeapTuple	tuple = slot->tts_tuple;
-	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
-	Datum	   *values = slot->tts_values;
-	bool	   *isnull = slot->tts_isnull;
-	HeapTupleHeader tup = tuple->t_data;
-	bool		hasnulls = HeapTupleHasNulls(tuple);
-	int			attnum;
-	char	   *tp;				/* ptr to tuple data */
-	long		off;			/* offset in tuple data */
-	bits8	   *bp = tup->t_bits;	/* ptr to null bitmap in tuple */
-	bool		slow;			/* can we use/set attcacheoff? */
-
-	/*
-	 * Check whether the first call for this tuple, and initialize or restore
-	 * loop state.
-	 */
-	attnum = slot->tts_nvalid;
-	if (attnum == 0)
-	{
-		/* Start from the first attribute */
-		off = 0;
-		slow = false;
-	}
-	else
-	{
-		/* Restore state from previous execution */
-		off = slot->tts_off;
-		slow = slot->tts_slow;
-	}
-
-	tp = (char *) tup + tup->t_hoff;
-
-	for (; attnum < natts; attnum++)
-	{
-		Form_pg_attribute thisatt = TupleDescAttr(tupleDesc, attnum);
-
-		if (hasnulls && att_isnull(attnum, bp))
-		{
-			values[attnum] = (Datum) 0;
-			isnull[attnum] = true;
-			slow = true;		/* can't use attcacheoff anymore */
-			continue;
-		}
-
-		isnull[attnum] = false;
-
-		if (!slow && thisatt->attcacheoff >= 0)
-			off = thisatt->attcacheoff;
-		else if (thisatt->attlen == -1)
-		{
-			/*
-			 * We can only cache the offset for a varlena attribute if the
-			 * offset is already suitably aligned, so that there would be no
-			 * pad bytes in any case: then the offset will be valid for either
-			 * an aligned or unaligned value.
-			 */
-			if (!slow &&
-				off == att_align_nominal(off, thisatt->attalign))
-				thisatt->attcacheoff = off;
-			else
-			{
-				off = att_align_pointer(off, thisatt->attalign, -1,
-										tp + off);
-				slow = true;
-			}
-		}
-		else
-		{
-			/* not varlena, so safe to use att_align_nominal */
-			off = att_align_nominal(off, thisatt->attalign);
-
-			if (!slow)
-				thisatt->attcacheoff = off;
-		}
-
-		values[attnum] = fetchatt(thisatt, tp + off);
-
-		off = att_addlength_pointer(off, thisatt->attlen, tp + off);
-
-		if (thisatt->attlen <= 0)
-			slow = true;		/* can't use attcacheoff anymore */
-	}
-
-	/*
-	 * Save state for next execution
-	 */
-	slot->tts_nvalid = attnum;
-	slot->tts_off = off;
-	slot->tts_slow = slow;
-}
-
 /*
  * slot_getattr
  *		This function fetches an attribute of the slot's current tuple.
@@ -1141,91 +1037,7 @@ slot_deform_tuple(TupleTableSlot *slot, int natts)
 Datum
 slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
 {
-	HeapTuple	tuple = slot->tts_tuple;
-	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
-	HeapTupleHeader tup;
-
-	/*
-	 * system attributes are handled by heap_getsysattr
-	 */
-	if (attnum <= 0)
-	{
-		if (tuple == NULL)		/* internal error */
-			elog(ERROR, "cannot extract system attribute from virtual tuple");
-		if (tuple == &(slot->tts_minhdr))	/* internal error */
-			elog(ERROR, "cannot extract system attribute from minimal tuple");
-		return heap_getsysattr(tuple, attnum, tupleDesc, isnull);
-	}
-
-	/*
-	 * fast path if desired attribute already cached
-	 */
-	if (attnum <= slot->tts_nvalid)
-	{
-		*isnull = slot->tts_isnull[attnum - 1];
-		return slot->tts_values[attnum - 1];
-	}
-
-	/*
-	 * return NULL if attnum is out of range according to the tupdesc
-	 */
-	if (attnum > tupleDesc->natts)
-	{
-		*isnull = true;
-		return (Datum) 0;
-	}
-
-	/*
-	 * otherwise we had better have a physical tuple (tts_nvalid should equal
-	 * natts in all virtual-tuple cases)
-	 */
-	if (tuple == NULL)			/* internal error */
-		elog(ERROR, "cannot extract attribute from empty tuple slot");
-
-	/*
-	 * return NULL if attnum is out of range according to the tuple
-	 *
-	 * (We have to check this separately because of various inheritance and
-	 * table-alteration scenarios: the tuple could be either longer or shorter
-	 * than the tupdesc.)
-	 */
-	tup = tuple->t_data;
-	if (attnum > HeapTupleHeaderGetNatts(tup))
-	{
-		*isnull = true;
-		return (Datum) 0;
-	}
-
-	/*
-	 * check if target attribute is null: no point in groveling through tuple
-	 */
-	if (HeapTupleHasNulls(tuple) && att_isnull(attnum - 1, tup->t_bits))
-	{
-		*isnull = true;
-		return (Datum) 0;
-	}
-
-	/*
-	 * If the attribute's column has been dropped, we force a NULL result.
-	 * This case should not happen in normal use, but it could happen if we
-	 * are executing a plan cached before the column was dropped.
-	 */
-	if (TupleDescAttr(tupleDesc, attnum - 1)->attisdropped)
-	{
-		*isnull = true;
-		return (Datum) 0;
-	}
-
-	/*
-	 * Extract the attribute, along with any preceding attributes.
-	 */
-	slot_deform_tuple(slot, attnum);
-
-	/*
-	 * The result is acquired from tts_values array.
-	 */
-	*isnull = slot->tts_isnull[attnum - 1];
-	return slot->tts_values[attnum - 1];
+	return slot->tts_storageslotam->slot_getattr(slot, attnum, isnull);
 }
 
 /*
@@ -1237,40 +1049,7 @@ slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
 void
 slot_getallattrs(TupleTableSlot *slot)
 {
-	int			tdesc_natts = slot->tts_tupleDescriptor->natts;
-	int			attnum;
-	HeapTuple	tuple;
-
-	/* Quick out if we have 'em all already */
-	if (slot->tts_nvalid == tdesc_natts)
-		return;
-
-	/*
-	 * otherwise we had better have a physical tuple (tts_nvalid should equal
-	 * natts in all virtual-tuple cases)
-	 */
-	tuple = slot->tts_tuple;
-	if (tuple == NULL)			/* internal error */
-		elog(ERROR, "cannot extract attribute from empty tuple slot");
-
-	/*
-	 * load up any slots available from physical tuple
-	 */
-	attnum = HeapTupleHeaderGetNatts(tuple->t_data);
-	attnum = Min(attnum, tdesc_natts);
-
-	slot_deform_tuple(slot, attnum);
-
-	/*
-	 * If tuple doesn't have all the atts indicated by tupleDesc, read the
-	 * rest as null
-	 */
-	for (; attnum < tdesc_natts; attnum++)
-	{
-		slot->tts_values[attnum] = (Datum) 0;
-		slot->tts_isnull[attnum] = true;
-	}
-	slot->tts_nvalid = tdesc_natts;
+	slot->tts_storageslotam->slot_virtualize_tuple(slot, slot->tts_tupleDescriptor->natts);
 }
 
 /*
@@ -1281,43 +1060,7 @@ slot_getallattrs(TupleTableSlot *slot)
 void
 slot_getsomeattrs(TupleTableSlot *slot, int attnum)
 {
-	HeapTuple	tuple;
-	int			attno;
-
-	/* Quick out if we have 'em all already */
-	if (slot->tts_nvalid >= attnum)
-		return;
-
-	/* Check for caller error */
-	if (attnum <= 0 || attnum > slot->tts_tupleDescriptor->natts)
-		elog(ERROR, "invalid attribute number %d", attnum);
-
-	/*
-	 * otherwise we had better have a physical tuple (tts_nvalid should equal
-	 * natts in all virtual-tuple cases)
-	 */
-	tuple = slot->tts_tuple;
-	if (tuple == NULL)			/* internal error */
-		elog(ERROR, "cannot extract attribute from empty tuple slot");
-
-	/*
-	 * load up any slots available from physical tuple
-	 */
-	attno = HeapTupleHeaderGetNatts(tuple->t_data);
-	attno = Min(attno, attnum);
-
-	slot_deform_tuple(slot, attno);
-
-	/*
-	 * If tuple doesn't have all the atts indicated by tupleDesc, read the
-	 * rest as null
-	 */
-	for (; attno < attnum; attno++)
-	{
-		slot->tts_values[attno] = (Datum) 0;
-		slot->tts_isnull[attno] = true;
-	}
-	slot->tts_nvalid = attnum;
+	slot->tts_storageslotam->slot_virtualize_tuple(slot, attnum);
 }
 
 /*
@@ -1328,42 +1071,11 @@ slot_getsomeattrs(TupleTableSlot *slot, int attnum)
 bool
 slot_attisnull(TupleTableSlot *slot, int attnum)
 {
-	HeapTuple	tuple = slot->tts_tuple;
-	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
-
-	/*
-	 * system attributes are handled by heap_attisnull
-	 */
-	if (attnum <= 0)
-	{
-		if (tuple == NULL)		/* internal error */
-			elog(ERROR, "cannot extract system attribute from virtual tuple");
-		if (tuple == &(slot->tts_minhdr))	/* internal error */
-			elog(ERROR, "cannot extract system attribute from minimal tuple");
-		return heap_attisnull(tuple, attnum);
-	}
-
-	/*
-	 * fast path if desired attribute already cached
-	 */
-	if (attnum <= slot->tts_nvalid)
-		return slot->tts_isnull[attnum - 1];
-
-	/*
-	 * return NULL if attnum is out of range according to the tupdesc
-	 */
-	if (attnum > tupleDesc->natts)
-		return true;
+	bool		isnull;
 
-	/*
-	 * otherwise we had better have a physical tuple (tts_nvalid should equal
-	 * natts in all virtual-tuple cases)
-	 */
-	if (tuple == NULL)			/* internal error */
-		elog(ERROR, "cannot extract attribute from empty tuple slot");
+	slot->tts_storageslotam->slot_getattr(slot, attnum, &isnull);
 
-	/* and let the tuple tell it */
-	return heap_attisnull(tuple, attnum);
+	return isnull;
 }
 
 /*
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index 2d3ed44baf..8cccf927e9 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -39,5 +39,7 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 	amroutine->snapshot_satisfiesUpdate = HeapTupleSatisfiesUpdate;
 	amroutine->snapshot_satisfiesVacuum = HeapTupleSatisfiesVacuum;
 
+	amroutine->slot_storageam = heapam_storage_slot_handler;
+
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/storage/storage_common.c b/src/backend/access/storage/storage_common.c
index 3ede680c89..b65153bb38 100644
--- a/src/backend/access/storage/storage_common.c
+++ b/src/backend/access/storage/storage_common.c
@@ -95,6 +95,410 @@ HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple)
 	return true;
 }
 
+/*-----------------------
+ *
+ * Slot storage handler API
+ * ----------------------
+ */
+
+static HeapTuple
+heapam_get_tuple(TupleTableSlot *slot, bool palloc_copy)
+{
+	HeapTuple	tup;
+	HeapamTuple *stuple = (HeapamTuple *) slot->tts_storage;
+
+	if (stuple)
+	{
+		if (stuple->hst_mintuple)
+		{
+			tup = heap_tuple_from_minimal_tuple(stuple->hst_mintuple);
+		}
+		else
+		{
+			if (!palloc_copy)
+				tup = stuple->hst_heaptuple;
+			else
+				tup = heap_copytuple(stuple->hst_heaptuple);
+		}
+	}
+	else
+	{
+		tup = heap_form_tuple(slot->tts_tupleDescriptor,
+							  slot->tts_values,
+							  slot->tts_isnull);
+	}
+
+	return tup;
+}
+
+static MinimalTuple
+heapam_get_min_tuple(TupleTableSlot *slot, bool palloc_copy)
+{
+	MinimalTuple tup;
+	HeapamTuple *stuple = (HeapamTuple *) slot->tts_storage;
+
+	if (stuple)
+	{
+		if (stuple->hst_mintuple)
+		{
+			if (!palloc_copy)
+				tup = stuple->hst_mintuple;
+			else
+				tup = heap_copy_minimal_tuple(stuple->hst_mintuple);
+		}
+		else
+		{
+			tup = minimal_tuple_from_heap_tuple(stuple->hst_heaptuple);
+		}
+	}
+	else
+	{
+		tup = heap_form_minimal_tuple(slot->tts_tupleDescriptor,
+									  slot->tts_values,
+									  slot->tts_isnull);
+	}
+
+	return tup;
+}
+
+
+/*
+ * slot_deform_tuple
+ *		Given a TupleTableSlot, extract data from the slot's physical tuple
+ *		into its Datum/isnull arrays.  Data is extracted up through the
+ *		natts'th column (caller must ensure this is a legal column number).
+ *
+ *		This is essentially an incremental version of heap_deform_tuple:
+ *		on each call we extract attributes up to the one needed, without
+ *		re-computing information about previously extracted attributes.
+ *		slot->tts_nvalid is the number of attributes already extracted.
+ */
+static void
+slot_deform_tuple(TupleTableSlot *slot, int natts)
+{
+	HeapamTuple *stuple = (HeapamTuple *) slot->tts_storage;
+	HeapTuple	tuple = stuple ? stuple->hst_heaptuple : NULL;
+	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
+	Datum	   *values = slot->tts_values;
+	bool	   *isnull = slot->tts_isnull;
+	HeapTupleHeader tup = tuple->t_data;
+	bool		hasnulls = HeapTupleHasNulls(tuple);
+	int			attnum;
+	char	   *tp;				/* ptr to tuple data */
+	long		off;			/* offset in tuple data */
+	bits8	   *bp = tup->t_bits;	/* ptr to null bitmap in tuple */
+	bool		slow;			/* can we use/set attcacheoff? */
+
+	/*
+	 * Check whether the first call for this tuple, and initialize or restore
+	 * loop state.
+	 */
+	attnum = slot->tts_nvalid;
+	if (attnum == 0)
+	{
+		/* Start from the first attribute */
+		off = 0;
+		slow = false;
+	}
+	else
+	{
+		/* Restore state from previous execution */
+		off = stuple->hst_off;
+		slow = stuple->hst_slow;
+	}
+
+	tp = (char *) tup + tup->t_hoff;
+
+	for (; attnum < natts; attnum++)
+	{
+		Form_pg_attribute thisatt = TupleDescAttr(tupleDesc, attnum);
+
+		if (hasnulls && att_isnull(attnum, bp))
+		{
+			values[attnum] = (Datum) 0;
+			isnull[attnum] = true;
+			slow = true;		/* can't use attcacheoff anymore */
+			continue;
+		}
+
+		isnull[attnum] = false;
+
+		if (!slow && thisatt->attcacheoff >= 0)
+			off = thisatt->attcacheoff;
+		else if (thisatt->attlen == -1)
+		{
+			/*
+			 * We can only cache the offset for a varlena attribute if the
+			 * offset is already suitably aligned, so that there would be no
+			 * pad bytes in any case: then the offset will be valid for either
+			 * an aligned or unaligned value.
+			 */
+			if (!slow &&
+				off == att_align_nominal(off, thisatt->attalign))
+				thisatt->attcacheoff = off;
+			else
+			{
+				off = att_align_pointer(off, thisatt->attalign, -1,
+										tp + off);
+				slow = true;
+			}
+		}
+		else
+		{
+			/* not varlena, so safe to use att_align_nominal */
+			off = att_align_nominal(off, thisatt->attalign);
+
+			if (!slow)
+				thisatt->attcacheoff = off;
+		}
+
+		values[attnum] = fetchatt(thisatt, tp + off);
+
+		off = att_addlength_pointer(off, thisatt->attlen, tp + off);
+
+		if (thisatt->attlen <= 0)
+			slow = true;		/* can't use attcacheoff anymore */
+	}
+
+	/*
+	 * Save state for next execution
+	 */
+	slot->tts_nvalid = attnum;
+	stuple->hst_off = off;
+	stuple->hst_slow = slow;
+}
+
+static void
+heapam_slot_virtualize_tuple(TupleTableSlot *slot, int16 upto)
+{
+	HeapamTuple *stuple;
+	HeapTuple	tuple;
+	int			attno;
+
+	/* Quick out if we have 'em all already */
+	if (slot->tts_nvalid >= upto)
+		return;
+
+	/* Check for caller error */
+	if (upto <= 0 || upto > slot->tts_tupleDescriptor->natts)
+		elog(ERROR, "invalid attribute number %d", upto);
+
+	/*
+	 * otherwise we had better have a physical tuple (tts_nvalid should equal
+	 * natts in all virtual-tuple cases)
+	 */
+	stuple = slot->tts_storage; /* XXX SlotGetTupleStorage(slot) ??? */
+	tuple = stuple->hst_heaptuple;
+	if (tuple == NULL)			/* internal error */
+		elog(ERROR, "cannot extract attribute from empty tuple slot");
+
+	/*
+	 * load up any slots available from physical tuple
+	 */
+	attno = HeapTupleHeaderGetNatts(tuple->t_data);
+	attno = Min(attno, upto);
+
+	slot_deform_tuple(slot, attno);
+
+	/*
+	 * If tuple doesn't have all the atts indicated by tupleDesc, read the
+	 * rest as null
+	 */
+	for (; attno < upto; attno++)
+	{
+		slot->tts_values[attno] = (Datum) 0;
+		slot->tts_isnull[attno] = true;
+	}
+	slot->tts_nvalid = upto;
+}
+
+static void
+heapam_slot_update_tuple_tableoid(TupleTableSlot *slot, Oid tableoid)
+{
+	HeapTuple	tuple;
+
+	tuple = heapam_get_tuple(slot, false);
+	tuple->t_tableOid = tableoid;
+}
+
+static void
+heapam_slot_store_tuple(TupleTableSlot *slot, StorageTuple tuple, bool shouldFree, bool minimum_tuple)
+{
+	HeapamTuple *stuple;
+	MemoryContext oldcontext;
+
+	oldcontext = MemoryContextSwitchTo(slot->tts_mcxt);
+
+	stuple = (HeapamTuple *) palloc0(sizeof(HeapamTuple));
+
+	if (!minimum_tuple)
+	{
+		stuple->hst_heaptuple = tuple;
+		stuple->hst_slow = false;
+		stuple->hst_off = 0;
+		stuple->hst_mintuple = NULL;
+		slot->tts_shouldFreeMin = false;
+		slot->tts_shouldFree = shouldFree;
+	}
+	else
+	{
+		stuple->hst_mintuple = tuple;
+		stuple->hst_minhdr.t_len = ((MinimalTuple) tuple)->t_len + MINIMAL_TUPLE_OFFSET;
+		stuple->hst_minhdr.t_data = (HeapTupleHeader) ((char *) tuple - MINIMAL_TUPLE_OFFSET);
+		stuple->hst_heaptuple = &stuple->hst_minhdr;
+		slot->tts_shouldFreeMin = shouldFree;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	slot->tts_tid = ((HeapTuple) tuple)->t_self;
+	slot->tts_storage = stuple;
+}
+
+static void
+heapam_slot_clear_tuple(TupleTableSlot *slot)
+{
+	HeapamTuple *stuple;
+
+	/* XXX should this be an Assert() instead? */
+	if (slot->tts_isempty)
+		return;
+
+	stuple = slot->tts_storage;
+	if (stuple == NULL)
+		return;
+
+	if (slot->tts_shouldFree)
+		heap_freetuple(stuple->hst_heaptuple);
+
+	if (slot->tts_shouldFreeMin)
+		heap_free_minimal_tuple(stuple->hst_mintuple);
+
+	slot->tts_shouldFree = false;
+	slot->tts_shouldFreeMin = false;
+
+	pfree(stuple);
+	slot->tts_storage = NULL;
+}
+
+/*
+ * slot_getattr
+ *		This function fetches an attribute of the slot's current tuple.
+ *		It is functionally equivalent to heap_getattr, but fetches of
+ *		multiple attributes of the same tuple will be optimized better,
+ *		because we avoid O(N^2) behavior from multiple calls of
+ *		nocachegetattr(), even when attcacheoff isn't usable.
+ *
+ *		A difference from raw heap_getattr is that attnums beyond the
+ *		slot's tupdesc's last attribute will be considered NULL even
+ *		when the physical tuple is longer than the tupdesc.
+ */
+static Datum
+heapam_slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
+{
+	HeapamTuple *stuple = slot->tts_storage;
+	HeapTuple	tuple = stuple ? stuple->hst_heaptuple : NULL;
+	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
+	HeapTupleHeader tup;
+
+	/*
+	 * system attributes are handled by heap_getsysattr
+	 */
+	if (attnum <= 0)
+	{
+		if (tuple == NULL)		/* internal error */
+			elog(ERROR, "cannot extract system attribute from virtual tuple");
+		if (tuple == &(stuple->hst_minhdr)) /* internal error */
+			elog(ERROR, "cannot extract system attribute from minimal tuple");
+		return heap_getsysattr(tuple, attnum, tupleDesc, isnull);
+	}
+
+	/*
+	 * fast path if desired attribute already cached
+	 */
+	if (attnum <= slot->tts_nvalid)
+	{
+		*isnull = slot->tts_isnull[attnum - 1];
+		return slot->tts_values[attnum - 1];
+	}
+
+	/*
+	 * return NULL if attnum is out of range according to the tupdesc
+	 */
+	if (attnum > tupleDesc->natts)
+	{
+		*isnull = true;
+		return (Datum) 0;
+	}
+
+	/*
+	 * otherwise we had better have a physical tuple (tts_nvalid should equal
+	 * natts in all virtual-tuple cases)
+	 */
+	if (tuple == NULL)			/* internal error */
+		elog(ERROR, "cannot extract attribute from empty tuple slot");
+
+	/*
+	 * return NULL if attnum is out of range according to the tuple
+	 *
+	 * (We have to check this separately because of various inheritance and
+	 * table-alteration scenarios: the tuple could be either longer or shorter
+	 * than the tupdesc.)
+	 */
+	tup = tuple->t_data;
+	if (attnum > HeapTupleHeaderGetNatts(tup))
+	{
+		*isnull = true;
+		return (Datum) 0;
+	}
+
+	/*
+	 * check if target attribute is null: no point in groveling through tuple
+	 */
+	if (HeapTupleHasNulls(tuple) && att_isnull(attnum - 1, tup->t_bits))
+	{
+		*isnull = true;
+		return (Datum) 0;
+	}
+
+	/*
+	 * If the attribute's column has been dropped, we force a NULL result.
+	 * This case should not happen in normal use, but it could happen if we
+	 * are executing a plan cached before the column was dropped.
+	 */
+	if (TupleDescAttr(tupleDesc, (attnum - 1))->attisdropped)
+	{
+		*isnull = true;
+		return (Datum) 0;
+	}
+
+	/*
+	 * Extract the attribute, along with any preceding attributes.
+	 */
+	slot_deform_tuple(slot, attnum);
+
+	/*
+	 * The result is acquired from tts_values array.
+	 */
+	*isnull = slot->tts_isnull[attnum - 1];
+	return slot->tts_values[attnum - 1];
+}
+
+StorageSlotAmRoutine *
+heapam_storage_slot_handler(void)
+{
+	StorageSlotAmRoutine *amroutine = palloc(sizeof(StorageSlotAmRoutine));
+
+	amroutine->slot_store_tuple = heapam_slot_store_tuple;
+	amroutine->slot_virtualize_tuple = heapam_slot_virtualize_tuple;
+	amroutine->slot_clear_tuple = heapam_slot_clear_tuple;
+	amroutine->slot_tuple = heapam_get_tuple;
+	amroutine->slot_min_tuple = heapam_get_min_tuple;
+	amroutine->slot_getattr = heapam_slot_getattr;
+	amroutine->slot_update_tableoid = heapam_slot_update_tuple_tableoid;
+
+	return amroutine;
+}
 
 /*
  * HeapTupleIsSurelyDead
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 254be28ae4..0a0eecf509 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2720,7 +2720,7 @@ CopyFrom(CopyState cstate)
 			if (slot == NULL)	/* "do nothing" */
 				skip_tuple = true;
 			else				/* trigger might have changed tuple */
-				tuple = ExecMaterializeSlot(slot);
+				tuple = ExecHeapifySlot(slot);
 		}
 
 		if (!skip_tuple)
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 4d77411a68..213a8cccbc 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -588,7 +588,7 @@ intorel_receive(TupleTableSlot *slot, DestReceiver *self)
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
 	 */
-	tuple = ExecMaterializeSlot(slot);
+	tuple = ExecHeapifySlot(slot);
 
 	/*
 	 * force assignment of new OID (see comments in ExecInsert)
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index d2e0376511..b440740e28 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -497,7 +497,7 @@ transientrel_receive(TupleTableSlot *slot, DestReceiver *self)
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
 	 */
-	tuple = ExecMaterializeSlot(slot);
+	tuple = ExecHeapifySlot(slot);
 
 	heap_insert(myState->transientrel,
 				tuple,
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 92ae3822d8..96c4fe7d43 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2289,7 +2289,7 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 					 TupleTableSlot *slot)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
-	HeapTuple	slottuple = ExecMaterializeSlot(slot);
+	HeapTuple	slottuple = ExecHeapifySlot(slot);
 	HeapTuple	newtuple = slottuple;
 	HeapTuple	oldtuple;
 	TriggerData LocTriggerData;
@@ -2370,7 +2370,7 @@ ExecIRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 					 TupleTableSlot *slot)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
-	HeapTuple	slottuple = ExecMaterializeSlot(slot);
+	HeapTuple	slottuple = ExecHeapifySlot(slot);
 	HeapTuple	newtuple = slottuple;
 	HeapTuple	oldtuple;
 	TriggerData LocTriggerData;
@@ -2728,7 +2728,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 					 TupleTableSlot *slot)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
-	HeapTuple	slottuple = ExecMaterializeSlot(slot);
+	HeapTuple	slottuple = ExecHeapifySlot(slot);
 	HeapTuple	newtuple = slottuple;
 	TriggerData LocTriggerData;
 	HeapTuple	trigtuple;
@@ -2770,7 +2770,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 	if (newSlot != NULL)
 	{
 		slot = ExecFilterJunk(relinfo->ri_junkFilter, newSlot);
-		slottuple = ExecMaterializeSlot(slot);
+		slottuple = ExecHeapifySlot(slot);
 		newtuple = slottuple;
 	}
 
@@ -2879,7 +2879,7 @@ ExecIRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 					 HeapTuple trigtuple, TupleTableSlot *slot)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
-	HeapTuple	slottuple = ExecMaterializeSlot(slot);
+	HeapTuple	slottuple = ExecHeapifySlot(slot);
 	HeapTuple	newtuple = slottuple;
 	TriggerData LocTriggerData;
 	HeapTuple	oldtuple;
@@ -4006,14 +4006,13 @@ AfterTriggerExecute(AfterTriggerEvent event,
 			 * because we start with a minimal tuple that ExecFetchSlotTuple()
 			 * must materialize anyway.
 			 */
-			LocTriggerData.tg_trigtuple =
-				ExecMaterializeSlot(trig_tuple_slot1);
+			LocTriggerData.tg_trigtuple = ExecHeapifySlot(trig_tuple_slot1);
 			LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
 
 			LocTriggerData.tg_newtuple =
 				((evtshared->ats_event & TRIGGER_EVENT_OPMASK) ==
 				 TRIGGER_EVENT_UPDATE) ?
-				ExecMaterializeSlot(trig_tuple_slot2) : NULL;
+				ExecHeapifySlot(trig_tuple_slot2) : NULL;
 			LocTriggerData.tg_newtuplebuf = InvalidBuffer;
 
 			break;
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 6c4612dad4..42939e2a3b 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -508,13 +508,15 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			Datum		d;
 
 			/* these asserts must match defenses in slot_getattr */
-			Assert(innerslot->tts_tuple != NULL);
-			Assert(innerslot->tts_tuple != &(innerslot->tts_minhdr));
+			Assert(innerslot->tts_storage != NULL);
+
+			/*
+			 * hari
+			 * Assert(innerslot->tts_storageslotam->slot_is_physical_tuple(innerslot));
+			 */
 
 			/* heap_getsysattr has sufficient defenses against bad attnums */
-			d = heap_getsysattr(innerslot->tts_tuple, attnum,
-								innerslot->tts_tupleDescriptor,
-								op->resnull);
+			d = slot_getattr(innerslot, attnum, op->resnull);
 			*op->resvalue = d;
 
 			EEO_NEXT();
@@ -526,13 +528,14 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			Datum		d;
 
 			/* these asserts must match defenses in slot_getattr */
-			Assert(outerslot->tts_tuple != NULL);
-			Assert(outerslot->tts_tuple != &(outerslot->tts_minhdr));
+			Assert(outerslot->tts_storage != NULL);
 
+			/*
+			 * hari
+			 * Assert(outerslot->tts_storageslotam->slot_is_physical_tuple(outerslot));
+			 */
 			/* heap_getsysattr has sufficient defenses against bad attnums */
-			d = heap_getsysattr(outerslot->tts_tuple, attnum,
-								outerslot->tts_tupleDescriptor,
-								op->resnull);
+			d = slot_getattr(outerslot, attnum, op->resnull);
 			*op->resvalue = d;
 
 			EEO_NEXT();
@@ -544,13 +547,15 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			Datum		d;
 
 			/* these asserts must match defenses in slot_getattr */
-			Assert(scanslot->tts_tuple != NULL);
-			Assert(scanslot->tts_tuple != &(scanslot->tts_minhdr));
+			Assert(scanslot->tts_storage != NULL);
+
+			/*
+			 * hari
+			 * Assert(scanslot->tts_storageslotam->slot_is_physical_tuple(scanslot));
+			 */
 
 			/* heap_getsysattr has sufficient defenses against bad attnums */
-			d = heap_getsysattr(scanslot->tts_tuple, attnum,
-								scanslot->tts_tupleDescriptor,
-								op->resnull);
+			d = slot_getattr(scanslot, attnum, op->resnull);
 			*op->resvalue = d;
 
 			EEO_NEXT();
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index bd786a1be6..12c15fd6bc 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -171,7 +171,7 @@ retry:
 		HTSU_Result res;
 		HeapTupleData locktup;
 
-		ItemPointerCopy(&outslot->tts_tuple->t_self, &locktup.t_self);
+		ItemPointerCopy(&outslot->tts_tid, &locktup.t_self);
 
 		PushActiveSnapshot(GetLatestSnapshot());
 
@@ -211,59 +211,6 @@ retry:
 	return found;
 }
 
-/*
- * Compare the tuple and slot and check if they have equal values.
- *
- * We use binary datum comparison which might return false negatives but
- * that's the best we can do here as there may be multiple notions of
- * equality for the data types and table columns don't specify which one
- * to use.
- */
-static bool
-tuple_equals_slot(TupleDesc desc, HeapTuple tup, TupleTableSlot *slot)
-{
-	Datum		values[MaxTupleAttributeNumber];
-	bool		isnull[MaxTupleAttributeNumber];
-	int			attrnum;
-
-	heap_deform_tuple(tup, desc, values, isnull);
-
-	/* Check equality of the attributes. */
-	for (attrnum = 0; attrnum < desc->natts; attrnum++)
-	{
-		Form_pg_attribute att;
-		TypeCacheEntry *typentry;
-
-		/*
-		 * If one value is NULL and other is not, then they are certainly not
-		 * equal
-		 */
-		if (isnull[attrnum] != slot->tts_isnull[attrnum])
-			return false;
-
-		/*
-		 * If both are NULL, they can be considered equal.
-		 */
-		if (isnull[attrnum])
-			continue;
-
-		att = TupleDescAttr(desc, attrnum);
-
-		typentry = lookup_type_cache(att->atttypid, TYPECACHE_EQ_OPR_FINFO);
-		if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
-			ereport(ERROR,
-					(errcode(ERRCODE_UNDEFINED_FUNCTION),
-					 errmsg("could not identify an equality operator for type %s",
-							format_type_be(att->atttypid))));
-
-		if (!DatumGetBool(FunctionCall2(&typentry->eq_opr_finfo,
-										values[attrnum],
-										slot->tts_values[attrnum])))
-			return false;
-	}
-
-	return true;
-}
 
 /*
  * Search the relation 'rel' for tuple using the sequential scan.
@@ -279,6 +226,7 @@ bool
 RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 						 TupleTableSlot *searchslot, TupleTableSlot *outslot)
 {
+	TupleTableSlot *scanslot;
 	HeapTuple	scantuple;
 	HeapScanDesc scan;
 	SnapshotData snap;
@@ -292,6 +240,8 @@ RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 	InitDirtySnapshot(snap);
 	scan = heap_beginscan(rel, &snap, 0, NULL);
 
+	scanslot = MakeSingleTupleTableSlot(desc);
+
 retry:
 	found = false;
 
@@ -300,12 +250,12 @@ retry:
 	/* Try to find the tuple */
 	while ((scantuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
 	{
-		if (!tuple_equals_slot(desc, scantuple, searchslot))
+		ExecStoreTuple(scantuple, scanslot, InvalidBuffer, false);
+		if (!ExecSlotCompare(scanslot, searchslot))
 			continue;
 
 		found = true;
-		ExecStoreTuple(scantuple, outslot, InvalidBuffer, false);
-		ExecMaterializeSlot(outslot);
+		ExecCopySlot(outslot, scanslot);
 
 		xwait = TransactionIdIsValid(snap.xmin) ?
 			snap.xmin : snap.xmax;
@@ -329,7 +279,7 @@ retry:
 		HTSU_Result res;
 		HeapTupleData locktup;
 
-		ItemPointerCopy(&outslot->tts_tuple->t_self, &locktup.t_self);
+		ItemPointerCopy(&outslot->tts_tid, &locktup.t_self);
 
 		PushActiveSnapshot(GetLatestSnapshot());
 
@@ -362,6 +312,7 @@ retry:
 	}
 
 	heap_endscan(scan);
+	ExecDropSingleTupleTableSlot(scanslot);
 
 	return found;
 }
@@ -404,7 +355,7 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		/* Store the slot into tuple that we can inspect. */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		/* OK, store the tuple and create index entries for it */
 		simple_heap_insert(rel, tuple);
@@ -442,6 +393,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
+	ItemPointer tid = &(searchslot->tts_tid);
 
 	/* For now we support only tables. */
 	Assert(rel->rd_rel->relkind == RELKIND_RELATION);
@@ -453,7 +405,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 		resultRelInfo->ri_TrigDesc->trig_update_before_row)
 	{
 		slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
-									&searchslot->tts_tuple->t_self,
+									tid,
 									NULL, slot);
 
 		if (slot == NULL)		/* "do nothing" */
@@ -469,21 +421,20 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		/* Store the slot into tuple that we can write. */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		/* OK, update the tuple and index entries for it */
-		simple_heap_update(rel, &searchslot->tts_tuple->t_self,
-						   slot->tts_tuple);
+		simple_heap_update(rel, tid, tuple);
 
 		if (resultRelInfo->ri_NumIndices > 0 &&
-			!HeapTupleIsHeapOnly(slot->tts_tuple))
-			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+			!HeapTupleIsHeapOnly(tuple))
+			recheckIndexes = ExecInsertIndexTuples(slot, tid,
 												   estate, false, NULL,
 												   NIL);
 
 		/* AFTER ROW UPDATE Triggers */
 		ExecARUpdateTriggers(estate, resultRelInfo,
-							 &searchslot->tts_tuple->t_self,
+							 tid,
 							 NULL, tuple, recheckIndexes, NULL);
 
 		list_free(recheckIndexes);
@@ -503,6 +454,7 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate,
 	bool		skip_tuple = false;
 	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
+	ItemPointer tid = &(searchslot->tts_tid);
 
 	/* For now we support only tables. */
 	Assert(rel->rd_rel->relkind == RELKIND_RELATION);
@@ -514,7 +466,7 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate,
 		resultRelInfo->ri_TrigDesc->trig_delete_before_row)
 	{
 		skip_tuple = !ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
-										   &searchslot->tts_tuple->t_self,
+										   tid,
 										   NULL);
 	}
 
@@ -523,11 +475,11 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate,
 		List	   *recheckIndexes = NIL;
 
 		/* OK, delete the tuple */
-		simple_heap_delete(rel, &searchslot->tts_tuple->t_self);
+		simple_heap_delete(rel, tid);
 
 		/* AFTER ROW DELETE Triggers */
 		ExecARDeleteTriggers(estate, resultRelInfo,
-							 &searchslot->tts_tuple->t_self, NULL, NULL);
+							 tid, NULL, NULL);
 
 		list_free(recheckIndexes);
 	}
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 51d2c5d166..01ca94f9e5 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -82,6 +82,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/storage_common.h"
 #include "access/tuptoaster.h"
 #include "funcapi.h"
 #include "catalog/pg_type.h"
@@ -113,16 +114,15 @@ MakeTupleTableSlot(void)
 	TupleTableSlot *slot = makeNode(TupleTableSlot);
 
 	slot->tts_isempty = true;
-	slot->tts_shouldFree = false;
-	slot->tts_shouldFreeMin = false;
-	slot->tts_tuple = NULL;
 	slot->tts_tupleDescriptor = NULL;
 	slot->tts_mcxt = CurrentMemoryContext;
-	slot->tts_buffer = InvalidBuffer;
 	slot->tts_nvalid = 0;
 	slot->tts_values = NULL;
 	slot->tts_isnull = NULL;
-	slot->tts_mintuple = NULL;
+	slot->tts_tupleOid = InvalidOid;
+	slot->tts_tableOid = InvalidOid;
+	slot->tts_storageslotam = heapam_storage_slot_handler();
+	slot->tts_storage = NULL;
 
 	return slot;
 }
@@ -205,6 +205,54 @@ MakeSingleTupleTableSlot(TupleDesc tupdesc)
 	return slot;
 }
 
+/* --------------------------------
+ *		ExecSlotCompare
+ *
+ *		This is a slot comparision function to find out
+ *		whether both the slots are same or not?
+ * --------------------------------
+ */
+bool
+ExecSlotCompare(TupleTableSlot *slot1, TupleTableSlot *slot2)
+{
+	int			attrnum;
+
+	Assert(slot1->tts_tupleDescriptor->natts == slot2->tts_tupleDescriptor->natts);
+
+	slot_getallattrs(slot1);
+	slot_getallattrs(slot2);
+
+	/* Check equality of the attributes. */
+	for (attrnum = 0; attrnum < slot1->tts_tupleDescriptor->natts; attrnum++)
+	{
+		Form_pg_attribute att;
+		TypeCacheEntry *typentry;
+
+		/*
+		 * If one value is NULL and other is not, then they are certainly not
+		 * equal
+		 */
+		if (slot1->tts_isnull[attrnum] != slot2->tts_isnull[attrnum])
+			return false;
+
+		att = TupleDescAttr(slot1->tts_tupleDescriptor, attrnum);
+
+		typentry = lookup_type_cache(att->atttypid, TYPECACHE_EQ_OPR_FINFO);
+		if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_FUNCTION),
+					 errmsg("could not identify an equality operator for type %s",
+							format_type_be(att->atttypid))));
+
+		if (!DatumGetBool(FunctionCall2(&typentry->eq_opr_finfo,
+										slot1->tts_values[attrnum],
+										slot2->tts_values[attrnum])))
+			return false;
+	}
+
+	return true;
+}
+
 /* --------------------------------
  *		ExecDropSingleTupleTableSlot
  *
@@ -317,7 +365,7 @@ ExecSetSlotDescriptor(TupleTableSlot *slot, /* slot to change */
  * --------------------------------
  */
 TupleTableSlot *
-ExecStoreTuple(HeapTuple tuple,
+ExecStoreTuple(void *tuple,
 			   TupleTableSlot *slot,
 			   Buffer buffer,
 			   bool shouldFree)
@@ -328,47 +376,27 @@ ExecStoreTuple(HeapTuple tuple,
 	Assert(tuple != NULL);
 	Assert(slot != NULL);
 	Assert(slot->tts_tupleDescriptor != NULL);
+	Assert(slot->tts_storageslotam != NULL);
 	/* passing shouldFree=true for a tuple on a disk page is not sane */
 	Assert(BufferIsValid(buffer) ? (!shouldFree) : true);
 
 	/*
 	 * Free any old physical tuple belonging to the slot.
 	 */
-	if (slot->tts_shouldFree)
-		heap_freetuple(slot->tts_tuple);
-	if (slot->tts_shouldFreeMin)
-		heap_free_minimal_tuple(slot->tts_mintuple);
+	slot->tts_storageslotam->slot_clear_tuple(slot);	/* XXX ?? */
 
 	/*
-	 * Store the new tuple into the specified slot.
+	 * Store the new tuple into the specified slot, and mark the slot as no
+	 * longer empty.  This clears any previously stored physical tuple.
 	 */
+	/* XXX should we pass the buffer down to the storageAM perhaps? */
+	slot->tts_storageslotam->slot_store_tuple(slot, tuple, shouldFree, false);
+
 	slot->tts_isempty = false;
-	slot->tts_shouldFree = shouldFree;
-	slot->tts_shouldFreeMin = false;
-	slot->tts_tuple = tuple;
-	slot->tts_mintuple = NULL;
 
 	/* Mark extracted state invalid */
 	slot->tts_nvalid = 0;
 
-	/*
-	 * If tuple is on a disk page, keep the page pinned as long as we hold a
-	 * pointer into it.  We assume the caller already has such a pin.
-	 *
-	 * This is coded to optimize the case where the slot previously held a
-	 * tuple on the same disk page: in that case releasing and re-acquiring
-	 * the pin is a waste of cycles.  This is a common situation during
-	 * seqscans, so it's worth troubling over.
-	 */
-	if (slot->tts_buffer != buffer)
-	{
-		if (BufferIsValid(slot->tts_buffer))
-			ReleaseBuffer(slot->tts_buffer);
-		slot->tts_buffer = buffer;
-		if (BufferIsValid(buffer))
-			IncrBufferRefCount(buffer);
-	}
-
 	return slot;
 }
 
@@ -395,31 +423,19 @@ ExecStoreMinimalTuple(MinimalTuple mtup,
 	/*
 	 * Free any old physical tuple belonging to the slot.
 	 */
-	if (slot->tts_shouldFree)
-		heap_freetuple(slot->tts_tuple);
-	if (slot->tts_shouldFreeMin)
-		heap_free_minimal_tuple(slot->tts_mintuple);
-
-	/*
-	 * Drop the pin on the referenced buffer, if there is one.
-	 */
-	if (BufferIsValid(slot->tts_buffer))
-		ReleaseBuffer(slot->tts_buffer);
-
-	slot->tts_buffer = InvalidBuffer;
+	slot->tts_storageslotam->slot_clear_tuple(slot);	/* XXX ?? */
 
 	/*
 	 * Store the new tuple into the specified slot.
 	 */
 	slot->tts_isempty = false;
-	slot->tts_shouldFree = false;
-	slot->tts_shouldFreeMin = shouldFree;
-	slot->tts_tuple = &slot->tts_minhdr;
-	slot->tts_mintuple = mtup;
 
-	slot->tts_minhdr.t_len = mtup->t_len + MINIMAL_TUPLE_OFFSET;
-	slot->tts_minhdr.t_data = (HeapTupleHeader) ((char *) mtup - MINIMAL_TUPLE_OFFSET);
-	/* no need to set t_self or t_tableOid since we won't allow access */
+	/*
+	 * Store the new tuple into the specified slot, and mark the slot as no
+	 * longer empty.  This clears any previously stored physical tuple.
+	 */
+	/* XXX should we pass the buffer down to the storageAM perhaps? */
+	slot->tts_storageslotam->slot_store_tuple(slot, mtup, false, true);
 
 	/* Mark extracted state invalid */
 	slot->tts_nvalid = 0;
@@ -444,25 +460,9 @@ ExecClearTuple(TupleTableSlot *slot)	/* slot in which to store tuple */
 	Assert(slot != NULL);
 
 	/*
-	 * Free the old physical tuple if necessary.
+	 * Tell the storage AM to release any resource associated with the slot.
 	 */
-	if (slot->tts_shouldFree)
-		heap_freetuple(slot->tts_tuple);
-	if (slot->tts_shouldFreeMin)
-		heap_free_minimal_tuple(slot->tts_mintuple);
-
-	slot->tts_tuple = NULL;
-	slot->tts_mintuple = NULL;
-	slot->tts_shouldFree = false;
-	slot->tts_shouldFreeMin = false;
-
-	/*
-	 * Drop the pin on the referenced buffer, if there is one.
-	 */
-	if (BufferIsValid(slot->tts_buffer))
-		ReleaseBuffer(slot->tts_buffer);
-
-	slot->tts_buffer = InvalidBuffer;
+	slot->tts_storageslotam->slot_clear_tuple(slot);
 
 	/*
 	 * Mark it empty.
@@ -541,7 +541,7 @@ ExecStoreAllNullTuple(TupleTableSlot *slot)
  *		however the "system columns" of the result will not be meaningful.
  * --------------------------------
  */
-HeapTuple
+StorageTuple
 ExecCopySlotTuple(TupleTableSlot *slot)
 {
 	/*
@@ -550,20 +550,7 @@ ExecCopySlotTuple(TupleTableSlot *slot)
 	Assert(slot != NULL);
 	Assert(!slot->tts_isempty);
 
-	/*
-	 * If we have a physical tuple (either format) then just copy it.
-	 */
-	if (TTS_HAS_PHYSICAL_TUPLE(slot))
-		return heap_copytuple(slot->tts_tuple);
-	if (slot->tts_mintuple)
-		return heap_tuple_from_minimal_tuple(slot->tts_mintuple);
-
-	/*
-	 * Otherwise we need to build a tuple from the Datum array.
-	 */
-	return heap_form_tuple(slot->tts_tupleDescriptor,
-						   slot->tts_values,
-						   slot->tts_isnull);
+	return slot->tts_storageslotam->slot_tuple(slot, true);
 }
 
 /* --------------------------------
@@ -582,21 +569,19 @@ ExecCopySlotMinimalTuple(TupleTableSlot *slot)
 	Assert(slot != NULL);
 	Assert(!slot->tts_isempty);
 
-	/*
-	 * If we have a physical tuple then just copy it.  Prefer to copy
-	 * tts_mintuple since that's a tad cheaper.
-	 */
-	if (slot->tts_mintuple)
-		return heap_copy_minimal_tuple(slot->tts_mintuple);
-	if (slot->tts_tuple)
-		return minimal_tuple_from_heap_tuple(slot->tts_tuple);
+	return slot->tts_storageslotam->slot_min_tuple(slot, true);
+}
 
+void
+ExecSlotUpdateTupleTableoid(TupleTableSlot *slot, Oid tableoid)
+{
 	/*
-	 * Otherwise we need to build a tuple from the Datum array.
+	 * sanity checks
 	 */
-	return heap_form_minimal_tuple(slot->tts_tupleDescriptor,
-								   slot->tts_values,
-								   slot->tts_isnull);
+	Assert(slot != NULL);
+	Assert(!slot->tts_isempty);
+
+	slot->tts_storageslotam->slot_update_tableoid(slot, tableoid);
 }
 
 /* --------------------------------
@@ -614,25 +599,34 @@ ExecCopySlotMinimalTuple(TupleTableSlot *slot)
  * Hence, the result must be treated as read-only.
  * --------------------------------
  */
-HeapTuple
+StorageTuple
 ExecFetchSlotTuple(TupleTableSlot *slot)
 {
+	MemoryContext oldContext;
+	StorageTuple tup;
+
 	/*
 	 * sanity checks
 	 */
 	Assert(slot != NULL);
 	Assert(!slot->tts_isempty);
 
-	/*
-	 * If we have a regular physical tuple then just return it.
-	 */
-	if (TTS_HAS_PHYSICAL_TUPLE(slot))
-		return slot->tts_tuple;
+	if (slot->tts_shouldFree)
+		return slot->tts_storageslotam->slot_tuple(slot, false);
 
 	/*
-	 * Otherwise materialize the slot...
+	 * Otherwise, copy or build a tuple, and store it into the slot.
+	 *
+	 * We may be called in a context that is shorter-lived than the tuple
+	 * slot, but we have to ensure that the materialized tuple will survive
+	 * anyway.
 	 */
-	return ExecMaterializeSlot(slot);
+	oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
+	tup = ExecCopySlotTuple(slot);
+	ExecStoreTuple(tup, slot, InvalidBuffer, true);
+	MemoryContextSwitchTo(oldContext);
+
+	return tup;
 }
 
 /* --------------------------------
@@ -652,6 +646,7 @@ MinimalTuple
 ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 {
 	MemoryContext oldContext;
+	MinimalTuple tup;
 
 	/*
 	 * sanity checks
@@ -659,11 +654,8 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 	Assert(slot != NULL);
 	Assert(!slot->tts_isempty);
 
-	/*
-	 * If we have a minimal physical tuple (local or not) then just return it.
-	 */
-	if (slot->tts_mintuple)
-		return slot->tts_mintuple;
+	if (slot->tts_shouldFreeMin)
+		return slot->tts_storageslotam->slot_min_tuple(slot, false);
 
 	/*
 	 * Otherwise, copy or build a minimal tuple, and store it into the slot.
@@ -673,18 +665,11 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 	 * anyway.
 	 */
 	oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
-	slot->tts_mintuple = ExecCopySlotMinimalTuple(slot);
-	slot->tts_shouldFreeMin = true;
+	tup = ExecCopySlotMinimalTuple(slot);
+	ExecStoreMinimalTuple(tup, slot, true);
 	MemoryContextSwitchTo(oldContext);
 
-	/*
-	 * Note: we may now have a situation where we have a local minimal tuple
-	 * attached to a virtual or non-local physical tuple.  There seems no harm
-	 * in that at the moment, but if any materializes, we should change this
-	 * function to force the slot into minimal-tuple-only state.
-	 */
-
-	return slot->tts_mintuple;
+	return tup;
 }
 
 /* --------------------------------
@@ -713,18 +698,19 @@ ExecFetchSlotTupleDatum(TupleTableSlot *slot)
  *			Force a slot into the "materialized" state.
  *
  *		This causes the slot's tuple to be a local copy not dependent on
- *		any external storage.  A pointer to the contained tuple is returned.
+ *		any external storage.
  *
  *		A typical use for this operation is to prepare a computed tuple
  *		for being stored on disk.  The original data may or may not be
  *		virtual, but in any case we need a private copy for heap_insert
- *		to scribble on.
+ *		to scribble on.  XXX is this comment good?
  * --------------------------------
  */
-HeapTuple
+void
 ExecMaterializeSlot(TupleTableSlot *slot)
 {
 	MemoryContext oldContext;
+	HeapTuple	tup;
 
 	/*
 	 * sanity checks
@@ -732,12 +718,8 @@ ExecMaterializeSlot(TupleTableSlot *slot)
 	Assert(slot != NULL);
 	Assert(!slot->tts_isempty);
 
-	/*
-	 * If we have a regular physical tuple, and it's locally palloc'd, we have
-	 * nothing to do.
-	 */
-	if (slot->tts_tuple && slot->tts_shouldFree)
-		return slot->tts_tuple;
+	if (slot->tts_shouldFree)
+		return;
 
 	/*
 	 * Otherwise, copy or build a physical tuple, and store it into the slot.
@@ -747,18 +729,10 @@ ExecMaterializeSlot(TupleTableSlot *slot)
 	 * anyway.
 	 */
 	oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
-	slot->tts_tuple = ExecCopySlotTuple(slot);
-	slot->tts_shouldFree = true;
+	tup = ExecCopySlotTuple(slot);
+	ExecStoreTuple(tup, slot, InvalidBuffer, true);
 	MemoryContextSwitchTo(oldContext);
 
-	/*
-	 * Drop the pin on the referenced buffer, if there is one.
-	 */
-	if (BufferIsValid(slot->tts_buffer))
-		ReleaseBuffer(slot->tts_buffer);
-
-	slot->tts_buffer = InvalidBuffer;
-
 	/*
 	 * Mark extracted state invalid.  This is important because the slot is
 	 * not supposed to depend any more on the previous external data; we
@@ -768,17 +742,15 @@ ExecMaterializeSlot(TupleTableSlot *slot)
 	 * that we have not pfree'd tts_mintuple, if there is one.)
 	 */
 	slot->tts_nvalid = 0;
+}
 
-	/*
-	 * On the same principle of not depending on previous remote storage,
-	 * forget the mintuple if it's not local storage.  (If it is local
-	 * storage, we must not pfree it now, since callers might have already
-	 * fetched datum pointers referencing it.)
-	 */
-	if (!slot->tts_shouldFreeMin)
-		slot->tts_mintuple = NULL;
+StorageTuple
+ExecHeapifySlot(TupleTableSlot *slot)
+{
+	ExecMaterializeSlot(slot);
+	Assert(slot->tts_storage != NULL);
 
-	return slot->tts_tuple;
+	return slot->tts_storageslotam->slot_tuple(slot, false);
 }
 
 /* --------------------------------
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index dc6cfcfa66..690308845c 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -62,7 +62,7 @@ ForeignNext(ForeignScanState *node)
 	 */
 	if (plan->fsSystemCol && !TupIsNull(slot))
 	{
-		HeapTuple	tup = ExecMaterializeSlot(slot);
+		HeapTuple	tup = ExecHeapifySlot(slot);
 
 		tup->t_tableOid = RelationGetRelid(node->ss.ss_currentRelation);
 	}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 79c34a6e6c..f0307ba50e 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -172,7 +172,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
 		 * initialize t_tableOid before evaluating them.
 		 */
 		Assert(!TupIsNull(econtext->ecxt_scantuple));
-		tuple = ExecMaterializeSlot(econtext->ecxt_scantuple);
+		tuple = ExecHeapifySlot(econtext->ecxt_scantuple);
 		tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
 	}
 	econtext->ecxt_outertuple = planSlot;
@@ -272,7 +272,7 @@ ExecInsert(ModifyTableState *mtstate,
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
 	 */
-	tuple = ExecMaterializeSlot(slot);
+	tuple = ExecHeapifySlot(slot);
 
 	/*
 	 * get information on the (current) result relation
@@ -407,7 +407,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW INSERT Triggers */
@@ -420,7 +420,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		newId = InvalidOid;
 	}
@@ -438,7 +438,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* FDW might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		/*
 		 * AFTER ROW Triggers or RETURNING expressions might reference the
@@ -747,7 +747,7 @@ ExecDelete(ModifyTableState *mtstate,
 		 */
 		if (slot->tts_isempty)
 			ExecStoreAllNullTuple(slot);
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
 	}
 	else
@@ -898,7 +898,7 @@ ldelete:;
 		 * Before releasing the target tuple again, make sure rslot has a
 		 * local copy of any pass-by-reference values.
 		 */
-		ExecMaterializeSlot(rslot);
+		ExecHeapifySlot(rslot);
 
 		ExecClearTuple(slot);
 		if (BufferIsValid(delbuffer))
@@ -959,7 +959,7 @@ ExecUpdate(ModifyTableState *mtstate,
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
 	 */
-	tuple = ExecMaterializeSlot(slot);
+	tuple = ExecHeapifySlot(slot);
 
 	/*
 	 * get information on the (current) result relation
@@ -978,7 +978,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW UPDATE Triggers */
@@ -992,7 +992,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
@@ -1008,7 +1008,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* FDW might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		/*
 		 * AFTER ROW Triggers or RETURNING expressions might reference the
@@ -1124,7 +1124,7 @@ lreplace:;
 					{
 						*tupleid = hufd.ctid;
 						slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
-						tuple = ExecMaterializeSlot(slot);
+						tuple = ExecHeapifySlot(slot);
 						goto lreplace;
 					}
 				}
diff --git a/src/backend/executor/tqueue.c b/src/backend/executor/tqueue.c
index 0dcb911c3c..ef681e2ec2 100644
--- a/src/backend/executor/tqueue.c
+++ b/src/backend/executor/tqueue.c
@@ -58,7 +58,7 @@ tqueueReceiveSlot(TupleTableSlot *slot, DestReceiver *self)
 	shm_mq_result result;
 
 	/* Send the tuple itself. */
-	tuple = ExecMaterializeSlot(slot);
+	tuple = ExecHeapifySlot(slot);
 	result = shm_mq_send(tqueue->queue, tuple->t_len, tuple->t_data, false);
 
 	/* Check for failure. */
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index fa5d9bb120..ad0aceb61e 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -729,9 +729,12 @@ apply_handle_update(StringInfo s)
 	 */
 	if (found)
 	{
+		HeapTuple	tuple;
+
 		/* Process and store remote tuple in the slot */
 		oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
-		ExecStoreTuple(localslot->tts_tuple, remoteslot, InvalidBuffer, false);
+		tuple = ExecHeapifySlot(localslot);
+		ExecStoreTuple(tuple, remoteslot, InvalidBuffer, false);
 		slot_modify_cstrings(remoteslot, rel, newtup.values, newtup.changed);
 		MemoryContextSwitchTo(oldctx);
 
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index b0d4c54121..168edb058d 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -20,6 +20,19 @@
 #include "access/transam.h"
 #include "storage/bufpage.h"
 
+/*
+ * Opaque tuple representation for executor's TupleTableSlot tts_storage
+ * (XXX This should probably live in a separate header)
+ */
+typedef struct HeapamTuple
+{
+	HeapTuple	hst_heaptuple;
+	bool		hst_slow;
+	long		hst_off;
+	MinimalTuple hst_mintuple;	/* minimal tuple, or NULL if none */
+	HeapTupleData hst_minhdr;	/* workspace for minimal-tuple-only case */
+}			HeapamTuple;
+
 /*
  * MaxTupleAttributeNumber limits the number of (user) columns in a tuple.
  * The key limit on this value is that the size of the fixed overhead for
@@ -658,7 +671,7 @@ struct MinimalTupleData
 /*
  * GETSTRUCT - given a HeapTuple pointer, return address of the user data
  */
-#define GETSTRUCT(TUP) ((char *) ((TUP)->t_data) + (TUP)->t_data->t_hoff)
+#define GETSTRUCT(TUP) ((char *) (((HeapTuple)(TUP))->t_data) + ((HeapTuple)(TUP))->t_data->t_hoff)
 
 /*
  * Accessor macros to be used with HeapTuple pointers.
diff --git a/src/include/access/storage_common.h b/src/include/access/storage_common.h
index 791f42dd31..5f799406fe 100644
--- a/src/include/access/storage_common.h
+++ b/src/include/access/storage_common.h
@@ -21,9 +21,46 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "access/xlog.h"
+#include "executor/tuptable.h"
 #include "storage/bufpage.h"
 #include "storage/bufmgr.h"
 
+/* A physical tuple coming from a storage AM scan */
+typedef void *StorageTuple;
+
+/*
+ * slot storage routine functions
+ */
+typedef void (*SlotStoreTuple_function) (TupleTableSlot *slot,
+										 StorageTuple tuple,
+										 bool shouldFree,
+										 bool minumumtuple);
+typedef void (*SlotClearTuple_function) (TupleTableSlot *slot);
+typedef Datum (*SlotGetattr_function) (TupleTableSlot *slot,
+									   int attnum, bool *isnull);
+typedef void (*SlotVirtualizeTuple_function) (TupleTableSlot *slot, int16 upto);
+
+typedef HeapTuple (*SlotGetTuple_function) (TupleTableSlot *slot, bool palloc_copy);
+typedef MinimalTuple (*SlotGetMinTuple_function) (TupleTableSlot *slot, bool palloc_copy);
+
+typedef void (*SlotUpdateTableoid_function) (TupleTableSlot *slot, Oid tableoid);
+
+typedef void (*SpeculativeAbort_function) (Relation rel,
+										   TupleTableSlot *slot);
+
+typedef struct StorageSlotAmRoutine
+{
+	/* Operations on TupleTableSlot */
+	SlotStoreTuple_function slot_store_tuple;
+	SlotVirtualizeTuple_function slot_virtualize_tuple;
+	SlotClearTuple_function slot_clear_tuple;
+	SlotGetattr_function slot_getattr;
+	SlotGetTuple_function slot_tuple;
+	SlotGetMinTuple_function slot_min_tuple;
+	SlotUpdateTableoid_function slot_update_tableoid;
+}			StorageSlotAmRoutine;
+
+typedef StorageSlotAmRoutine * (*slot_storageam_hook) (void);
 
 /* Result codes for HeapTupleSatisfiesVacuum */
 typedef enum
@@ -42,6 +79,7 @@ extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
 extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
 extern bool HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin);
 extern bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
+extern StorageSlotAmRoutine * heapam_storage_slot_handler(void);
 
 /*
  * SetHintBits()
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
index f5d5edd704..5e807d827e 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -16,15 +16,14 @@
 #include "utils/snapshot.h"
 #include "fmgr.h"
 
-/* A physical tuple coming from a storage AM scan */
-typedef void *StorageTuple;
-
+/*
+ * Storage routine functions
+ */
 typedef bool (*SnapshotSatisfies_function) (StorageTuple htup, Snapshot snapshot, Buffer buffer);
 typedef HTSU_Result (*SnapshotSatisfiesUpdate_function) (StorageTuple htup, CommandId curcid, Buffer buffer);
 typedef HTSV_Result (*SnapshotSatisfiesVacuum_function) (StorageTuple htup, TransactionId OldestXmin, Buffer buffer);
 
 
-
 /*
  * API struct for a storage AM.  Note this must be stored in a single palloc'd
  * chunk of memory.
@@ -42,6 +41,8 @@ typedef struct StorageAmRoutine
 	SnapshotSatisfiesUpdate_function snapshot_satisfiesUpdate;	/* HeapTupleSatisfiesUpdate */
 	SnapshotSatisfiesVacuum_function snapshot_satisfiesVacuum;	/* HeapTupleSatisfiesVacuum */
 
+	slot_storageam_hook slot_storageam;
+
 }			StorageAmRoutine;
 
 extern StorageAmRoutine * GetStorageAmRoutine(Oid amhandler);
diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h
index db2a42af5e..e6ff66f14b 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -18,9 +18,25 @@
 #include "access/tupdesc.h"
 #include "storage/buf.h"
 
+/*
+ * Forward declare StorageAmRoutine to avoid including storageamapi.h here
+ */
+struct StorageSlotAmRoutine;
+
+/*
+ * Forward declare StorageTuple to avoid including storageamapi.h here
+ */
+typedef void *StorageTuple;
+
 /*----------
  * The executor stores tuples in a "tuple table" which is a List of
- * independent TupleTableSlots.  There are several cases we need to handle:
+ * independent TupleTableSlots.
+ *
+ * XXX The "html-commented out" text below no longer reflects reality, as
+ * physical tuples are now responsibility of storage AMs.  But we have kept
+ * "minimal tuples".  Adjust this comment!
+ *
+ * <!-- There are several cases we need to handle:
  *		1. physical tuple in a disk buffer page
  *		2. physical tuple constructed in palloc'ed memory
  *		3. "minimal" physical tuple constructed in palloc'ed memory
@@ -56,6 +72,7 @@
  * had the fatal defect of invalidating any pass-by-reference Datums pointing
  * into the existing slot contents.)  Both copies must contain identical data
  * payloads when this is the case.
+ * -->
  *
  * The Datum/isnull arrays of a TupleTableSlot serve double duty.  When the
  * slot contains a virtual tuple, they are the authoritative data.  When the
@@ -82,11 +99,6 @@
  * When tts_shouldFree is true, the physical tuple is "owned" by the slot
  * and should be freed when the slot's reference to the tuple is dropped.
  *
- * If tts_buffer is not InvalidBuffer, then the slot is holding a pin
- * on the indicated buffer page; drop the pin when we release the
- * slot's reference to that buffer.  (tts_shouldFree should always be
- * false in such a case, since presumably tts_tuple is pointing at the
- * buffer page.)
  *
  * tts_nvalid indicates the number of valid columns in the tts_values/isnull
  * arrays.  When the slot is holding a "virtual" tuple this must be equal
@@ -114,24 +126,21 @@ typedef struct TupleTableSlot
 {
 	NodeTag		type;
 	bool		tts_isempty;	/* true = slot is empty */
-	bool		tts_shouldFree; /* should pfree tts_tuple? */
-	bool		tts_shouldFreeMin;	/* should pfree tts_mintuple? */
-	bool		tts_slow;		/* saved state for slot_deform_tuple */
-	HeapTuple	tts_tuple;		/* physical tuple, or NULL if virtual */
+	ItemPointerData tts_tid;	/* XXX describe */
 	TupleDesc	tts_tupleDescriptor;	/* slot's tuple descriptor */
 	MemoryContext tts_mcxt;		/* slot itself is in this context */
-	Buffer		tts_buffer;		/* tuple's buffer, or InvalidBuffer */
+	Oid			tts_tableOid;	/* XXX describe */
+	Oid			tts_tupleOid;	/* XXX describe */
 	int			tts_nvalid;		/* # of valid values in tts_values */
+	uint32		tts_speculativeToken;	/* XXX describe */
+	bool		tts_shouldFree;
+	bool		tts_shouldFreeMin;
 	Datum	   *tts_values;		/* current per-attribute values */
 	bool	   *tts_isnull;		/* current per-attribute isnull flags */
-	MinimalTuple tts_mintuple;	/* minimal tuple, or NULL if none */
-	HeapTupleData tts_minhdr;	/* workspace for minimal-tuple-only case */
-	long		tts_off;		/* saved state for slot_deform_tuple */
+	struct StorageSlotAmRoutine *tts_storageslotam; /* storage AM */
+	void	   *tts_storage;	/* storage AM's opaque space */
 } TupleTableSlot;
 
-#define TTS_HAS_PHYSICAL_TUPLE(slot)  \
-	((slot)->tts_tuple != NULL && (slot)->tts_tuple != &((slot)->tts_minhdr))
-
 /*
  * TupIsNull -- is a TupleTableSlot empty?
  */
@@ -143,9 +152,10 @@ extern TupleTableSlot *MakeTupleTableSlot(void);
 extern TupleTableSlot *ExecAllocTableSlot(List **tupleTable);
 extern void ExecResetTupleTable(List *tupleTable, bool shouldFree);
 extern TupleTableSlot *MakeSingleTupleTableSlot(TupleDesc tupdesc);
+extern bool ExecSlotCompare(TupleTableSlot *slot1, TupleTableSlot *slot2);
 extern void ExecDropSingleTupleTableSlot(TupleTableSlot *slot);
 extern void ExecSetSlotDescriptor(TupleTableSlot *slot, TupleDesc tupdesc);
-extern TupleTableSlot *ExecStoreTuple(HeapTuple tuple,
+extern TupleTableSlot *ExecStoreTuple(void *tuple,
 			   TupleTableSlot *slot,
 			   Buffer buffer,
 			   bool shouldFree);
@@ -155,12 +165,14 @@ extern TupleTableSlot *ExecStoreMinimalTuple(MinimalTuple mtup,
 extern TupleTableSlot *ExecClearTuple(TupleTableSlot *slot);
 extern TupleTableSlot *ExecStoreVirtualTuple(TupleTableSlot *slot);
 extern TupleTableSlot *ExecStoreAllNullTuple(TupleTableSlot *slot);
-extern HeapTuple ExecCopySlotTuple(TupleTableSlot *slot);
+extern StorageTuple ExecCopySlotTuple(TupleTableSlot *slot);
 extern MinimalTuple ExecCopySlotMinimalTuple(TupleTableSlot *slot);
-extern HeapTuple ExecFetchSlotTuple(TupleTableSlot *slot);
+extern void ExecSlotUpdateTupleTableoid(TupleTableSlot *slot, Oid tableoid);
+extern StorageTuple ExecFetchSlotTuple(TupleTableSlot *slot);
 extern MinimalTuple ExecFetchSlotMinimalTuple(TupleTableSlot *slot);
 extern Datum ExecFetchSlotTupleDatum(TupleTableSlot *slot);
-extern HeapTuple ExecMaterializeSlot(TupleTableSlot *slot);
+extern void ExecMaterializeSlot(TupleTableSlot *slot);
+extern StorageTuple ExecHeapifySlot(TupleTableSlot *slot);
 extern TupleTableSlot *ExecCopySlot(TupleTableSlot *dstslot,
 			 TupleTableSlot *srcslot);
 
-- 
2.15.0.windows.1



  [application/octet-stream] 0006-Tuple-Insert-API-is-added-to-Storage-AM.patch (102.1K, ../../CAJrrPGdsQSRuq7N3W63CWXVkM_Acgc8HBphDvzOq1ik=Mt6j+Q@mail.gmail.com/9-0006-Tuple-Insert-API-is-added-to-Storage-AM.patch)
  download | inline diff:
From 4abb8b578a22b3ee46d608af41ec52b50bf072af Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Mon, 11 Dec 2017 21:36:49 +1100
Subject: [PATCH 6/8] Tuple Insert API is added to Storage AM

heap_insert, heap_delete, heap_fetch, heap_update,
heap_get_latest_oid, heap_lock_tuple and heap_multi_insert
functions are added to storage AM. Move the index insertion
logic into storage AM, The Index insert still outside for
the case of multi_insert (Yet to change).

Replaced the usage of HeapTuple with storageTuple in
some places, increased the use of slot.
---
 src/backend/access/common/heaptuple.c       |  24 +++
 src/backend/access/heap/heapam.c            | 136 +++++++++-------
 src/backend/access/heap/heapam_storage.c    | 227 ++++++++++++++++++++++++++
 src/backend/access/heap/rewriteheap.c       |   5 +-
 src/backend/access/heap/tuptoaster.c        |   9 +-
 src/backend/access/storage/storage_common.c |  16 +-
 src/backend/access/storage/storageam.c      | 126 ++++++++++++++
 src/backend/commands/copy.c                 |  39 ++---
 src/backend/commands/createas.c             |  24 +--
 src/backend/commands/matview.c              |  22 ++-
 src/backend/commands/tablecmds.c            |   6 +-
 src/backend/commands/trigger.c              |  50 +++---
 src/backend/executor/execIndexing.c         |   2 +-
 src/backend/executor/execMain.c             | 131 ++++++++-------
 src/backend/executor/execReplication.c      |  72 ++++----
 src/backend/executor/nodeLockRows.c         |  47 +++---
 src/backend/executor/nodeModifyTable.c      | 244 +++++++++++++---------------
 src/backend/executor/nodeTidscan.c          |  23 ++-
 src/backend/utils/adt/tid.c                 |   5 +-
 src/include/access/heapam.h                 |  15 +-
 src/include/access/htup_details.h           |   1 +
 src/include/access/storage_common.h         |   5 -
 src/include/access/storageam.h              |  63 +++++++
 src/include/access/storageamapi.h           | 102 ++++++++++++
 src/include/commands/trigger.h              |   2 +-
 src/include/executor/executor.h             |  15 +-
 src/include/executor/tuptable.h             |   1 +
 src/include/nodes/execnodes.h               |   8 +-
 28 files changed, 981 insertions(+), 439 deletions(-)
 create mode 100644 src/include/access/storageam.h

diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index c29935b91a..a003df1c28 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -685,6 +685,30 @@ heap_copy_tuple_as_datum(HeapTuple tuple, TupleDesc tupleDesc)
 	return PointerGetDatum(td);
 }
 
+/*
+ * heap_form_tuple_by_datum
+ *		construct a tuple from the given dataum
+ *
+ * The result is allocated in the current memory context.
+ */
+HeapTuple
+heap_form_tuple_by_datum(Datum data, Oid tableoid)
+{
+	HeapTuple	newTuple;
+	HeapTupleHeader td;
+
+	td = DatumGetHeapTupleHeader(data);
+
+	newTuple = (HeapTuple) palloc(HEAPTUPLESIZE + HeapTupleHeaderGetDatumLength(td));
+	newTuple->t_len = HeapTupleHeaderGetDatumLength(td);
+	newTuple->t_self = td->t_ctid;
+	newTuple->t_tableOid = tableoid;
+	newTuple->t_data = (HeapTupleHeader) ((char *) newTuple + HEAPTUPLESIZE);
+	memcpy((char *) newTuple->t_data, (char *) td, newTuple->t_len);
+
+	return newTuple;
+}
+
 /*
  * heap_form_tuple
  *		construct a tuple from the given values[] and isnull[] arrays,
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index d63afbaf69..84358748bd 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -1893,13 +1893,13 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
  */
 bool
 heap_fetch(Relation relation,
+		   ItemPointer tid,
 		   Snapshot snapshot,
 		   HeapTuple tuple,
 		   Buffer *userbuf,
 		   bool keep_buf,
 		   Relation stats_relation)
 {
-	ItemPointer tid = &(tuple->t_self);
 	ItemId		lp;
 	Buffer		buffer;
 	Page		page;
@@ -1933,7 +1933,6 @@ heap_fetch(Relation relation,
 			ReleaseBuffer(buffer);
 			*userbuf = InvalidBuffer;
 		}
-		tuple->t_data = NULL;
 		return false;
 	}
 
@@ -1955,13 +1954,13 @@ heap_fetch(Relation relation,
 			ReleaseBuffer(buffer);
 			*userbuf = InvalidBuffer;
 		}
-		tuple->t_data = NULL;
 		return false;
 	}
 
 	/*
-	 * fill in *tuple fields
+	 * fill in tuple fields and place it in stuple
 	 */
+	ItemPointerCopy(tid, &(tuple->t_self));
 	tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
 	tuple->t_len = ItemIdGetLength(lp);
 	tuple->t_tableOid = RelationGetRelid(relation);
@@ -2312,6 +2311,18 @@ heap_get_latest_tid(Relation relation,
 	}							/* end of loop */
 }
 
+/*
+ * HeapTupleSetHintBits --- exported version of SetHintBits()
+ *
+ * This must be separate because of C99's brain-dead notions about how to
+ * implement inline functions.
+ */
+static void
+HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
+					 uint16 infomask, TransactionId xid)
+{
+	SetHintBits(tuple, buffer, infomask, xid);
+}
 
 /*
  * UpdateXmaxHintBits - update tuple hint bits after xmax transaction ends
@@ -4576,13 +4587,12 @@ get_mxact_status_for_lock(LockTupleMode mode, bool is_update)
  * See README.tuplock for a thorough explanation of this mechanism.
  */
 HTSU_Result
-heap_lock_tuple(Relation relation, HeapTuple tuple,
+heap_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * stuple,
 				CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 				bool follow_updates,
 				Buffer *buffer, HeapUpdateFailureData *hufd)
 {
 	HTSU_Result result;
-	ItemPointer tid = &(tuple->t_self);
 	ItemId		lp;
 	Page		page;
 	Buffer		vmbuffer = InvalidBuffer;
@@ -4595,6 +4605,9 @@ heap_lock_tuple(Relation relation, HeapTuple tuple,
 	bool		first_time = true;
 	bool		have_tuple_lock = false;
 	bool		cleared_all_frozen = false;
+	HeapTupleData tuple;
+
+	Assert(stuple != NULL);
 
 	*buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
 	block = ItemPointerGetBlockNumber(tid);
@@ -4614,12 +4627,13 @@ heap_lock_tuple(Relation relation, HeapTuple tuple,
 	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid));
 	Assert(ItemIdIsNormal(lp));
 
-	tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	tuple->t_len = ItemIdGetLength(lp);
-	tuple->t_tableOid = RelationGetRelid(relation);
+	tuple.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+	tuple.t_len = ItemIdGetLength(lp);
+	tuple.t_tableOid = RelationGetRelid(relation);
+	ItemPointerCopy(tid, &tuple.t_self);
 
 l3:
-	result = relation->rd_stamroutine->snapshot_satisfiesUpdate(tuple, cid, *buffer);
+	result = HeapTupleSatisfiesUpdate(&tuple, cid, *buffer);
 
 	if (result == HeapTupleInvisible)
 	{
@@ -4641,10 +4655,10 @@ l3:
 		ItemPointerData t_ctid;
 
 		/* must copy state data before unlocking buffer */
-		xwait = HeapTupleHeaderGetRawXmax(tuple->t_data);
-		infomask = tuple->t_data->t_infomask;
-		infomask2 = tuple->t_data->t_infomask2;
-		ItemPointerCopy(&tuple->t_data->t_ctid, &t_ctid);
+		xwait = HeapTupleHeaderGetRawXmax(tuple.t_data);
+		infomask = tuple.t_data->t_infomask;
+		infomask2 = tuple.t_data->t_infomask2;
+		ItemPointerCopy(&tuple.t_data->t_ctid, &t_ctid);
 
 		LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
 
@@ -4776,7 +4790,7 @@ l3:
 				{
 					HTSU_Result res;
 
-					res = heap_lock_updated_tuple(relation, tuple, &t_ctid,
+					res = heap_lock_updated_tuple(relation, &tuple, &t_ctid,
 												  GetCurrentTransactionId(),
 												  mode);
 					if (res != HeapTupleMayBeUpdated)
@@ -4797,8 +4811,8 @@ l3:
 				 * now need to follow the update chain to lock the new
 				 * versions.
 				 */
-				if (!HeapTupleHeaderIsOnlyLocked(tuple->t_data) &&
-					((tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED) ||
+				if (!HeapTupleHeaderIsOnlyLocked(tuple.t_data) &&
+					((tuple.t_data->t_infomask2 & HEAP_KEYS_UPDATED) ||
 					 !updated))
 					goto l3;
 
@@ -4829,8 +4843,8 @@ l3:
 				 * Make sure it's still an appropriate lock, else start over.
 				 * See above about allowing xmax to change.
 				 */
-				if (!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask) ||
-					HEAP_XMAX_IS_EXCL_LOCKED(tuple->t_data->t_infomask))
+				if (!HEAP_XMAX_IS_LOCKED_ONLY(tuple.t_data->t_infomask) ||
+					HEAP_XMAX_IS_EXCL_LOCKED(tuple.t_data->t_infomask))
 					goto l3;
 				require_sleep = false;
 			}
@@ -4852,8 +4866,8 @@ l3:
 					 * meantime, start over.
 					 */
 					LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-					if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
-						!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple->t_data),
+					if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
+						!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple.t_data),
 											 xwait))
 						goto l3;
 
@@ -4866,9 +4880,9 @@ l3:
 				LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
 
 				/* if the xmax changed in the meantime, start over */
-				if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
+				if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
 					!TransactionIdEquals(
-										 HeapTupleHeaderGetRawXmax(tuple->t_data),
+										 HeapTupleHeaderGetRawXmax(tuple.t_data),
 										 xwait))
 					goto l3;
 				/* otherwise, we're good */
@@ -4893,11 +4907,11 @@ l3:
 		{
 			/* ... but if the xmax changed in the meantime, start over */
 			LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-			if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
-				!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple->t_data),
+			if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
+				!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple.t_data),
 									 xwait))
 				goto l3;
-			Assert(HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask));
+			Assert(HEAP_XMAX_IS_LOCKED_ONLY(tuple.t_data->t_infomask));
 			require_sleep = false;
 		}
 
@@ -4954,7 +4968,7 @@ l3:
 				{
 					case LockWaitBlock:
 						MultiXactIdWait((MultiXactId) xwait, status, infomask,
-										relation, &tuple->t_self, XLTW_Lock, NULL);
+										relation, &tuple.t_self, XLTW_Lock, NULL);
 						break;
 					case LockWaitSkip:
 						if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
@@ -4995,7 +5009,7 @@ l3:
 				switch (wait_policy)
 				{
 					case LockWaitBlock:
-						XactLockTableWait(xwait, relation, &tuple->t_self,
+						XactLockTableWait(xwait, relation, &tuple.t_self,
 										  XLTW_Lock);
 						break;
 					case LockWaitSkip:
@@ -5022,7 +5036,7 @@ l3:
 			{
 				HTSU_Result res;
 
-				res = heap_lock_updated_tuple(relation, tuple, &t_ctid,
+				res = heap_lock_updated_tuple(relation, &tuple, &t_ctid,
 											  GetCurrentTransactionId(),
 											  mode);
 				if (res != HeapTupleMayBeUpdated)
@@ -5041,8 +5055,8 @@ l3:
 			 * other xact could update this tuple before we get to this point.
 			 * Check for xmax change, and start over if so.
 			 */
-			if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
-				!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple->t_data),
+			if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
+				!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple.t_data),
 									 xwait))
 				goto l3;
 
@@ -5056,7 +5070,7 @@ l3:
 				 * don't check for this in the multixact case, because some
 				 * locker transactions might still be running.
 				 */
-				UpdateXmaxHintBits(tuple->t_data, *buffer, xwait);
+				UpdateXmaxHintBits(tuple.t_data, *buffer, xwait);
 			}
 		}
 
@@ -5068,9 +5082,9 @@ l3:
 		 * at all for whatever reason.
 		 */
 		if (!require_sleep ||
-			(tuple->t_data->t_infomask & HEAP_XMAX_INVALID) ||
-			HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask) ||
-			HeapTupleHeaderIsOnlyLocked(tuple->t_data))
+			(tuple.t_data->t_infomask & HEAP_XMAX_INVALID) ||
+			HEAP_XMAX_IS_LOCKED_ONLY(tuple.t_data->t_infomask) ||
+			HeapTupleHeaderIsOnlyLocked(tuple.t_data))
 			result = HeapTupleMayBeUpdated;
 		else
 			result = HeapTupleUpdated;
@@ -5081,11 +5095,11 @@ failed:
 	{
 		Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated ||
 			   result == HeapTupleWouldBlock);
-		Assert(!(tuple->t_data->t_infomask & HEAP_XMAX_INVALID));
-		hufd->ctid = tuple->t_data->t_ctid;
-		hufd->xmax = HeapTupleHeaderGetUpdateXid(tuple->t_data);
+		Assert(!(tuple.t_data->t_infomask & HEAP_XMAX_INVALID));
+		hufd->ctid = tuple.t_data->t_ctid;
+		hufd->xmax = HeapTupleHeaderGetUpdateXid(tuple.t_data);
 		if (result == HeapTupleSelfUpdated)
-			hufd->cmax = HeapTupleHeaderGetCmax(tuple->t_data);
+			hufd->cmax = HeapTupleHeaderGetCmax(tuple.t_data);
 		else
 			hufd->cmax = InvalidCommandId;
 		goto out_locked;
@@ -5108,8 +5122,8 @@ failed:
 		goto l3;
 	}
 
-	xmax = HeapTupleHeaderGetRawXmax(tuple->t_data);
-	old_infomask = tuple->t_data->t_infomask;
+	xmax = HeapTupleHeaderGetRawXmax(tuple.t_data);
+	old_infomask = tuple.t_data->t_infomask;
 
 	/*
 	 * If this is the first possibly-multixact-able operation in the current
@@ -5126,7 +5140,7 @@ failed:
 	 * not modify the tuple just yet, because that would leave it in the wrong
 	 * state if multixact.c elogs.
 	 */
-	compute_new_xmax_infomask(xmax, old_infomask, tuple->t_data->t_infomask2,
+	compute_new_xmax_infomask(xmax, old_infomask, tuple.t_data->t_infomask2,
 							  GetCurrentTransactionId(), mode, false,
 							  &xid, &new_infomask, &new_infomask2);
 
@@ -5142,13 +5156,13 @@ failed:
 	 * Also reset the HOT UPDATE bit, but only if there's no update; otherwise
 	 * we would break the HOT chain.
 	 */
-	tuple->t_data->t_infomask &= ~HEAP_XMAX_BITS;
-	tuple->t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
-	tuple->t_data->t_infomask |= new_infomask;
-	tuple->t_data->t_infomask2 |= new_infomask2;
+	tuple.t_data->t_infomask &= ~HEAP_XMAX_BITS;
+	tuple.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+	tuple.t_data->t_infomask |= new_infomask;
+	tuple.t_data->t_infomask2 |= new_infomask2;
 	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
-		HeapTupleHeaderClearHotUpdated(tuple->t_data);
-	HeapTupleHeaderSetXmax(tuple->t_data, xid);
+		HeapTupleHeaderClearHotUpdated(tuple.t_data);
+	HeapTupleHeaderSetXmax(tuple.t_data, xid);
 
 	/*
 	 * Make sure there is no forward chain link in t_ctid.  Note that in the
@@ -5158,7 +5172,7 @@ failed:
 	 * the tuple as well.
 	 */
 	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
-		tuple->t_data->t_ctid = *tid;
+		tuple.t_data->t_ctid = *tid;
 
 	/* Clear only the all-frozen bit on visibility map if needed */
 	if (PageIsAllVisible(page) &&
@@ -5189,10 +5203,10 @@ failed:
 		XLogBeginInsert();
 		XLogRegisterBuffer(0, *buffer, REGBUF_STANDARD);
 
-		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self);
+		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple.t_self);
 		xlrec.locking_xid = xid;
 		xlrec.infobits_set = compute_infobits(new_infomask,
-											  tuple->t_data->t_infomask2);
+											  tuple.t_data->t_infomask2);
 		xlrec.flags = cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
 		XLogRegisterData((char *) &xlrec, SizeOfHeapLock);
 
@@ -5226,6 +5240,7 @@ out_unlocked:
 	if (have_tuple_lock)
 		UnlockTupleTuplock(relation, tid, mode);
 
+	*stuple = heap_copytuple(&tuple);
 	return result;
 }
 
@@ -5683,9 +5698,8 @@ heap_lock_updated_tuple_rec(Relation rel, ItemPointer tid, TransactionId xid,
 		new_infomask = 0;
 		new_xmax = InvalidTransactionId;
 		block = ItemPointerGetBlockNumber(&tupid);
-		ItemPointerCopy(&tupid, &(mytup.t_self));
 
-		if (!heap_fetch(rel, SnapshotAny, &mytup, &buf, false, NULL))
+		if (!heap_fetch(rel, &tupid, SnapshotAny, &mytup, &buf, false, NULL))
 		{
 			/*
 			 * if we fail to find the updated version of the tuple, it's
@@ -6032,14 +6046,18 @@ heap_lock_updated_tuple(Relation rel, HeapTuple tuple, ItemPointer ctid,
  * An explicit confirmation WAL record also makes logical decoding simpler.
  */
 void
-heap_finish_speculative(Relation relation, HeapTuple tuple)
+heap_finish_speculative(Relation relation, TupleTableSlot *slot)
 {
+	HeapamTuple *stuple = (HeapamTuple *) slot->tts_storage;
+	HeapTuple	tuple = stuple->hst_heaptuple;
 	Buffer		buffer;
 	Page		page;
 	OffsetNumber offnum;
 	ItemId		lp = NULL;
 	HeapTupleHeader htup;
 
+	Assert(slot->tts_speculativeToken != 0);
+
 	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&(tuple->t_self)));
 	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 	page = (Page) BufferGetPage(buffer);
@@ -6094,6 +6112,7 @@ heap_finish_speculative(Relation relation, HeapTuple tuple)
 	END_CRIT_SECTION();
 
 	UnlockReleaseBuffer(buffer);
+	slot->tts_speculativeToken = 0;
 }
 
 /*
@@ -6123,8 +6142,10 @@ heap_finish_speculative(Relation relation, HeapTuple tuple)
  * confirmation records.
  */
 void
-heap_abort_speculative(Relation relation, HeapTuple tuple)
+heap_abort_speculative(Relation relation, TupleTableSlot *slot)
 {
+	HeapamTuple *stuple = (HeapamTuple *) slot->tts_storage;
+	HeapTuple	tuple = stuple->hst_heaptuple;
 	TransactionId xid = GetCurrentTransactionId();
 	ItemPointer tid = &(tuple->t_self);
 	ItemId		lp;
@@ -6133,6 +6154,10 @@ heap_abort_speculative(Relation relation, HeapTuple tuple)
 	BlockNumber block;
 	Buffer		buffer;
 
+	/*
+	 * Assert(slot->tts_speculativeToken != 0); This needs some update in
+	 * toast
+	 */
 	Assert(ItemPointerIsValid(tid));
 
 	block = ItemPointerGetBlockNumber(tid);
@@ -6246,6 +6271,7 @@ heap_abort_speculative(Relation relation, HeapTuple tuple)
 
 	/* count deletion, as we counted the insertion too */
 	pgstat_count_heap_delete(relation);
+	slot->tts_speculativeToken = 0;
 }
 
 /*
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index 8cccf927e9..77fdf7f91a 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -22,6 +22,7 @@
 
 #include "access/storageamapi.h"
 #include "utils/builtins.h"
+#include "utils/rel.h"
 
 extern bool HeapTupleSatisfies(StorageTuple stup, Snapshot snapshot, Buffer buffer);
 extern HTSU_Result HeapTupleSatisfiesUpdate(StorageTuple stup, CommandId curcid,
@@ -29,6 +30,219 @@ extern HTSU_Result HeapTupleSatisfiesUpdate(StorageTuple stup, CommandId curcid,
 extern HTSV_Result HeapTupleSatisfiesVacuum(StorageTuple stup, TransactionId OldestXmin,
 						 Buffer buffer);
 
+/* ----------------------------------------------------------------
+ *				storage AM support routines for heapam
+ * ----------------------------------------------------------------
+ */
+
+static bool
+heapam_fetch(Relation relation,
+			 ItemPointer tid,
+			 Snapshot snapshot,
+			 StorageTuple * stuple,
+			 Buffer *userbuf,
+			 bool keep_buf,
+			 Relation stats_relation)
+{
+	HeapTupleData tuple;
+
+	*stuple = NULL;
+	if (heap_fetch(relation, tid, snapshot, &tuple, userbuf, keep_buf, stats_relation))
+	{
+		*stuple = heap_copytuple(&tuple);
+		return true;
+	}
+
+	return false;
+}
+
+/*
+ * Insert a heap tuple from a slot, which may contain an OID and speculative
+ * insertion token.
+ */
+static Oid
+heapam_heap_insert(Relation relation, TupleTableSlot *slot, CommandId cid,
+				   int options, BulkInsertState bistate, InsertIndexTuples IndexFunc,
+				   EState *estate, List *arbiterIndexes, List **recheckIndexes)
+{
+	Oid			oid;
+	HeapTuple	tuple = NULL;
+
+	if (slot->tts_storage)
+	{
+		HeapamTuple *htuple = slot->tts_storage;
+
+		tuple = htuple->hst_heaptuple;
+
+		if (relation->rd_rel->relhasoids)
+			HeapTupleSetOid(tuple, InvalidOid);
+	}
+	else
+	{
+		/*
+		 * Obtain the physical tuple to insert, building from the slot values.
+		 * XXX: maybe the slot already contains a physical tuple in the right
+		 * format?  In fact, if the slot isn't fully deformed, this is
+		 * completely bogus ...
+		 */
+		tuple = heap_form_tuple(slot->tts_tupleDescriptor,
+								slot->tts_values,
+								slot->tts_isnull);
+	}
+
+	/* Set the OID, if the slot has one */
+	if (slot->tts_tupleOid != InvalidOid)
+		HeapTupleHeaderSetOid(tuple->t_data, slot->tts_tupleOid);
+
+	/* Update the tuple with table oid */
+	if (slot->tts_tableOid != InvalidOid)
+		tuple->t_tableOid = slot->tts_tableOid;
+
+	/* Set the speculative insertion token, if the slot has one */
+	if ((options & HEAP_INSERT_SPECULATIVE) && slot->tts_speculativeToken)
+		HeapTupleHeaderSetSpeculativeToken(tuple->t_data, slot->tts_speculativeToken);
+
+	/* Perform the insertion, and copy the resulting ItemPointer */
+	oid = heap_insert(relation, tuple, cid, options, bistate);
+	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
+
+	if (slot->tts_storage == NULL)
+		ExecStoreTuple(tuple, slot, InvalidBuffer, true);
+
+	if ((estate != NULL) && (estate->es_result_relation_info->ri_NumIndices > 0))
+	{
+		Assert(IndexFunc != NULL);
+
+		if (options & HEAP_INSERT_SPECULATIVE)
+		{
+			bool		specConflict = false;
+
+			*recheckIndexes = (IndexFunc) (slot, estate, true,
+										   &specConflict,
+										   arbiterIndexes);
+
+			/* adjust the tuple's state accordingly */
+			if (!specConflict)
+				heap_finish_speculative(relation, slot);
+			else
+			{
+				heap_abort_speculative(relation, slot);
+				slot->tts_specConflict = true;
+			}
+		}
+		else
+		{
+			*recheckIndexes = (IndexFunc) (slot, estate, false,
+										   NULL, arbiterIndexes);
+		}
+	}
+
+	return oid;
+}
+
+static HTSU_Result
+heapam_heap_delete(Relation relation, ItemPointer tid, CommandId cid,
+				   Snapshot crosscheck, bool wait,
+				   HeapUpdateFailureData *hufd)
+{
+	return heap_delete(relation, tid, cid, crosscheck, wait, hufd);
+}
+
+
+
+static HTSU_Result
+heapam_heap_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
+				   EState *estate, CommandId cid, Snapshot crosscheck,
+				   bool wait, HeapUpdateFailureData *hufd, LockTupleMode *lockmode,
+				   InsertIndexTuples IndexFunc, List **recheckIndexes)
+{
+	HeapTuple	tuple;
+	HTSU_Result result;
+
+	if (slot->tts_storage)
+	{
+		HeapamTuple *htuple = slot->tts_storage;
+
+		tuple = htuple->hst_heaptuple;
+	}
+	else
+	{
+		tuple = heap_form_tuple(slot->tts_tupleDescriptor,
+								slot->tts_values,
+								slot->tts_isnull);
+	}
+
+	/* Set the OID, if the slot has one */
+	if (slot->tts_tupleOid != InvalidOid)
+		HeapTupleHeaderSetOid(tuple->t_data, slot->tts_tupleOid);
+
+	/* Update the tuple with table oid */
+	if (slot->tts_tableOid != InvalidOid)
+		tuple->t_tableOid = slot->tts_tableOid;
+
+	result = heap_update(relation, otid, tuple, cid, crosscheck, wait,
+						 hufd, lockmode);
+	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
+
+	if (slot->tts_storage == NULL)
+		ExecStoreTuple(tuple, slot, InvalidBuffer, true);
+
+	/*
+	 * Note: instead of having to update the old index tuples associated with
+	 * the heap tuple, all we do is form and insert new index tuples. This is
+	 * because UPDATEs are actually DELETEs and INSERTs, and index tuple
+	 * deletion is done later by VACUUM (see notes in ExecDelete). All we do
+	 * here is insert new index tuples.  -cim 9/27/89
+	 */
+
+	/*
+	 * insert index entries for tuple
+	 *
+	 * Note: heap_update returns the tid (location) of the new tuple in the
+	 * t_self field.
+	 *
+	 * If it's a HOT update, we mustn't insert new index entries.
+	 */
+	if ((result == HeapTupleMayBeUpdated) &&
+		((estate != NULL) && (estate->es_result_relation_info->ri_NumIndices > 0)) &&
+		(!HeapTupleIsHeapOnly(tuple)))
+		*recheckIndexes = (IndexFunc) (slot, estate, false, NULL, NIL);
+
+	return result;
+}
+
+static tuple_data
+heapam_get_tuple_data(StorageTuple tuple, tuple_data_flags flags)
+{
+	switch (flags)
+	{
+		case XMIN:
+			return (tuple_data) HeapTupleHeaderGetXmin(((HeapTuple) tuple)->t_data);
+			break;
+		case UPDATED_XID:
+			return (tuple_data) HeapTupleHeaderGetUpdateXid(((HeapTuple) tuple)->t_data);
+			break;
+		case CMIN:
+			return (tuple_data) HeapTupleHeaderGetCmin(((HeapTuple) tuple)->t_data);
+			break;
+		case TID:
+			return (tuple_data) ((HeapTuple) tuple)->t_self;
+			break;
+		case CTID:
+			return (tuple_data) ((HeapTuple) tuple)->t_data->t_ctid;
+			break;
+		default:
+			Assert(0);
+			break;
+	}
+}
+
+static StorageTuple
+heapam_form_tuple_by_datum(Datum data, Oid tableoid)
+{
+	return heap_form_tuple_by_datum(data, tableoid);
+}
+
 Datum
 heapam_storage_handler(PG_FUNCTION_ARGS)
 {
@@ -41,5 +255,18 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 
 	amroutine->slot_storageam = heapam_storage_slot_handler;
 
+	amroutine->tuple_fetch = heapam_fetch;
+	amroutine->tuple_insert = heapam_heap_insert;
+	amroutine->tuple_delete = heapam_heap_delete;
+	amroutine->tuple_update = heapam_heap_update;
+	amroutine->tuple_lock = heap_lock_tuple;
+	amroutine->multi_insert = heap_multi_insert;
+
+	amroutine->get_tuple_data = heapam_get_tuple_data;
+	amroutine->tuple_from_datum = heapam_form_tuple_by_datum;
+	amroutine->tuple_get_latest_tid = heap_get_latest_tid;
+	amroutine->speculative_abort = heap_abort_speculative;
+	amroutine->relation_sync = heap_sync;
+
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index f93c194e18..9afca702da 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -110,6 +110,7 @@
 #include "access/heapam.h"
 #include "access/heapam_xlog.h"
 #include "access/rewriteheap.h"
+#include "access/storageam.h"
 #include "access/transam.h"
 #include "access/tuptoaster.h"
 #include "access/xact.h"
@@ -126,13 +127,13 @@
 
 #include "storage/bufmgr.h"
 #include "storage/fd.h"
+#include "storage/procarray.h"
 #include "storage/smgr.h"
 
 #include "utils/memutils.h"
 #include "utils/rel.h"
 #include "utils/tqual.h"
 
-#include "storage/procarray.h"
 
 /*
  * State associated with a rewrite operation. This is opaque to the user
@@ -357,7 +358,7 @@ end_heap_rewrite(RewriteState state)
 	 * wrote before the checkpoint.
 	 */
 	if (RelationNeedsWAL(state->rs_new_rel))
-		heap_sync(state->rs_new_rel);
+		storage_sync(state->rs_new_rel);
 
 	logical_end_heap_rewrite(state);
 
diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c
index c74945a52a..1f67ab2af8 100644
--- a/src/backend/access/heap/tuptoaster.c
+++ b/src/backend/access/heap/tuptoaster.c
@@ -32,6 +32,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/storageam.h"
 #include "access/tuptoaster.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
@@ -1777,7 +1778,13 @@ toast_delete_datum(Relation rel, Datum value, bool is_speculative)
 		 * Have a chunk, delete it
 		 */
 		if (is_speculative)
-			heap_abort_speculative(toastrel, toasttup);
+		{
+			TupleTableSlot *slot = MakeSingleTupleTableSlot(RelationGetDescr(toastrel));
+
+			ExecStoreTuple(toasttup, slot, InvalidBuffer, false);
+			storage_abort_speculative(toastrel, slot);
+			ExecDropSingleTupleTableSlot(slot);
+		}
 		else
 			simple_heap_delete(toastrel, &toasttup->t_self);
 	}
diff --git a/src/backend/access/storage/storage_common.c b/src/backend/access/storage/storage_common.c
index b65153bb38..30976f6286 100644
--- a/src/backend/access/storage/storage_common.c
+++ b/src/backend/access/storage/storage_common.c
@@ -29,20 +29,6 @@
 SnapshotData SnapshotSelfData = {SELF_VISIBILITY};
 SnapshotData SnapshotAnyData = {ANY_VISIBILITY};
 
-/*
- * HeapTupleSetHintBits --- exported version of SetHintBits()
- *
- * This must be separate because of C99's brain-dead notions about how to
- * implement inline functions.
- */
-void
-HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
-					 uint16 infomask, TransactionId xid)
-{
-	SetHintBits(tuple, buffer, infomask, xid);
-}
-
-
 /*
  * Is the tuple really only locked?  That is, is it not updated?
  *
@@ -352,6 +338,8 @@ heapam_slot_store_tuple(TupleTableSlot *slot, StorageTuple tuple, bool shouldFre
 	MemoryContextSwitchTo(oldcontext);
 
 	slot->tts_tid = ((HeapTuple) tuple)->t_self;
+	if (slot->tts_tupleDescriptor->tdhasoid)
+		slot->tts_tupleOid = HeapTupleGetOid((HeapTuple) tuple);
 	slot->tts_storage = stuple;
 }
 
diff --git a/src/backend/access/storage/storageam.c b/src/backend/access/storage/storageam.c
index 8541c75782..487ffc9746 100644
--- a/src/backend/access/storage/storageam.c
+++ b/src/backend/access/storage/storageam.c
@@ -13,3 +13,129 @@
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
+
+#include "access/storageam.h"
+#include "access/storageamapi.h"
+#include "utils/rel.h"
+
+/*
+ *	storage_fetch		- retrieve tuple with given tid
+ */
+bool
+storage_fetch(Relation relation,
+			  ItemPointer tid,
+			  Snapshot snapshot,
+			  StorageTuple * stuple,
+			  Buffer *userbuf,
+			  bool keep_buf,
+			  Relation stats_relation)
+{
+	return relation->rd_stamroutine->tuple_fetch(relation, tid, snapshot, stuple,
+												 userbuf, keep_buf, stats_relation);
+}
+
+
+/*
+ *	storage_lock_tuple - lock a tuple in shared or exclusive mode
+ */
+HTSU_Result
+storage_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * stuple,
+				   CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
+				   bool follow_updates, Buffer *buffer, HeapUpdateFailureData *hufd)
+{
+	return relation->rd_stamroutine->tuple_lock(relation, tid, stuple,
+												cid, mode, wait_policy,
+												follow_updates, buffer, hufd);
+}
+
+/*
+ * Insert a tuple from a slot into storage AM routine
+ */
+Oid
+storage_insert(Relation relation, TupleTableSlot *slot, CommandId cid,
+			   int options, BulkInsertState bistate, InsertIndexTuples IndexFunc,
+			   EState *estate, List *arbiterIndexes, List **recheckIndexes)
+{
+	return relation->rd_stamroutine->tuple_insert(relation, slot, cid, options,
+												  bistate, IndexFunc, estate,
+												  arbiterIndexes, recheckIndexes);
+}
+
+/*
+ * Delete a tuple from tid using storage AM routine
+ */
+HTSU_Result
+storage_delete(Relation relation, ItemPointer tid, CommandId cid,
+			   Snapshot crosscheck, bool wait,
+			   HeapUpdateFailureData *hufd)
+{
+	return relation->rd_stamroutine->tuple_delete(relation, tid, cid,
+												  crosscheck, wait, hufd);
+}
+
+/*
+ * update a tuple from tid using storage AM routine
+ */
+HTSU_Result
+storage_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
+			   EState *estate, CommandId cid, Snapshot crosscheck, bool wait,
+			   HeapUpdateFailureData *hufd, LockTupleMode *lockmode,
+			   InsertIndexTuples IndexFunc, List **recheckIndexes)
+{
+	return relation->rd_stamroutine->tuple_update(relation, otid, slot, estate,
+												  cid, crosscheck, wait, hufd,
+												  lockmode, IndexFunc, recheckIndexes);
+}
+
+
+/*
+ *	storage_multi_insert	- insert multiple tuple into a storage
+ */
+void
+storage_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
+					 CommandId cid, int options, BulkInsertState bistate)
+{
+	relation->rd_stamroutine->multi_insert(relation, tuples, ntuples,
+										   cid, options, bistate);
+}
+
+/*
+ *	storage_abort_speculative - kill a speculatively inserted tuple
+ */
+void
+storage_abort_speculative(Relation relation, TupleTableSlot *slot)
+{
+	relation->rd_stamroutine->speculative_abort(relation, slot);
+}
+
+tuple_data
+storage_tuple_get_data(Relation relation, StorageTuple tuple, tuple_data_flags flags)
+{
+	return relation->rd_stamroutine->get_tuple_data(tuple, flags);
+}
+
+StorageTuple
+storage_tuple_by_datum(Relation relation, Datum data, Oid tableoid)
+{
+	if (relation)
+		return relation->rd_stamroutine->tuple_from_datum(data, tableoid);
+	else
+		return heap_form_tuple_by_datum(data, tableoid);
+}
+
+void
+storage_get_latest_tid(Relation relation,
+					   Snapshot snapshot,
+					   ItemPointer tid)
+{
+	relation->rd_stamroutine->tuple_get_latest_tid(relation, snapshot, tid);
+}
+
+/*
+ *	storage_sync		- sync a heap, for use when no WAL has been written
+ */
+void
+storage_sync(Relation rel)
+{
+	rel->rd_stamroutine->relation_sync(rel);
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 0a0eecf509..62d1ac98d5 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -20,6 +20,7 @@
 
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "access/xlog.h"
@@ -2719,8 +2720,6 @@ CopyFrom(CopyState cstate)
 
 			if (slot == NULL)	/* "do nothing" */
 				skip_tuple = true;
-			else				/* trigger might have changed tuple */
-				tuple = ExecHeapifySlot(slot);
 		}
 
 		if (!skip_tuple)
@@ -2783,19 +2782,11 @@ CopyFrom(CopyState cstate)
 					List	   *recheckIndexes = NIL;
 
 					/* OK, store the tuple and create index entries for it */
-					heap_insert(resultRelInfo->ri_RelationDesc, tuple, mycid,
-								hi_options, bistate);
-
-					if (resultRelInfo->ri_NumIndices > 0)
-						recheckIndexes = ExecInsertIndexTuples(slot,
-															   &(tuple->t_self),
-															   estate,
-															   false,
-															   NULL,
-															   NIL);
+					storage_insert(resultRelInfo->ri_RelationDesc, slot, mycid, hi_options,
+								   bistate, ExecInsertIndexTuples, estate, NIL, &recheckIndexes);
 
 					/* AFTER ROW INSERT Triggers */
-					ExecARInsertTriggers(estate, resultRelInfo, tuple,
+					ExecARInsertTriggers(estate, resultRelInfo, slot,
 										 recheckIndexes, cstate->transition_capture);
 
 					list_free(recheckIndexes);
@@ -2891,7 +2882,7 @@ CopyFrom(CopyState cstate)
 	 * indexes since those use WAL anyway)
 	 */
 	if (hi_options & HEAP_INSERT_SKIP_WAL)
-		heap_sync(cstate->rel);
+		storage_sync(cstate->rel);
 
 	return processed;
 }
@@ -2924,12 +2915,12 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
 	 * before calling it.
 	 */
 	oldcontext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
-	heap_multi_insert(cstate->rel,
-					  bufferedTuples,
-					  nBufferedTuples,
-					  mycid,
-					  hi_options,
-					  bistate);
+	storage_multi_insert(cstate->rel,
+						 bufferedTuples,
+						 nBufferedTuples,
+						 mycid,
+						 hi_options,
+						 bistate);
 	MemoryContextSwitchTo(oldcontext);
 
 	/*
@@ -2945,10 +2936,9 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
 			cstate->cur_lineno = firstBufferedLineNo + i;
 			ExecStoreTuple(bufferedTuples[i], myslot, InvalidBuffer, false);
 			recheckIndexes =
-				ExecInsertIndexTuples(myslot, &(bufferedTuples[i]->t_self),
-									  estate, false, NULL, NIL);
+				ExecInsertIndexTuples(myslot, estate, false, NULL, NIL);
 			ExecARInsertTriggers(estate, resultRelInfo,
-								 bufferedTuples[i],
+								 myslot,
 								 recheckIndexes, cstate->transition_capture);
 			list_free(recheckIndexes);
 		}
@@ -2965,8 +2955,9 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
 		for (i = 0; i < nBufferedTuples; i++)
 		{
 			cstate->cur_lineno = firstBufferedLineNo + i;
+			ExecStoreTuple(bufferedTuples[i], myslot, InvalidBuffer, false);
 			ExecARInsertTriggers(estate, resultRelInfo,
-								 bufferedTuples[i],
+								 myslot,
 								 NIL, cstate->transition_capture);
 		}
 	}
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 213a8cccbc..9e6fb8740b 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -26,6 +26,7 @@
 
 #include "access/reloptions.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "access/xlog.h"
@@ -582,25 +583,28 @@ static bool
 intorel_receive(TupleTableSlot *slot, DestReceiver *self)
 {
 	DR_intorel *myState = (DR_intorel *) self;
-	HeapTuple	tuple;
 
 	/*
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
 	 */
-	tuple = ExecHeapifySlot(slot);
+	ExecMaterializeSlot(slot);
 
 	/*
 	 * force assignment of new OID (see comments in ExecInsert)
 	 */
 	if (myState->rel->rd_rel->relhasoids)
-		HeapTupleSetOid(tuple, InvalidOid);
-
-	heap_insert(myState->rel,
-				tuple,
-				myState->output_cid,
-				myState->hi_options,
-				myState->bistate);
+		slot->tts_tupleOid = InvalidOid;
+
+	storage_insert(myState->rel,
+				   slot,
+				   myState->output_cid,
+				   myState->hi_options,
+				   myState->bistate,
+				   NULL,
+				   NULL,
+				   NIL,
+				   NULL);
 
 	/* We know this is a newly created relation, so there are no indexes */
 
@@ -619,7 +623,7 @@ intorel_shutdown(DestReceiver *self)
 
 	/* If we skipped using WAL, must heap_sync before commit */
 	if (myState->hi_options & HEAP_INSERT_SKIP_WAL)
-		heap_sync(myState->rel);
+		storage_sync(myState->rel);
 
 	/* close rel, but keep lock until commit */
 	heap_close(myState->rel, NoLock);
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index b440740e28..936ea9b9e5 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -16,6 +16,7 @@
 
 #include "access/htup_details.h"
 #include "access/multixact.h"
+#include "access/storageam.h"
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/catalog.h"
@@ -491,19 +492,22 @@ static bool
 transientrel_receive(TupleTableSlot *slot, DestReceiver *self)
 {
 	DR_transientrel *myState = (DR_transientrel *) self;
-	HeapTuple	tuple;
 
 	/*
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
 	 */
-	tuple = ExecHeapifySlot(slot);
-
-	heap_insert(myState->transientrel,
-				tuple,
-				myState->output_cid,
-				myState->hi_options,
-				myState->bistate);
+	ExecMaterializeSlot(slot);
+
+	storage_insert(myState->transientrel,
+				   slot,
+				   myState->output_cid,
+				   myState->hi_options,
+				   myState->bistate,
+				   NULL,
+				   NULL,
+				   NIL,
+				   NULL);
 
 	/* We know this is a newly created relation, so there are no indexes */
 
@@ -522,7 +526,7 @@ transientrel_shutdown(DestReceiver *self)
 
 	/* If we skipped using WAL, must heap_sync before commit */
 	if (myState->hi_options & HEAP_INSERT_SKIP_WAL)
-		heap_sync(myState->transientrel);
+		storage_sync(myState->transientrel);
 
 	/* close transientrel, but keep lock until commit */
 	heap_close(myState->transientrel, NoLock);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d979ce266d..73b68826f2 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -19,6 +19,7 @@
 #include "access/multixact.h"
 #include "access/reloptions.h"
 #include "access/relscan.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/tupconvert.h"
 #include "access/xact.h"
@@ -4663,7 +4664,8 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 
 			/* Write the tuple out to the new relation */
 			if (newrel)
-				heap_insert(newrel, tuple, mycid, hi_options, bistate);
+				storage_insert(newrel, newslot, mycid, hi_options, bistate,
+							   NULL, NULL, NIL, NULL);
 
 			ResetExprContext(econtext);
 
@@ -4687,7 +4689,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 
 		/* If we skipped writing WAL, then we need to sync the heap. */
 		if (hi_options & HEAP_INSERT_SKIP_WAL)
-			heap_sync(newrel);
+			storage_sync(newrel);
 
 		heap_close(newrel, NoLock);
 	}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 96c4fe7d43..0a58ea16b6 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -15,6 +15,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/htup_details.h"
 #include "access/xact.h"
@@ -2352,17 +2353,21 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 
 void
 ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
-					 HeapTuple trigtuple, List *recheckIndexes,
+					 TupleTableSlot *slot, List *recheckIndexes,
 					 TransitionCaptureState *transition_capture)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 
 	if ((trigdesc && trigdesc->trig_insert_after_row) ||
 		(transition_capture && transition_capture->tcs_insert_new_table))
+	{
+		HeapTuple	trigtuple = ExecHeapifySlot(slot);
+
 		AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT,
 							  true, NULL, trigtuple,
 							  recheckIndexes, NULL,
 							  transition_capture);
+	}
 }
 
 TupleTableSlot *
@@ -3012,9 +3017,10 @@ GetTupleForTrigger(EState *estate,
 				   TupleTableSlot **newSlot)
 {
 	Relation	relation = relinfo->ri_RelationDesc;
-	HeapTupleData tuple;
+	StorageTuple tuple;
 	HeapTuple	result;
 	Buffer		buffer;
+	tuple_data	t_data;
 
 	if (newSlot != NULL)
 	{
@@ -3030,11 +3036,11 @@ GetTupleForTrigger(EState *estate,
 		 * lock tuple for update
 		 */
 ltrmark:;
-		tuple.t_self = *tid;
-		test = heap_lock_tuple(relation, &tuple,
-							   estate->es_output_cid,
-							   lockmode, LockWaitBlock,
-							   false, &buffer, &hufd);
+		test = storage_lock_tuple(relation, tid, &tuple,
+								  estate->es_output_cid,
+								  lockmode, LockWaitBlock,
+								  false, &buffer, &hufd);
+		result = tuple;
 		switch (test)
 		{
 			case HeapTupleSelfUpdated:
@@ -3066,7 +3072,8 @@ ltrmark:;
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("could not serialize access due to concurrent update")));
-				if (!ItemPointerEquals(&hufd.ctid, &tuple.t_self))
+				t_data = relation->rd_stamroutine->get_tuple_data(tuple, TID);
+				if (!ItemPointerEquals(&hufd.ctid, &(t_data.tid)))
 				{
 					/* it was updated, so look at the updated version */
 					TupleTableSlot *epqslot;
@@ -3112,6 +3119,7 @@ ltrmark:;
 	{
 		Page		page;
 		ItemId		lp;
+		HeapTupleData tupledata;
 
 		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
 
@@ -3130,17 +3138,17 @@ ltrmark:;
 
 		Assert(ItemIdIsNormal(lp));
 
-		tuple.t_data = (HeapTupleHeader) PageGetItem(page, lp);
-		tuple.t_len = ItemIdGetLength(lp);
-		tuple.t_self = *tid;
-		tuple.t_tableOid = RelationGetRelid(relation);
+		tupledata.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+		tupledata.t_len = ItemIdGetLength(lp);
+		tupledata.t_self = *tid;
+		tupledata.t_tableOid = RelationGetRelid(relation);
 
 		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+
+		result = heap_copytuple(&tupledata);
 	}
 
-	result = heap_copytuple(&tuple);
 	ReleaseBuffer(buffer);
-
 	return result;
 }
 
@@ -3946,8 +3954,8 @@ AfterTriggerExecute(AfterTriggerEvent event,
 	AfterTriggerShared evtshared = GetTriggerSharedData(event);
 	Oid			tgoid = evtshared->ats_tgoid;
 	TriggerData LocTriggerData;
-	HeapTupleData tuple1;
-	HeapTupleData tuple2;
+	StorageTuple tuple1;
+	StorageTuple tuple2;
 	HeapTuple	rettuple;
 	Buffer		buffer1 = InvalidBuffer;
 	Buffer		buffer2 = InvalidBuffer;
@@ -4020,10 +4028,9 @@ AfterTriggerExecute(AfterTriggerEvent event,
 		default:
 			if (ItemPointerIsValid(&(event->ate_ctid1)))
 			{
-				ItemPointerCopy(&(event->ate_ctid1), &(tuple1.t_self));
-				if (!heap_fetch(rel, SnapshotAny, &tuple1, &buffer1, false, NULL))
+				if (!storage_fetch(rel, &(event->ate_ctid1), SnapshotAny, &tuple1, &buffer1, false, NULL))
 					elog(ERROR, "failed to fetch tuple1 for AFTER trigger");
-				LocTriggerData.tg_trigtuple = &tuple1;
+				LocTriggerData.tg_trigtuple = tuple1;
 				LocTriggerData.tg_trigtuplebuf = buffer1;
 			}
 			else
@@ -4037,10 +4044,9 @@ AfterTriggerExecute(AfterTriggerEvent event,
 				AFTER_TRIGGER_2CTID &&
 				ItemPointerIsValid(&(event->ate_ctid2)))
 			{
-				ItemPointerCopy(&(event->ate_ctid2), &(tuple2.t_self));
-				if (!heap_fetch(rel, SnapshotAny, &tuple2, &buffer2, false, NULL))
+				if (!storage_fetch(rel, &(event->ate_ctid2), SnapshotAny, &tuple2, &buffer2, false, NULL))
 					elog(ERROR, "failed to fetch tuple2 for AFTER trigger");
-				LocTriggerData.tg_newtuple = &tuple2;
+				LocTriggerData.tg_newtuple = tuple2;
 				LocTriggerData.tg_newtuplebuf = buffer2;
 			}
 			else
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 89e189fa71..ab533cf9c7 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -269,12 +269,12 @@ ExecCloseIndices(ResultRelInfo *resultRelInfo)
  */
 List *
 ExecInsertIndexTuples(TupleTableSlot *slot,
-					  ItemPointer tupleid,
 					  EState *estate,
 					  bool noDupErr,
 					  bool *specConflict,
 					  List *arbiterIndexes)
 {
+	ItemPointer tupleid = &slot->tts_tid;
 	List	   *result = NIL;
 	ResultRelInfo *resultRelInfo;
 	int			i;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index dbaa47f2d3..c3c28d0fac 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -38,6 +38,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -1894,7 +1895,7 @@ ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
 		/* See the comment above. */
 		if (resultRelInfo->ri_PartitionRoot)
 		{
-			HeapTuple	tuple = ExecFetchSlotTuple(slot);
+			StorageTuple tuple = ExecFetchSlotTuple(slot);
 			TupleDesc	old_tupdesc = RelationGetDescr(rel);
 			TupleConversionMap *map;
 
@@ -1974,7 +1975,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				 */
 				if (resultRelInfo->ri_PartitionRoot)
 				{
-					HeapTuple	tuple = ExecFetchSlotTuple(slot);
+					StorageTuple tuple = ExecFetchSlotTuple(slot);
 					TupleConversionMap *map;
 
 					rel = resultRelInfo->ri_PartitionRoot;
@@ -2021,7 +2022,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 			/* See the comment above. */
 			if (resultRelInfo->ri_PartitionRoot)
 			{
-				HeapTuple	tuple = ExecFetchSlotTuple(slot);
+				StorageTuple tuple = ExecFetchSlotTuple(slot);
 				TupleDesc	old_tupdesc = RelationGetDescr(rel);
 				TupleConversionMap *map;
 
@@ -2480,7 +2481,8 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
 			 ItemPointer tid, TransactionId priorXmax)
 {
 	TupleTableSlot *slot;
-	HeapTuple	copyTuple;
+	StorageTuple copyTuple;
+	tuple_data	t_data;
 
 	Assert(rti > 0);
 
@@ -2497,7 +2499,9 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
 	 * For UPDATE/DELETE we have to return tid of actual row we're executing
 	 * PQ for.
 	 */
-	*tid = copyTuple->t_self;
+
+	t_data = storage_tuple_get_data(relation, copyTuple, TID);
+	*tid = t_data.tid;
 
 	/*
 	 * Need to run a recheck subquery.  Initialize or reinitialize EPQ state.
@@ -2528,7 +2532,7 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
 	 * is to guard against early re-use of the EPQ query.
 	 */
 	if (!TupIsNull(slot))
-		(void) ExecMaterializeSlot(slot);
+		ExecMaterializeSlot(slot);
 
 	/*
 	 * Clear out the test tuple.  This is needed in case the EPQ query is
@@ -2561,14 +2565,14 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
  * Note: properly, lockmode should be declared as enum LockTupleMode,
  * but we use "int" to avoid having to include heapam.h in executor.h.
  */
-HeapTuple
+StorageTuple
 EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 				  LockWaitPolicy wait_policy,
 				  ItemPointer tid, TransactionId priorXmax)
 {
-	HeapTuple	copyTuple = NULL;
-	HeapTupleData tuple;
+	StorageTuple tuple = NULL;
 	SnapshotData SnapshotDirty;
+	tuple_data	t_data;
 
 	/*
 	 * fetch target tuple
@@ -2576,12 +2580,12 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 	 * Loop here to deal with updated or busy tuples
 	 */
 	InitDirtySnapshot(SnapshotDirty);
-	tuple.t_self = *tid;
 	for (;;)
 	{
 		Buffer		buffer;
+		ItemPointerData ctid;
 
-		if (heap_fetch(relation, &SnapshotDirty, &tuple, &buffer, true, NULL))
+		if (storage_fetch(relation, tid, &SnapshotDirty, &tuple, &buffer, true, NULL))
 		{
 			HTSU_Result test;
 			HeapUpdateFailureData hufd;
@@ -2595,7 +2599,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 			 * 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),
+			if (!TransactionIdEquals(HeapTupleHeaderGetXmin(((HeapTuple) tuple)->t_data),
 									 priorXmax))
 			{
 				ReleaseBuffer(buffer);
@@ -2617,7 +2621,8 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 				{
 					case LockWaitBlock:
 						XactLockTableWait(SnapshotDirty.xmax,
-										  relation, &tuple.t_self,
+										  relation,
+										  tid,
 										  XLTW_FetchUpdated);
 						break;
 					case LockWaitSkip:
@@ -2646,20 +2651,23 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 			 * that priorXmax == xmin, so we can test that variable instead of
 			 * doing HeapTupleHeaderGetXmin again.
 			 */
-			if (TransactionIdIsCurrentTransactionId(priorXmax) &&
-				HeapTupleHeaderGetCmin(tuple.t_data) >= estate->es_output_cid)
+			if (TransactionIdIsCurrentTransactionId(priorXmax))
 			{
-				ReleaseBuffer(buffer);
-				return NULL;
+				t_data = storage_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 = heap_lock_tuple(relation, &tuple,
-								   estate->es_output_cid,
-								   lockmode, wait_policy,
-								   false, &buffer, &hufd);
+			test = storage_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);
 
@@ -2695,12 +2703,15 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 								(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 								 errmsg("could not serialize access due to concurrent update")));
 
+#if 0 //hari
 					/* Should not encounter speculative tuple on recheck */
 					Assert(!HeapTupleHeaderIsSpeculative(tuple.t_data));
-					if (!ItemPointerEquals(&hufd.ctid, &tuple.t_self))
+#endif
+					t_data = storage_tuple_get_data(relation, tuple, TID);
+					if (!ItemPointerEquals(&hufd.ctid, &t_data.tid))
 					{
 						/* it was updated, so look at the updated version */
-						tuple.t_self = hufd.ctid;
+						*tid = hufd.ctid;
 						/* updated row should have xmin matching this xmax */
 						priorXmax = hufd.xmax;
 						continue;
@@ -2722,10 +2733,6 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 					return NULL;	/* keep compiler quiet */
 			}
 
-			/*
-			 * We got tuple - now copy it for use by recheck query.
-			 */
-			copyTuple = heap_copytuple(&tuple);
 			ReleaseBuffer(buffer);
 			break;
 		}
@@ -2734,7 +2741,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 		 * 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)
+		if (tuple == NULL)
 		{
 			ReleaseBuffer(buffer);
 			return NULL;
@@ -2743,7 +2750,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 		/*
 		 * As above, if xmin isn't what we're expecting, do nothing.
 		 */
-		if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple.t_data),
+		if (!TransactionIdEquals(HeapTupleHeaderGetXmin(((HeapTuple) tuple)->t_data),
 								 priorXmax))
 		{
 			ReleaseBuffer(buffer);
@@ -2762,7 +2769,9 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 		 * 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))
+		t_data = storage_tuple_get_data(relation, tuple, CTID);
+		ctid = t_data.tid;
+		if (ItemPointerEquals(tid, &ctid))
 		{
 			/* deleted, so forget about it */
 			ReleaseBuffer(buffer);
@@ -2770,17 +2779,19 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 		}
 
 		/* updated, so look at the updated row */
-		tuple.t_self = tuple.t_data->t_ctid;
+		*tid = ctid;
+
 		/* updated row should have xmin matching this xmax */
-		priorXmax = HeapTupleHeaderGetUpdateXid(tuple.t_data);
+		t_data = storage_tuple_get_data(relation, tuple, UPDATED_XID);
+		priorXmax = t_data.xid;
 		ReleaseBuffer(buffer);
 		/* loop back to fetch next in chain */
 	}
 
 	/*
-	 * Return the copied tuple
+	 * Return the tuple
 	 */
-	return copyTuple;
+	return tuple;
 }
 
 /*
@@ -2826,7 +2837,7 @@ EvalPlanQualSetPlan(EPQState *epqstate, Plan *subplan, List *auxrowmarks)
  * NB: passed tuple must be palloc'd; it may get freed later
  */
 void
-EvalPlanQualSetTuple(EPQState *epqstate, Index rti, HeapTuple tuple)
+EvalPlanQualSetTuple(EPQState *epqstate, Index rti, StorageTuple tuple)
 {
 	EState	   *estate = epqstate->estate;
 
@@ -2845,7 +2856,7 @@ EvalPlanQualSetTuple(EPQState *epqstate, Index rti, HeapTuple tuple)
 /*
  * Fetch back the current test tuple (if any) for the specified RTI
  */
-HeapTuple
+StorageTuple
 EvalPlanQualGetTuple(EPQState *epqstate, Index rti)
 {
 	EState	   *estate = epqstate->estate;
@@ -2873,7 +2884,7 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 		ExecRowMark *erm = aerm->rowmark;
 		Datum		datum;
 		bool		isNull;
-		HeapTupleData tuple;
+		StorageTuple tuple;
 
 		if (RowMarkRequiresRowShareLock(erm->markType))
 			elog(ERROR, "EvalPlanQual doesn't support locking rowmarks");
@@ -2904,8 +2915,6 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 
 		if (erm->markType == ROW_MARK_REFERENCE)
 		{
-			HeapTuple	copyTuple;
-
 			Assert(erm->relation != NULL);
 
 			/* fetch the tuple's ctid */
@@ -2929,11 +2938,11 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 							 errmsg("cannot lock rows in foreign table \"%s\"",
 									RelationGetRelationName(erm->relation))));
-				copyTuple = fdwroutine->RefetchForeignRow(epqstate->estate,
-														  erm,
-														  datum,
-														  &updated);
-				if (copyTuple == NULL)
+				tuple = fdwroutine->RefetchForeignRow(epqstate->estate,
+													  erm,
+													  datum,
+													  &updated);
+				if (tuple == NULL)
 					elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck");
 
 				/*
@@ -2947,23 +2956,18 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 				/* ordinary table, fetch the tuple */
 				Buffer		buffer;
 
-				tuple.t_self = *((ItemPointer) DatumGetPointer(datum));
-				if (!heap_fetch(erm->relation, SnapshotAny, &tuple, &buffer,
-								false, NULL))
+				if (!storage_fetch(erm->relation, (ItemPointer) DatumGetPointer(datum), SnapshotAny, &tuple, &buffer,
+								   false, NULL))
 					elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck");
 
-				/* successful, copy tuple */
-				copyTuple = heap_copytuple(&tuple);
 				ReleaseBuffer(buffer);
 			}
 
 			/* store tuple */
-			EvalPlanQualSetTuple(epqstate, erm->rti, copyTuple);
+			EvalPlanQualSetTuple(epqstate, erm->rti, tuple);
 		}
 		else
 		{
-			HeapTupleHeader td;
-
 			Assert(erm->markType == ROW_MARK_COPY);
 
 			/* fetch the whole-row Var for the relation */
@@ -2973,19 +2977,12 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 			/* non-locked rels could be on the inside of outer joins */
 			if (isNull)
 				continue;
-			td = DatumGetHeapTupleHeader(datum);
-
-			/* build a temporary HeapTuple control structure */
-			tuple.t_len = HeapTupleHeaderGetDatumLength(td);
-			tuple.t_data = td;
-			/* relation might be a foreign table, if so provide tableoid */
-			tuple.t_tableOid = erm->relid;
-			/* also copy t_ctid in case there's valid data there */
-			tuple.t_self = td->t_ctid;
-
-			/* copy and store tuple */
-			EvalPlanQualSetTuple(epqstate, erm->rti,
-								 heap_copytuple(&tuple));
+
+			tuple = storage_tuple_by_datum(erm->relation, datum, erm->relid);
+
+			/* store tuple */
+			EvalPlanQualSetTuple(epqstate, erm->rti, tuple);
+
 		}
 	}
 }
@@ -3154,8 +3151,8 @@ EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
 	}
 	else
 	{
-		estate->es_epqTuple = (HeapTuple *)
-			palloc0(rtsize * sizeof(HeapTuple));
+		estate->es_epqTuple = (StorageTuple *)
+			palloc0(rtsize * sizeof(StorageTuple));
 		estate->es_epqTupleSet = (bool *)
 			palloc0(rtsize * sizeof(bool));
 	}
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 12c15fd6bc..fcb58a0421 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -15,6 +15,7 @@
 #include "postgres.h"
 
 #include "access/relscan.h"
+#include "access/storageam.h"
 #include "access/transam.h"
 #include "access/xact.h"
 #include "commands/trigger.h"
@@ -169,19 +170,19 @@ retry:
 		Buffer		buf;
 		HeapUpdateFailureData hufd;
 		HTSU_Result res;
-		HeapTupleData locktup;
-
-		ItemPointerCopy(&outslot->tts_tid, &locktup.t_self);
+		StorageTuple locktup;
 
 		PushActiveSnapshot(GetLatestSnapshot());
 
-		res = heap_lock_tuple(rel, &locktup, GetCurrentCommandId(false),
-							  lockmode,
-							  LockWaitBlock,
-							  false /* don't follow updates */ ,
-							  &buf, &hufd);
+		res = storage_lock_tuple(rel, &(outslot->tts_tid), &locktup, GetCurrentCommandId(false),
+								 lockmode,
+								 LockWaitBlock,
+								 false /* don't follow updates */ ,
+								 &buf, &hufd);
 		/* the tuple slot already has the buffer pinned */
-		ReleaseBuffer(buf);
+		if (BufferIsValid(buf))
+			ReleaseBuffer(buf);
+		pfree(locktup);
 
 		PopActiveSnapshot();
 
@@ -277,19 +278,20 @@ retry:
 		Buffer		buf;
 		HeapUpdateFailureData hufd;
 		HTSU_Result res;
-		HeapTupleData locktup;
-
-		ItemPointerCopy(&outslot->tts_tid, &locktup.t_self);
+		StorageTuple locktup;
 
 		PushActiveSnapshot(GetLatestSnapshot());
 
-		res = heap_lock_tuple(rel, &locktup, GetCurrentCommandId(false),
-							  lockmode,
-							  LockWaitBlock,
-							  false /* don't follow updates */ ,
-							  &buf, &hufd);
+		res = storage_lock_tuple(rel, &(outslot->tts_tid), &locktup, GetCurrentCommandId(false),
+								 lockmode,
+								 LockWaitBlock,
+								 false /* don't follow updates */ ,
+								 &buf, &hufd);
 		/* the tuple slot already has the buffer pinned */
-		ReleaseBuffer(buf);
+		if (BufferIsValid(buf))
+			ReleaseBuffer(buf);
+
+		pfree(locktup);
 
 		PopActiveSnapshot();
 
@@ -327,7 +329,6 @@ void
 ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 {
 	bool		skip_tuple = false;
-	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 
@@ -354,19 +355,12 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 		if (rel->rd_att->constr)
 			ExecConstraints(resultRelInfo, slot, estate);
 
-		/* Store the slot into tuple that we can inspect. */
-		tuple = ExecHeapifySlot(slot);
-
-		/* OK, store the tuple and create index entries for it */
-		simple_heap_insert(rel, tuple);
-
-		if (resultRelInfo->ri_NumIndices > 0)
-			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
-												   estate, false, NULL,
-												   NIL);
+		storage_insert(resultRelInfo->ri_RelationDesc, slot,
+					   GetCurrentCommandId(true), 0, NULL,
+					   ExecInsertIndexTuples, estate, NIL, &recheckIndexes);
 
 		/* AFTER ROW INSERT Triggers */
-		ExecARInsertTriggers(estate, resultRelInfo, tuple,
+		ExecARInsertTriggers(estate, resultRelInfo, slot,
 							 recheckIndexes, NULL);
 
 		/*
@@ -390,7 +384,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 						 TupleTableSlot *searchslot, TupleTableSlot *slot)
 {
 	bool		skip_tuple = false;
-	HeapTuple	tuple;
+	StorageTuple tuple;
 	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	ItemPointer tid = &(searchslot->tts_tid);
@@ -415,22 +409,18 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 	if (!skip_tuple)
 	{
 		List	   *recheckIndexes = NIL;
+		HeapUpdateFailureData hufd;
+		LockTupleMode lockmode;
+		InsertIndexTuples IndexFunc = ExecInsertIndexTuples;
 
 		/* Check the constraints of the tuple */
 		if (rel->rd_att->constr)
 			ExecConstraints(resultRelInfo, slot, estate);
 
-		/* Store the slot into tuple that we can write. */
-		tuple = ExecHeapifySlot(slot);
+		storage_update(rel, tid, slot, estate, GetCurrentCommandId(true), InvalidSnapshot,
+					   true, &hufd, &lockmode, IndexFunc, &recheckIndexes);
 
-		/* OK, update the tuple and index entries for it */
-		simple_heap_update(rel, tid, tuple);
-
-		if (resultRelInfo->ri_NumIndices > 0 &&
-			!HeapTupleIsHeapOnly(tuple))
-			recheckIndexes = ExecInsertIndexTuples(slot, tid,
-												   estate, false, NULL,
-												   NIL);
+		tuple = ExecHeapifySlot(slot);
 
 		/* AFTER ROW UPDATE Triggers */
 		ExecARUpdateTriggers(estate, resultRelInfo,
diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c
index 93895600a5..2da5240d24 100644
--- a/src/backend/executor/nodeLockRows.c
+++ b/src/backend/executor/nodeLockRows.c
@@ -22,6 +22,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/xact.h"
 #include "executor/executor.h"
 #include "executor/nodeLockRows.h"
@@ -74,18 +75,20 @@ lnext:
 	{
 		ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc);
 		ExecRowMark *erm = aerm->rowmark;
-		HeapTuple  *testTuple;
+		StorageTuple *testTuple;
 		Datum		datum;
 		bool		isNull;
-		HeapTupleData tuple;
+		StorageTuple tuple;
 		Buffer		buffer;
 		HeapUpdateFailureData hufd;
 		LockTupleMode lockmode;
 		HTSU_Result test;
-		HeapTuple	copyTuple;
+		StorageTuple copyTuple;
+		ItemPointerData tid;
+		tuple_data	t_data;
 
 		/* clear any leftover test tuple for this rel */
-		testTuple = &(node->lr_curtuples[erm->rti - 1]);
+		testTuple = (StorageTuple) (&(node->lr_curtuples[erm->rti - 1]));
 		if (*testTuple != NULL)
 			heap_freetuple(*testTuple);
 		*testTuple = NULL;
@@ -159,7 +162,7 @@ lnext:
 		}
 
 		/* okay, try to lock the tuple */
-		tuple.t_self = *((ItemPointer) DatumGetPointer(datum));
+		tid = *((ItemPointer) DatumGetPointer(datum));
 		switch (erm->markType)
 		{
 			case ROW_MARK_EXCLUSIVE:
@@ -180,11 +183,13 @@ lnext:
 				break;
 		}
 
-		test = heap_lock_tuple(erm->relation, &tuple,
-							   estate->es_output_cid,
-							   lockmode, erm->waitPolicy, true,
-							   &buffer, &hufd);
-		ReleaseBuffer(buffer);
+		test = storage_lock_tuple(erm->relation, &tid, &tuple,
+								  estate->es_output_cid,
+								  lockmode, erm->waitPolicy, true,
+								  &buffer, &hufd);
+		if (BufferIsValid(buffer))
+			ReleaseBuffer(buffer);
+
 		switch (test)
 		{
 			case HeapTupleWouldBlock:
@@ -218,7 +223,8 @@ lnext:
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("could not serialize access due to concurrent update")));
-				if (ItemPointerEquals(&hufd.ctid, &tuple.t_self))
+				t_data = erm->relation->rd_stamroutine->get_tuple_data(tuple, TID);
+				if (ItemPointerEquals(&hufd.ctid, &(t_data.tid)))
 				{
 					/* Tuple was deleted, so don't return it */
 					goto lnext;
@@ -238,7 +244,8 @@ lnext:
 					goto lnext;
 				}
 				/* remember the actually locked tuple's TID */
-				tuple.t_self = copyTuple->t_self;
+				t_data = erm->relation->rd_stamroutine->get_tuple_data(copyTuple, TID);
+				tid = t_data.tid;
 
 				/* Save locked tuple for EvalPlanQual testing below */
 				*testTuple = copyTuple;
@@ -258,7 +265,7 @@ lnext:
 		}
 
 		/* Remember locked tuple's TID for EPQ testing and WHERE CURRENT OF */
-		erm->curCtid = tuple.t_self;
+		erm->curCtid = tid;
 	}
 
 	/*
@@ -280,7 +287,7 @@ lnext:
 		{
 			ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc);
 			ExecRowMark *erm = aerm->rowmark;
-			HeapTupleData tuple;
+			StorageTuple tuple;
 			Buffer		buffer;
 
 			/* skip non-active child tables, but clear their test tuples */
@@ -308,14 +315,12 @@ lnext:
 			Assert(ItemPointerIsValid(&(erm->curCtid)));
 
 			/* okay, fetch the tuple */
-			tuple.t_self = erm->curCtid;
-			if (!heap_fetch(erm->relation, SnapshotAny, &tuple, &buffer,
-							false, NULL))
+			if (!storage_fetch(erm->relation, &erm->curCtid, SnapshotAny, &tuple, &buffer,
+							   false, NULL))
 				elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck");
 
 			/* successful, copy and store tuple */
-			EvalPlanQualSetTuple(&node->lr_epqstate, erm->rti,
-								 heap_copytuple(&tuple));
+			EvalPlanQualSetTuple(&node->lr_epqstate, erm->rti, tuple);
 			ReleaseBuffer(buffer);
 		}
 
@@ -394,8 +399,8 @@ ExecInitLockRows(LockRows *node, EState *estate, int eflags)
 	 * Create workspace in which we can remember per-RTE locked tuples
 	 */
 	lrstate->lr_ntables = list_length(estate->es_range_table);
-	lrstate->lr_curtuples = (HeapTuple *)
-		palloc0(lrstate->lr_ntables * sizeof(HeapTuple));
+	lrstate->lr_curtuples = (StorageTuple *)
+		palloc0(lrstate->lr_ntables * sizeof(StorageTuple));
 
 	/*
 	 * Locate the ExecRowMark(s) that this node is responsible for, and
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index f0307ba50e..86bf30bd06 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -38,7 +38,10 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/storageam.h"
+#include "access/storageam.h"
 #include "access/xact.h"
+#include "catalog/pg_am.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -165,15 +168,13 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
 		econtext->ecxt_scantuple = tupleSlot;
 	else
 	{
-		HeapTuple	tuple;
-
 		/*
 		 * RETURNING expressions might reference the tableoid column, so
 		 * initialize t_tableOid before evaluating them.
 		 */
 		Assert(!TupIsNull(econtext->ecxt_scantuple));
-		tuple = ExecHeapifySlot(econtext->ecxt_scantuple);
-		tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+		ExecSlotUpdateTupleTableoid(econtext->ecxt_scantuple,
+									RelationGetRelid(resultRelInfo->ri_RelationDesc));
 	}
 	econtext->ecxt_outertuple = planSlot;
 
@@ -192,7 +193,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
 static void
 ExecCheckHeapTupleVisible(EState *estate,
 						  Relation rel,
-						  HeapTuple tuple,
+						  StorageTuple tuple,
 						  Buffer buffer)
 {
 	if (!IsolationUsesXactSnapshot())
@@ -205,13 +206,15 @@ ExecCheckHeapTupleVisible(EState *estate,
 	LockBuffer(buffer, BUFFER_LOCK_SHARE);
 	if (!HeapTupleSatisfiesVisibility(rel->rd_stamroutine, tuple, estate->es_snapshot, buffer))
 	{
+		tuple_data	t_data = storage_tuple_get_data(rel, tuple, XMIN);
+
 		/*
 		 * We should not raise a serialization failure if the conflict is
 		 * against a tuple inserted by our own transaction, even if it's not
 		 * visible to our snapshot.  (This would happen, for example, if
 		 * conflicting keys are proposed for insertion in a single command.)
 		 */
-		if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple->t_data)))
+		if (!TransactionIdIsCurrentTransactionId(t_data.xid))
 			ereport(ERROR,
 					(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 					 errmsg("could not serialize access due to concurrent update")));
@@ -227,19 +230,20 @@ ExecCheckTIDVisible(EState *estate,
 					ResultRelInfo *relinfo,
 					ItemPointer tid)
 {
-	Relation	rel = relinfo->ri_RelationDesc;
 	Buffer		buffer;
-	HeapTupleData tuple;
+	Relation	rel = relinfo->ri_RelationDesc;
+	StorageTuple tuple;
 
 	/* Redundantly check isolation level */
 	if (!IsolationUsesXactSnapshot())
 		return;
 
-	tuple.t_self = *tid;
-	if (!heap_fetch(rel, SnapshotAny, &tuple, &buffer, false, NULL))
+	if (!storage_fetch(rel, tid, SnapshotAny, &tuple, &buffer, false, NULL))
 		elog(ERROR, "failed to fetch conflicting tuple for ON CONFLICT");
-	ExecCheckHeapTupleVisible(estate, rel, &tuple, buffer);
-	ReleaseBuffer(buffer);
+	ExecCheckHeapTupleVisible(estate, rel, tuple, buffer);
+	if (BufferIsValid(buffer))
+		ReleaseBuffer(buffer);
+	pfree(tuple);
 }
 
 /* ----------------------------------------------------------------
@@ -260,7 +264,7 @@ ExecInsert(ModifyTableState *mtstate,
 		   EState *estate,
 		   bool canSetTag)
 {
-	HeapTuple	tuple;
+	StorageTuple tuple;
 	ResultRelInfo *resultRelInfo;
 	ResultRelInfo *saved_resultRelInfo = NULL;
 	Relation	resultRelationDesc;
@@ -268,12 +272,6 @@ ExecInsert(ModifyTableState *mtstate,
 	List	   *recheckIndexes = NIL;
 	TupleTableSlot *result = NULL;
 
-	/*
-	 * get the heap tuple out of the tuple table slot, making sure we have a
-	 * writable copy
-	 */
-	tuple = ExecHeapifySlot(slot);
-
 	/*
 	 * get information on the (current) result relation
 	 */
@@ -285,6 +283,8 @@ ExecInsert(ModifyTableState *mtstate,
 		int			leaf_part_index;
 		TupleConversionMap *map;
 
+		tuple = ExecHeapifySlot(slot);
+
 		/*
 		 * Away we go ... If we end up not finding a partition after all,
 		 * ExecFindPartition() does not return and errors out instead.
@@ -375,19 +375,31 @@ ExecInsert(ModifyTableState *mtstate,
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/*
-	 * If the result relation has OIDs, force the tuple's OID to zero so that
-	 * heap_insert will assign a fresh OID.  Usually the OID already will be
-	 * zero at this point, but there are corner cases where the plan tree can
-	 * return a tuple extracted literally from some table with the same
-	 * rowtype.
+	 * get the heap tuple out of the tuple table slot, making sure we have a
+	 * writable copy  <-- obsolete comment XXX explain what we really do here
+	 *
+	 * Do we really need to do this here?
+	 */
+	ExecMaterializeSlot(slot);
+
+
+	/*
+	 * If the result relation uses heapam and has OIDs, force the tuple's OID
+	 * to zero so that heap_insert will assign a fresh OID.  Usually the OID
+	 * already will be zero at this point, but there are corner cases where
+	 * the plan tree can return a tuple extracted literally from some table
+	 * with the same rowtype.
 	 *
 	 * XXX if we ever wanted to allow users to assign their own OIDs to new
 	 * rows, this'd be the place to do it.  For the moment, we make a point of
 	 * doing this before calling triggers, so that a user-supplied trigger
 	 * could hack the OID if desired.
 	 */
-	if (resultRelationDesc->rd_rel->relhasoids)
-		HeapTupleSetOid(tuple, InvalidOid);
+	if (resultRelationDesc->rd_rel->relam == HEAPAM_STORAGE_AM_OID &&
+		resultRelationDesc->rd_rel->relhasoids)
+	{
+		slot->tts_tupleOid = InvalidOid;
+	}
 
 	/*
 	 * BEFORE ROW INSERT Triggers.
@@ -405,9 +417,6 @@ ExecInsert(ModifyTableState *mtstate,
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
-
-		/* trigger might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW INSERT Triggers */
@@ -419,9 +428,6 @@ ExecInsert(ModifyTableState *mtstate,
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
 
-		/* trigger might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
-
 		newId = InvalidOid;
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
@@ -437,14 +443,12 @@ ExecInsert(ModifyTableState *mtstate,
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
 
-		/* FDW might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
-
 		/*
 		 * AFTER ROW Triggers or RETURNING expressions might reference the
 		 * tableoid column, so initialize t_tableOid before evaluating them.
 		 */
-		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
+		slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
+		ExecSlotUpdateTupleTableoid(slot, slot->tts_tableOid);
 
 		newId = InvalidOid;
 	}
@@ -464,7 +468,8 @@ ExecInsert(ModifyTableState *mtstate,
 		 * Constraints might reference the tableoid column, so initialize
 		 * t_tableOid before evaluating them.
 		 */
-		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
+		slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
+		ExecSlotUpdateTupleTableoid(slot, slot->tts_tableOid);
 
 		/*
 		 * Check any RLS INSERT WITH CHECK policies
@@ -494,7 +499,6 @@ ExecInsert(ModifyTableState *mtstate,
 			/* Perform a speculative insertion. */
 			uint32		specToken;
 			ItemPointerData conflictTid;
-			bool		specConflict;
 
 			/*
 			 * Do a non-conclusive check for conflicts first.
@@ -509,7 +513,7 @@ ExecInsert(ModifyTableState *mtstate,
 			 * speculatively.
 			 */
 	vlock:
-			specConflict = false;
+			slot->tts_specConflict = false;
 			if (!ExecCheckIndexConstraints(slot, estate, &conflictTid,
 										   arbiterIndexes))
 			{
@@ -555,24 +559,17 @@ ExecInsert(ModifyTableState *mtstate,
 			 * waiting for the whole transaction to complete.
 			 */
 			specToken = SpeculativeInsertionLockAcquire(GetCurrentTransactionId());
-			HeapTupleHeaderSetSpeculativeToken(tuple->t_data, specToken);
+			slot->tts_speculativeToken = specToken;
 
 			/* insert the tuple, with the speculative token */
-			newId = heap_insert(resultRelationDesc, tuple,
-								estate->es_output_cid,
-								HEAP_INSERT_SPECULATIVE,
-								NULL);
-
-			/* insert index entries for tuple */
-			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
-												   estate, true, &specConflict,
-												   arbiterIndexes);
-
-			/* adjust the tuple's state accordingly */
-			if (!specConflict)
-				heap_finish_speculative(resultRelationDesc, tuple);
-			else
-				heap_abort_speculative(resultRelationDesc, tuple);
+			newId = storage_insert(resultRelationDesc, slot,
+								   estate->es_output_cid,
+								   HEAP_INSERT_SPECULATIVE,
+								   NULL,
+								   ExecInsertIndexTuples,
+								   estate,
+								   arbiterIndexes,
+								   &recheckIndexes);
 
 			/*
 			 * Wake up anyone waiting for our decision.  They will re-check
@@ -588,7 +585,7 @@ ExecInsert(ModifyTableState *mtstate,
 			 * the pre-check again, which will now find the conflicting tuple
 			 * (unless it aborts before we get there).
 			 */
-			if (specConflict)
+			if (slot->tts_specConflict)
 			{
 				list_free(recheckIndexes);
 				goto vlock;
@@ -600,19 +597,14 @@ ExecInsert(ModifyTableState *mtstate,
 		{
 			/*
 			 * insert the tuple normally.
-			 *
-			 * Note: heap_insert returns the tid (location) of the new tuple
-			 * in the t_self field.
 			 */
-			newId = heap_insert(resultRelationDesc, tuple,
-								estate->es_output_cid,
-								0, NULL);
-
-			/* insert index entries for tuple */
-			if (resultRelInfo->ri_NumIndices > 0)
-				recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
-													   estate, false, NULL,
-													   arbiterIndexes);
+			newId = storage_insert(resultRelationDesc, slot,
+								   estate->es_output_cid,
+								   0, NULL,
+								   ExecInsertIndexTuples,
+								   estate,
+								   arbiterIndexes,
+								   &recheckIndexes);
 		}
 	}
 
@@ -620,11 +612,11 @@ ExecInsert(ModifyTableState *mtstate,
 	{
 		(estate->es_processed)++;
 		estate->es_lastoid = newId;
-		setLastTid(&(tuple->t_self));
+		setLastTid(&(slot->tts_tid));
 	}
 
 	/* AFTER ROW INSERT Triggers */
-	ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes,
+	ExecARInsertTriggers(estate, resultRelInfo, slot, recheckIndexes,
 						 mtstate->mt_transition_capture);
 
 	list_free(recheckIndexes);
@@ -675,7 +667,7 @@ ExecInsert(ModifyTableState *mtstate,
 static TupleTableSlot *
 ExecDelete(ModifyTableState *mtstate,
 		   ItemPointer tupleid,
-		   HeapTuple oldtuple,
+		   StorageTuple oldtuple,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
 		   EState *estate,
@@ -720,8 +712,6 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
-		HeapTuple	tuple;
-
 		/*
 		 * delete from foreign table: let the FDW do it
 		 *
@@ -747,8 +737,10 @@ ExecDelete(ModifyTableState *mtstate,
 		 */
 		if (slot->tts_isempty)
 			ExecStoreAllNullTuple(slot);
-		tuple = ExecHeapifySlot(slot);
-		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
+
+		ExecMaterializeSlot(slot);
+
+		slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
 	}
 	else
 	{
@@ -762,11 +754,11 @@ ExecDelete(ModifyTableState *mtstate,
 		 * mode transactions.
 		 */
 ldelete:;
-		result = heap_delete(resultRelationDesc, tupleid,
-							 estate->es_output_cid,
-							 estate->es_crosscheck_snapshot,
-							 true /* wait for commit */ ,
-							 &hufd);
+		result = storage_delete(resultRelationDesc, tupleid,
+								estate->es_output_cid,
+								estate->es_crosscheck_snapshot,
+								true /* wait for commit */ ,
+								&hufd);
 		switch (result)
 		{
 			case HeapTupleSelfUpdated:
@@ -862,7 +854,7 @@ ldelete:;
 		 * gotta fetch it.  We can use the trigger tuple slot.
 		 */
 		TupleTableSlot *rslot;
-		HeapTupleData deltuple;
+		StorageTuple deltuple = NULL;
 		Buffer		delbuffer;
 
 		if (resultRelInfo->ri_FdwRoutine)
@@ -876,20 +868,19 @@ ldelete:;
 			slot = estate->es_trig_tuple_slot;
 			if (oldtuple != NULL)
 			{
-				deltuple = *oldtuple;
+				deltuple = heap_copytuple(oldtuple);
 				delbuffer = InvalidBuffer;
 			}
 			else
 			{
-				deltuple.t_self = *tupleid;
-				if (!heap_fetch(resultRelationDesc, SnapshotAny,
-								&deltuple, &delbuffer, false, NULL))
+				if (!storage_fetch(resultRelationDesc, tupleid, SnapshotAny,
+								   &deltuple, &delbuffer, false, NULL))
 					elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
 			}
 
 			if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
 				ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
-			ExecStoreTuple(&deltuple, slot, InvalidBuffer, false);
+			ExecStoreTuple(deltuple, slot, InvalidBuffer, false);
 		}
 
 		rslot = ExecProcessReturning(resultRelInfo, slot, planSlot);
@@ -898,7 +889,7 @@ ldelete:;
 		 * Before releasing the target tuple again, make sure rslot has a
 		 * local copy of any pass-by-reference values.
 		 */
-		ExecHeapifySlot(rslot);
+		ExecMaterializeSlot(rslot);
 
 		ExecClearTuple(slot);
 		if (BufferIsValid(delbuffer))
@@ -935,14 +926,14 @@ ldelete:;
 static TupleTableSlot *
 ExecUpdate(ModifyTableState *mtstate,
 		   ItemPointer tupleid,
-		   HeapTuple oldtuple,
+		   StorageTuple oldtuple,
 		   TupleTableSlot *slot,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
 		   EState *estate,
 		   bool canSetTag)
 {
-	HeapTuple	tuple;
+	StorageTuple tuple;
 	ResultRelInfo *resultRelInfo;
 	Relation	resultRelationDesc;
 	HTSU_Result result;
@@ -1007,14 +998,14 @@ ExecUpdate(ModifyTableState *mtstate,
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
 
-		/* FDW might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
-
 		/*
 		 * AFTER ROW Triggers or RETURNING expressions might reference the
 		 * tableoid column, so initialize t_tableOid before evaluating them.
 		 */
-		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
+		ExecSlotUpdateTupleTableoid(slot, RelationGetRelid(resultRelationDesc));
+
+		/* FDW might have changed tuple */
+		tuple = ExecHeapifySlot(slot);
 	}
 	else
 	{
@@ -1024,7 +1015,7 @@ ExecUpdate(ModifyTableState *mtstate,
 		 * Constraints might reference the tableoid column, so initialize
 		 * t_tableOid before evaluating them.
 		 */
-		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
+		slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
 
 		/*
 		 * Check any RLS UPDATE WITH CHECK policies
@@ -1060,11 +1051,14 @@ lreplace:;
 		 * needed for referential integrity updates in transaction-snapshot
 		 * mode transactions.
 		 */
-		result = heap_update(resultRelationDesc, tupleid, tuple,
-							 estate->es_output_cid,
-							 estate->es_crosscheck_snapshot,
-							 true /* wait for commit */ ,
-							 &hufd, &lockmode);
+		result = storage_update(resultRelationDesc, tupleid, slot,
+								estate,
+								estate->es_output_cid,
+								estate->es_crosscheck_snapshot,
+								true /* wait for commit */ ,
+								&hufd, &lockmode,
+								ExecInsertIndexTuples,
+								&recheckIndexes);
 		switch (result)
 		{
 			case HeapTupleSelfUpdated:
@@ -1135,26 +1129,6 @@ lreplace:;
 				elog(ERROR, "unrecognized heap_update status: %u", result);
 				return NULL;
 		}
-
-		/*
-		 * Note: instead of having to update the old index tuples associated
-		 * with the heap tuple, all we do is form and insert new index tuples.
-		 * This is because UPDATEs are actually DELETEs and INSERTs, and index
-		 * tuple deletion is done later by VACUUM (see notes in ExecDelete).
-		 * All we do here is insert new index tuples.  -cim 9/27/89
-		 */
-
-		/*
-		 * insert index entries for tuple
-		 *
-		 * Note: heap_update returns the tid (location) of the new tuple in
-		 * the t_self field.
-		 *
-		 * If it's a HOT update, we mustn't insert new index entries.
-		 */
-		if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple))
-			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
-												   estate, false, NULL, NIL);
 	}
 
 	if (canSetTag)
@@ -1212,11 +1186,12 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	ExprContext *econtext = mtstate->ps.ps_ExprContext;
 	Relation	relation = resultRelInfo->ri_RelationDesc;
 	ExprState  *onConflictSetWhere = resultRelInfo->ri_onConflictSetWhere;
-	HeapTupleData tuple;
+	StorageTuple tuple = NULL;
 	HeapUpdateFailureData hufd;
 	LockTupleMode lockmode;
 	HTSU_Result test;
 	Buffer		buffer;
+	tuple_data	t_data;
 
 	/* Determine lock mode to use */
 	lockmode = ExecUpdateLockMode(estate, resultRelInfo);
@@ -1227,10 +1202,8 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	 * previous conclusion that the tuple is conclusively committed is not
 	 * true anymore.
 	 */
-	tuple.t_self = *conflictTid;
-	test = heap_lock_tuple(relation, &tuple, estate->es_output_cid,
-						   lockmode, LockWaitBlock, false, &buffer,
-						   &hufd);
+	test = storage_lock_tuple(relation, conflictTid, &tuple, estate->es_output_cid,
+							  lockmode, LockWaitBlock, false, &buffer, &hufd);
 	switch (test)
 	{
 		case HeapTupleMayBeUpdated:
@@ -1255,7 +1228,8 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 			 * that for SQL MERGE, an exception must be raised in the event of
 			 * an attempt to update the same row twice.
 			 */
-			if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple.t_data)))
+			t_data = storage_tuple_get_data(relation, tuple, XMIN);
+			if (TransactionIdIsCurrentTransactionId(t_data.xid))
 				ereport(ERROR,
 						(errcode(ERRCODE_CARDINALITY_VIOLATION),
 						 errmsg("ON CONFLICT DO UPDATE command cannot affect row a second time"),
@@ -1286,7 +1260,9 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 			 * loop here, as the new version of the row might not conflict
 			 * anymore, or the conflicting tuple has actually been deleted.
 			 */
-			ReleaseBuffer(buffer);
+			if (BufferIsValid(buffer))
+				ReleaseBuffer(buffer);
+			pfree(tuple);
 			return false;
 
 		default:
@@ -1314,10 +1290,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, buffer);
 
 	/* Store target's existing tuple in the state's dedicated slot */
-	ExecStoreTuple(&tuple, mtstate->mt_existing, buffer, false);
+	ExecStoreTuple(tuple, mtstate->mt_existing, buffer, false);
 
 	/*
 	 * Make tuple and any needed join variables available to ExecQual and
@@ -1332,7 +1308,9 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 
 	if (!ExecQual(onConflictSetWhere, econtext))
 	{
-		ReleaseBuffer(buffer);
+		if (BufferIsValid(buffer))
+			ReleaseBuffer(buffer);
+		pfree(tuple);
 		InstrCountFiltered1(&mtstate->ps, 1);
 		return true;			/* done with the tuple */
 	}
@@ -1372,12 +1350,14 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	 */
 
 	/* Execute UPDATE with projection */
-	*returning = ExecUpdate(mtstate, &tuple.t_self, NULL,
+	*returning = ExecUpdate(mtstate, conflictTid, NULL,
 							mtstate->mt_conflproj, planSlot,
 							&mtstate->mt_epqstate, mtstate->ps.state,
 							canSetTag);
 
-	ReleaseBuffer(buffer);
+	if (BufferIsValid(buffer))
+		ReleaseBuffer(buffer);
+	pfree(tuple);
 	return true;
 }
 
@@ -1580,7 +1560,7 @@ ExecModifyTable(PlanState *pstate)
 	ItemPointer tupleid;
 	ItemPointerData tuple_ctid;
 	HeapTupleData oldtupdata;
-	HeapTuple	oldtuple;
+	StorageTuple oldtuple;
 
 	CHECK_FOR_INTERRUPTS();
 
diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c
index 0ee76e7d25..47d8b7b12f 100644
--- a/src/backend/executor/nodeTidscan.c
+++ b/src/backend/executor/nodeTidscan.c
@@ -22,6 +22,7 @@
  */
 #include "postgres.h"
 
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "catalog/pg_type.h"
 #include "executor/execdebug.h"
@@ -306,7 +307,7 @@ TidNext(TidScanState *node)
 	ScanDirection direction;
 	Snapshot	snapshot;
 	Relation	heapRelation;
-	HeapTuple	tuple;
+	StorageTuple tuple;
 	TupleTableSlot *slot;
 	Buffer		buffer = InvalidBuffer;
 	ItemPointerData *tidList;
@@ -331,12 +332,6 @@ TidNext(TidScanState *node)
 	tidList = node->tss_TidList;
 	numTids = node->tss_NumTids;
 
-	/*
-	 * We use node->tss_htup as the tuple pointer; note this can't just be a
-	 * local variable here, as the scan tuple slot will keep a pointer to it.
-	 */
-	tuple = &(node->tss_htup);
-
 	/*
 	 * Initialize or advance scan position, depending on direction.
 	 */
@@ -364,7 +359,7 @@ TidNext(TidScanState *node)
 
 	while (node->tss_TidPtr >= 0 && node->tss_TidPtr < numTids)
 	{
-		tuple->t_self = tidList[node->tss_TidPtr];
+		ItemPointerData tid = tidList[node->tss_TidPtr];
 
 		/*
 		 * For WHERE CURRENT OF, the tuple retrieved from the cursor might
@@ -372,9 +367,9 @@ TidNext(TidScanState *node)
 		 * current according to our snapshot.
 		 */
 		if (node->tss_isCurrentOf)
-			heap_get_latest_tid(heapRelation, snapshot, &tuple->t_self);
+			storage_get_latest_tid(heapRelation, snapshot, &tid);
 
-		if (heap_fetch(heapRelation, snapshot, tuple, &buffer, false, NULL))
+		if (storage_fetch(heapRelation, &tid, snapshot, &tuple, &buffer, false, NULL))
 		{
 			/*
 			 * store the scanned tuple in the scan tuple slot of the scan
@@ -385,14 +380,16 @@ TidNext(TidScanState *node)
 			 */
 			ExecStoreTuple(tuple,	/* tuple to store */
 						   slot,	/* slot to store in */
-						   buffer,	/* buffer associated with tuple  */
-						   false);	/* don't pfree */
+						   InvalidBuffer,	/* buffer associated with tuple  */
+						   true);	/* don't pfree */
 
 			/*
 			 * At this point we have an extra pin on the buffer, because
 			 * ExecStoreTuple incremented the pin count. Drop our local pin.
 			 */
-			ReleaseBuffer(buffer);
+			/* hari */
+			if (BufferIsValid(buffer))
+				ReleaseBuffer(buffer);
 
 			return slot;
 		}
diff --git a/src/backend/utils/adt/tid.c b/src/backend/utils/adt/tid.c
index 854097dd58..c4481ecee9 100644
--- a/src/backend/utils/adt/tid.c
+++ b/src/backend/utils/adt/tid.c
@@ -21,6 +21,7 @@
 #include <limits.h>
 
 #include "access/heapam.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_type.h"
@@ -352,7 +353,7 @@ currtid_byreloid(PG_FUNCTION_ARGS)
 	ItemPointerCopy(tid, result);
 
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	heap_get_latest_tid(rel, snapshot, result);
+	storage_get_latest_tid(rel, snapshot, result);
 	UnregisterSnapshot(snapshot);
 
 	heap_close(rel, AccessShareLock);
@@ -387,7 +388,7 @@ currtid_byrelname(PG_FUNCTION_ARGS)
 	ItemPointerCopy(tid, result);
 
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	heap_get_latest_tid(rel, snapshot, result);
+	storage_get_latest_tid(rel, snapshot, result);
 	UnregisterSnapshot(snapshot);
 
 	heap_close(rel, AccessShareLock);
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 4e41024e92..251342b01b 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -16,6 +16,7 @@
 
 #include "access/sdir.h"
 #include "access/skey.h"
+#include "access/storage_common.h"
 #include "nodes/lockoptions.h"
 #include "nodes/primnodes.h"
 #include "storage/bufpage.h"
@@ -133,7 +134,7 @@ extern void heap_parallelscan_initialize(ParallelHeapScanDesc target,
 extern void heap_parallelscan_reinitialize(ParallelHeapScanDesc parallel_scan);
 extern HeapScanDesc heap_beginscan_parallel(Relation, ParallelHeapScanDesc);
 
-extern bool heap_fetch(Relation relation, Snapshot snapshot,
+extern bool heap_fetch(Relation relation, ItemPointer tid, Snapshot snapshot,
 		   HeapTuple tuple, Buffer *userbuf, bool keep_buf,
 		   Relation stats_relation);
 extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation,
@@ -141,7 +142,6 @@ extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation,
 					   bool *all_dead, bool first_call);
 extern bool heap_hot_search(ItemPointer tid, Relation relation,
 				Snapshot snapshot, bool *all_dead);
-
 extern void heap_get_latest_tid(Relation relation, Snapshot snapshot,
 					ItemPointer tid);
 extern void setLastTid(const ItemPointer tid);
@@ -157,16 +157,17 @@ extern void heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 extern HTSU_Result heap_delete(Relation relation, ItemPointer tid,
 			CommandId cid, Snapshot crosscheck, bool wait,
 			HeapUpdateFailureData *hufd);
-extern void heap_finish_speculative(Relation relation, HeapTuple tuple);
-extern void heap_abort_speculative(Relation relation, HeapTuple tuple);
+extern void heap_finish_speculative(Relation relation, TupleTableSlot *slot);
+extern void heap_abort_speculative(Relation relation, TupleTableSlot *slot);
 extern HTSU_Result heap_update(Relation relation, ItemPointer otid,
 			HeapTuple newtup,
 			CommandId cid, Snapshot crosscheck, bool wait,
 			HeapUpdateFailureData *hufd, LockTupleMode *lockmode);
-extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
+extern HTSU_Result heap_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * tuple,
 				CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 				bool follow_update,
 				Buffer *buffer, HeapUpdateFailureData *hufd);
+
 extern void heap_inplace_update(Relation relation, HeapTuple tuple);
 extern bool heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
 				  TransactionId cutoff_multi);
@@ -182,6 +183,10 @@ extern void simple_heap_update(Relation relation, ItemPointer otid,
 extern void heap_sync(Relation relation);
 extern void heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot);
 
+/* in heap/heapam_visibility.c */
+extern HTSU_Result HeapTupleSatisfiesUpdate(StorageTuple stup, CommandId curcid,
+						 Buffer buffer);
+
 /* in heap/pruneheap.c */
 extern void heap_page_prune_opt(Relation relation, Buffer buffer);
 extern int heap_page_prune(Relation relation, Buffer buffer,
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index 168edb058d..489aa78731 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -816,6 +816,7 @@ extern Datum heap_getsysattr(HeapTuple tup, int attnum, TupleDesc tupleDesc,
 extern HeapTuple heap_copytuple(HeapTuple tuple);
 extern void heap_copytuple_with_tuple(HeapTuple src, HeapTuple dest);
 extern Datum heap_copy_tuple_as_datum(HeapTuple tuple, TupleDesc tupleDesc);
+extern HeapTuple heap_form_tuple_by_datum(Datum data, Oid relid);
 extern HeapTuple heap_form_tuple(TupleDesc tupleDescriptor,
 				Datum *values, bool *isnull);
 extern HeapTuple heap_modify_tuple(HeapTuple tuple,
diff --git a/src/include/access/storage_common.h b/src/include/access/storage_common.h
index 5f799406fe..ff28b4dfec 100644
--- a/src/include/access/storage_common.h
+++ b/src/include/access/storage_common.h
@@ -45,9 +45,6 @@ typedef MinimalTuple (*SlotGetMinTuple_function) (TupleTableSlot *slot, bool pal
 
 typedef void (*SlotUpdateTableoid_function) (TupleTableSlot *slot, Oid tableoid);
 
-typedef void (*SpeculativeAbort_function) (Relation rel,
-										   TupleTableSlot *slot);
-
 typedef struct StorageSlotAmRoutine
 {
 	/* Operations on TupleTableSlot */
@@ -74,8 +71,6 @@ typedef enum
 
 
 /* in storage/storage_common.c */
-extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
-					 uint16 infomask, TransactionId xid);
 extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
 extern bool HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin);
 extern bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
diff --git a/src/include/access/storageam.h b/src/include/access/storageam.h
new file mode 100644
index 0000000000..e0658a1ddd
--- /dev/null
+++ b/src/include/access/storageam.h
@@ -0,0 +1,63 @@
+/*-------------------------------------------------------------------------
+ *
+ * storageam.h
+ *	  POSTGRES storage access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/storageam.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGEAM_H
+#define STORAGEAM_H
+
+#include "access/heapam.h"
+#include "access/storageamapi.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
+
+extern bool storage_fetch(Relation relation,
+			  ItemPointer tid,
+			  Snapshot snapshot,
+			  StorageTuple * stuple,
+			  Buffer *userbuf,
+			  bool keep_buf,
+			  Relation stats_relation);
+
+extern HTSU_Result storage_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * stuple,
+				   CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
+				   bool follow_updates,
+				   Buffer *buffer, HeapUpdateFailureData *hufd);
+
+extern Oid storage_insert(Relation relation, TupleTableSlot *slot, CommandId cid,
+			   int options, BulkInsertState bistate, InsertIndexTuples IndexFunc,
+			   EState *estate, List *arbiterIndexes, List **recheckIndexes);
+
+extern HTSU_Result storage_delete(Relation relation, ItemPointer tid, CommandId cid,
+			   Snapshot crosscheck, bool wait,
+			   HeapUpdateFailureData *hufd);
+
+extern HTSU_Result storage_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
+			   EState *estate, CommandId cid, Snapshot crosscheck, bool wait,
+			   HeapUpdateFailureData *hufd, LockTupleMode *lockmode,
+			   InsertIndexTuples IndexFunc, List **recheckIndexes);
+
+extern void storage_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
+					 CommandId cid, int options, BulkInsertState bistate);
+
+extern void storage_abort_speculative(Relation relation, TupleTableSlot *slot);
+
+extern tuple_data storage_tuple_get_data(Relation relation, StorageTuple tuple, tuple_data_flags flags);
+
+extern StorageTuple storage_tuple_by_datum(Relation relation, Datum data, Oid tableoid);
+
+extern void storage_get_latest_tid(Relation relation,
+					   Snapshot snapshot,
+					   ItemPointer tid);
+
+extern void storage_sync(Relation rel);
+
+#endif
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
index 5e807d827e..22bc2493ac 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -11,11 +11,33 @@
 #ifndef STORAGEAMAPI_H
 #define STORAGEAMAPI_H
 
+#include "access/heapam.h"
 #include "access/storage_common.h"
+#include "nodes/execnodes.h"
 #include "nodes/nodes.h"
 #include "utils/snapshot.h"
 #include "fmgr.h"
 
+typedef union tuple_data
+{
+	TransactionId xid;
+	CommandId	cid;
+	ItemPointerData tid;
+}			tuple_data;
+
+typedef enum tuple_data_flags
+{
+	XMIN = 0,
+	UPDATED_XID,
+	CMIN,
+	TID,
+	CTID
+}			tuple_data_flags;
+
+/* Function pointer to let the index tuple insert from storage am */
+typedef List *(*InsertIndexTuples) (TupleTableSlot *slot, EState *estate, bool noDupErr,
+									bool *specConflict, List *arbiterIndexes);
+
 /*
  * Storage routine functions
  */
@@ -24,6 +46,63 @@ typedef HTSU_Result (*SnapshotSatisfiesUpdate_function) (StorageTuple htup, Comm
 typedef HTSV_Result (*SnapshotSatisfiesVacuum_function) (StorageTuple htup, TransactionId OldestXmin, Buffer buffer);
 
 
+typedef Oid (*TupleInsert_function) (Relation rel, TupleTableSlot *slot, CommandId cid,
+									 int options, BulkInsertState bistate, InsertIndexTuples IndexFunc,
+									 EState *estate, List *arbiterIndexes, List **recheckIndexes);
+
+typedef HTSU_Result (*TupleDelete_function) (Relation relation,
+											 ItemPointer tid,
+											 CommandId cid,
+											 Snapshot crosscheck,
+											 bool wait,
+											 HeapUpdateFailureData *hufd);
+
+typedef HTSU_Result (*TupleUpdate_function) (Relation relation,
+											 ItemPointer otid,
+											 TupleTableSlot *slot,
+											 EState *estate,
+											 CommandId cid,
+											 Snapshot crosscheck,
+											 bool wait,
+											 HeapUpdateFailureData *hufd,
+											 LockTupleMode *lockmode,
+											 InsertIndexTuples IndexFunc,
+											 List **recheckIndexes);
+
+typedef bool (*TupleFetch_function) (Relation relation,
+									 ItemPointer tid,
+									 Snapshot snapshot,
+									 StorageTuple * tuple,
+									 Buffer *userbuf,
+									 bool keep_buf,
+									 Relation stats_relation);
+
+typedef HTSU_Result (*TupleLock_function) (Relation relation,
+										   ItemPointer tid,
+										   StorageTuple * tuple,
+										   CommandId cid,
+										   LockTupleMode mode,
+										   LockWaitPolicy wait_policy,
+										   bool follow_update,
+										   Buffer *buffer,
+										   HeapUpdateFailureData *hufd);
+
+typedef void (*MultiInsert_function) (Relation relation, HeapTuple *tuples, int ntuples,
+									  CommandId cid, int options, BulkInsertState bistate);
+
+typedef void (*TupleGetLatestTid_function) (Relation relation,
+											Snapshot snapshot,
+											ItemPointer tid);
+
+typedef tuple_data(*GetTupleData_function) (StorageTuple tuple, tuple_data_flags flags);
+
+typedef StorageTuple(*TupleFromDatum_function) (Datum data, Oid tableoid);
+
+typedef void (*SpeculativeAbort_function) (Relation rel,
+										   TupleTableSlot *slot);
+
+typedef void (*RelationSync_function) (Relation relation);
+
 /*
  * API struct for a storage AM.  Note this must be stored in a single palloc'd
  * chunk of memory.
@@ -43,6 +122,29 @@ typedef struct StorageAmRoutine
 
 	slot_storageam_hook slot_storageam;
 
+	/* Operations on physical tuples */
+	TupleInsert_function tuple_insert;	/* heap_insert */
+	TupleUpdate_function tuple_update;	/* heap_update */
+	TupleDelete_function tuple_delete;	/* heap_delete */
+	TupleFetch_function tuple_fetch;	/* heap_fetch */
+	TupleLock_function tuple_lock;	/* heap_lock_tuple */
+	MultiInsert_function multi_insert;	/* heap_multi_insert */
+	TupleGetLatestTid_function tuple_get_latest_tid;	/* heap_get_latest_tid */
+
+	GetTupleData_function get_tuple_data;
+	TupleFromDatum_function tuple_from_datum;
+
+	/*
+	 * Speculative insertion support operations
+	 *
+	 * Setting a tuple's speculative token is a slot-only operation, so no
+	 * need for a storage AM method, but after inserting a tuple containing a
+	 * speculative token, the insertion must be completed by these routines:
+	 */
+	SpeculativeAbort_function speculative_abort;
+
+	RelationSync_function relation_sync;	/* heap_sync */
+
 }			StorageAmRoutine;
 
 extern StorageAmRoutine * GetStorageAmRoutine(Oid amhandler);
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index adbcfa1297..203371148c 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -190,7 +190,7 @@ extern TupleTableSlot *ExecBRInsertTriggers(EState *estate,
 					 TupleTableSlot *slot);
 extern void ExecARInsertTriggers(EState *estate,
 					 ResultRelInfo *relinfo,
-					 HeapTuple trigtuple,
+					 TupleTableSlot *slot,
 					 List *recheckIndexes,
 					 TransitionCaptureState *transition_capture);
 extern TupleTableSlot *ExecIRInsertTriggers(EState *estate,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index b5578f5855..fa04a5ea65 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -198,16 +198,16 @@ 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 HeapTuple EvalPlanQualFetch(EState *estate, Relation relation,
-				  int lockmode, LockWaitPolicy wait_policy, ItemPointer tid,
-				  TransactionId priorXmax);
+extern StorageTuple EvalPlanQualFetch(EState *estate, Relation relation,
+									  int lockmode, LockWaitPolicy wait_policy, ItemPointer tid,
+									  TransactionId priorXmax);
 extern void EvalPlanQualInit(EPQState *epqstate, EState *estate,
 				 Plan *subplan, List *auxrowmarks, int epqParam);
 extern void EvalPlanQualSetPlan(EPQState *epqstate,
 					Plan *subplan, List *auxrowmarks);
 extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
-					 HeapTuple tuple);
-extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+					 StorageTuple tuple);
+extern StorageTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
 
 #define EvalPlanQualSetSlot(epqstate, slot)  ((epqstate)->origslot = (slot))
 extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
@@ -520,9 +520,8 @@ extern int	ExecCleanTargetListLength(List *targetlist);
  */
 extern void ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative);
 extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
-extern List *ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid,
-					  EState *estate, bool noDupErr, bool *specConflict,
-					  List *arbiterIndexes);
+extern List *ExecInsertIndexTuples(TupleTableSlot *slot, EState *estate, bool noDupErr,
+					  bool *specConflict, List *arbiterIndexes);
 extern bool ExecCheckIndexConstraints(TupleTableSlot *slot, EState *estate,
 						  ItemPointer conflictTid, List *arbiterIndexes);
 extern void check_exclusion_constraint(Relation heap, Relation index,
diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h
index e6ff66f14b..7839d5e9b7 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -133,6 +133,7 @@ typedef struct TupleTableSlot
 	Oid			tts_tupleOid;	/* XXX describe */
 	int			tts_nvalid;		/* # of valid values in tts_values */
 	uint32		tts_speculativeToken;	/* XXX describe */
+	bool		tts_specConflict;	/* XXX describe */
 	bool		tts_shouldFree;
 	bool		tts_shouldFreeMin;
 	Datum	   *tts_values;		/* current per-attribute values */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 1a35c5c9ad..a956138e4a 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -504,7 +504,7 @@ typedef struct EState
 	 * remember if the tuple has been returned already.  Arrays are of size
 	 * list_length(es_range_table) and are indexed by scan node scanrelid - 1.
 	 */
-	HeapTuple  *es_epqTuple;	/* array of EPQ substitute tuples */
+	StorageTuple *es_epqTuple;	/* array of EPQ substitute tuples */
 	bool	   *es_epqTupleSet; /* true if EPQ tuple is provided */
 	bool	   *es_epqScanDone; /* true if EPQ tuple has been fetched */
 
@@ -2001,7 +2001,7 @@ typedef struct HashInstrumentation
 	int			nbatch;			/* number of batches at end of execution */
 	int			nbatch_original;	/* planned number of batches */
 	size_t		space_peak;		/* speak memory usage in bytes */
-} HashInstrumentation;
+}			HashInstrumentation;
 
 /* ----------------
  *	 Shared memory container for per-worker hash information
@@ -2011,7 +2011,7 @@ typedef struct SharedHashInfo
 {
 	int			num_workers;
 	HashInstrumentation hinstrument[FLEXIBLE_ARRAY_MEMBER];
-} SharedHashInfo;
+}			SharedHashInfo;
 
 /* ----------------
  *	 HashState information
@@ -2069,7 +2069,7 @@ typedef struct LockRowsState
 	PlanState	ps;				/* its first field is NodeTag */
 	List	   *lr_arowMarks;	/* List of ExecAuxRowMarks */
 	EPQState	lr_epqstate;	/* for evaluating EvalPlanQual rechecks */
-	HeapTuple  *lr_curtuples;	/* locked tuples (one entry per RT entry) */
+	StorageTuple *lr_curtuples; /* locked tuples (one entry per RT entry) */
 	int			lr_ntables;		/* length of lr_curtuples[] array */
 } LockRowsState;
 
-- 
2.15.0.windows.1



  [application/octet-stream] 0007-Scan-functions-are-added-to-storage-AM.patch (105.2K, ../../CAJrrPGdsQSRuq7N3W63CWXVkM_Acgc8HBphDvzOq1ik=Mt6j+Q@mail.gmail.com/10-0007-Scan-functions-are-added-to-storage-AM.patch)
  download | inline diff:
From 6df7050db6b88afe01893f7473ed7ff4892fff24 Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Fri, 8 Dec 2017 21:53:43 +1100
Subject: [PATCH 7/8] Scan functions are added to storage AM

All the scan functions that are present
in heapam module are moved into heapm_storage
and corresponding function hooks are added.

Replaced HeapTuple with StorageTuple whereever
possible.

Currently directly returning slot functionality
instead of tuple is added only to limited number
of places.
---
 contrib/pgrowlocks/pgrowlocks.c            |   6 +-
 contrib/pgstattuple/pgstattuple.c          |   6 +-
 src/backend/access/heap/heapam.c           | 212 ++++++++++-----------------
 src/backend/access/heap/heapam_storage.c   |  11 +-
 src/backend/access/index/genam.c           |  11 +-
 src/backend/access/index/indexam.c         |  13 +-
 src/backend/access/nbtree/nbtinsert.c      |   7 +-
 src/backend/access/storage/storageam.c     | 223 +++++++++++++++++++++++++++++
 src/backend/bootstrap/bootstrap.c          |  25 ++--
 src/backend/catalog/aclchk.c               |  13 +-
 src/backend/catalog/index.c                |  59 ++++----
 src/backend/catalog/partition.c            |   7 +-
 src/backend/catalog/pg_conversion.c        |   7 +-
 src/backend/catalog/pg_db_role_setting.c   |   7 +-
 src/backend/catalog/pg_publication.c       |   7 +-
 src/backend/catalog/pg_subscription.c      |   7 +-
 src/backend/commands/cluster.c             |  13 +-
 src/backend/commands/constraint.c          |   3 +-
 src/backend/commands/copy.c                |   6 +-
 src/backend/commands/dbcommands.c          |  19 +--
 src/backend/commands/indexcmds.c           |   7 +-
 src/backend/commands/tablecmds.c           |  30 ++--
 src/backend/commands/tablespace.c          |  39 ++---
 src/backend/commands/typecmds.c            |  13 +-
 src/backend/commands/vacuum.c              |  13 +-
 src/backend/executor/execAmi.c             |   2 +-
 src/backend/executor/execIndexing.c        |  13 +-
 src/backend/executor/execReplication.c     |  15 +-
 src/backend/executor/execTuples.c          |   8 +-
 src/backend/executor/functions.c           |   4 +-
 src/backend/executor/nodeAgg.c             |   4 +-
 src/backend/executor/nodeBitmapHeapscan.c  |  19 +--
 src/backend/executor/nodeForeignscan.c     |   6 +-
 src/backend/executor/nodeGather.c          |   8 +-
 src/backend/executor/nodeGatherMerge.c     |  14 +-
 src/backend/executor/nodeIndexonlyscan.c   |   8 +-
 src/backend/executor/nodeIndexscan.c       |  16 +--
 src/backend/executor/nodeSamplescan.c      |  34 ++---
 src/backend/executor/nodeSeqscan.c         |  45 ++----
 src/backend/executor/nodeWindowAgg.c       |   4 +-
 src/backend/executor/spi.c                 |  20 +--
 src/backend/executor/tqueue.c              |   2 +-
 src/backend/postmaster/autovacuum.c        |  18 +--
 src/backend/postmaster/pgstat.c            |   7 +-
 src/backend/replication/logical/launcher.c |   7 +-
 src/backend/rewrite/rewriteDefine.c        |   7 +-
 src/backend/utils/init/postinit.c          |   7 +-
 src/include/access/heapam.h                |  27 ++--
 src/include/access/storage_common.h        |   1 +
 src/include/access/storageam.h             |  58 +++++++-
 src/include/access/storageamapi.h          |  66 ++++++---
 src/include/executor/functions.h           |   2 +-
 src/include/executor/spi.h                 |  12 +-
 src/include/executor/tqueue.h              |   4 +-
 src/include/funcapi.h                      |   2 +-
 55 files changed, 735 insertions(+), 469 deletions(-)

diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index 830e74fd07..bc8b423975 100644
--- a/contrib/pgrowlocks/pgrowlocks.c
+++ b/contrib/pgrowlocks/pgrowlocks.c
@@ -125,7 +125,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
 			aclcheck_error(aclresult, ACL_KIND_CLASS,
 						   RelationGetRelationName(rel));
 
-		scan = heap_beginscan(rel, GetActiveSnapshot(), 0, NULL);
+		scan = storage_beginscan(rel, GetActiveSnapshot(), 0, NULL);
 		mydata = palloc(sizeof(*mydata));
 		mydata->rel = rel;
 		mydata->scan = scan;
@@ -141,7 +141,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
 	scan = mydata->scan;
 
 	/* scan the relation */
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		HTSU_Result htsu;
 		TransactionId xmax;
@@ -306,7 +306,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
 		}
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(mydata->rel, AccessShareLock);
 
 	SRF_RETURN_DONE(funcctx);
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index e098202f84..c4b10d6efc 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -325,13 +325,13 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 	StorageAmRoutine *method = rel->rd_stamroutine;
 
 	/* Disable syncscan because we assume we scan from block zero upwards */
-	scan = heap_beginscan_strat(rel, SnapshotAny, 0, NULL, true, false);
+	scan = storage_beginscan_strat(rel, SnapshotAny, 0, NULL, true, false);
 	InitDirtySnapshot(SnapshotDirty);
 
 	nblocks = scan->rs_nblocks; /* # blocks to be scanned */
 
 	/* scan the relation */
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		CHECK_FOR_INTERRUPTS();
 
@@ -384,7 +384,7 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 		block++;
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	relation_close(rel, AccessShareLock);
 
 	stat.table_len = (uint64) nblocks * BLCKSZ;
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 84358748bd..4565e1c933 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -80,17 +80,6 @@
 /* GUC variable */
 bool		synchronize_seqscans = true;
 
-
-static HeapScanDesc heap_beginscan_internal(Relation relation,
-						Snapshot snapshot,
-						int nkeys, ScanKey key,
-						ParallelHeapScanDesc parallel_scan,
-						bool allow_strat,
-						bool allow_sync,
-						bool allow_pagemode,
-						bool is_bitmapscan,
-						bool is_samplescan,
-						bool temp_snap);
 static void heap_parallelscan_startblock_init(HeapScanDesc scan);
 static BlockNumber heap_parallelscan_nextpage(HeapScanDesc scan);
 static HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup,
@@ -1387,87 +1376,16 @@ heap_openrv_extended(const RangeVar *relation, LOCKMODE lockmode,
 	return r;
 }
 
-
-/* ----------------
- *		heap_beginscan	- begin relation scan
- *
- * heap_beginscan is the "standard" case.
- *
- * heap_beginscan_catalog differs in setting up its own temporary snapshot.
- *
- * heap_beginscan_strat offers an extended API that lets the caller control
- * whether a nondefault buffer access strategy can be used, and whether
- * syncscan can be chosen (possibly resulting in the scan not starting from
- * block zero).  Both of these default to true with plain heap_beginscan.
- *
- * heap_beginscan_bm is an alternative entry point for setting up a
- * HeapScanDesc for a bitmap heap scan.  Although that scan technology is
- * really quite unlike a standard seqscan, there is just enough commonality
- * to make it worth using the same data structure.
- *
- * heap_beginscan_sampling is an alternative entry point for setting up a
- * HeapScanDesc for a TABLESAMPLE scan.  As with bitmap scans, it's worth
- * using the same data structure although the behavior is rather different.
- * In addition to the options offered by heap_beginscan_strat, this call
- * also allows control of whether page-mode visibility checking is used.
- * ----------------
- */
 HeapScanDesc
 heap_beginscan(Relation relation, Snapshot snapshot,
-			   int nkeys, ScanKey key)
-{
-	return heap_beginscan_internal(relation, snapshot, nkeys, key, NULL,
-								   true, true, true, false, false, false);
-}
-
-HeapScanDesc
-heap_beginscan_catalog(Relation relation, int nkeys, ScanKey key)
-{
-	Oid			relid = RelationGetRelid(relation);
-	Snapshot	snapshot = RegisterSnapshot(GetCatalogSnapshot(relid));
-
-	return heap_beginscan_internal(relation, snapshot, nkeys, key, NULL,
-								   true, true, true, false, false, true);
-}
-
-HeapScanDesc
-heap_beginscan_strat(Relation relation, Snapshot snapshot,
-					 int nkeys, ScanKey key,
-					 bool allow_strat, bool allow_sync)
-{
-	return heap_beginscan_internal(relation, snapshot, nkeys, key, NULL,
-								   allow_strat, allow_sync, true,
-								   false, false, false);
-}
-
-HeapScanDesc
-heap_beginscan_bm(Relation relation, Snapshot snapshot,
-				  int nkeys, ScanKey key)
-{
-	return heap_beginscan_internal(relation, snapshot, nkeys, key, NULL,
-								   false, false, true, true, false, false);
-}
-
-HeapScanDesc
-heap_beginscan_sampling(Relation relation, Snapshot snapshot,
-						int nkeys, ScanKey key,
-						bool allow_strat, bool allow_sync, bool allow_pagemode)
-{
-	return heap_beginscan_internal(relation, snapshot, nkeys, key, NULL,
-								   allow_strat, allow_sync, allow_pagemode,
-								   false, true, false);
-}
-
-static HeapScanDesc
-heap_beginscan_internal(Relation relation, Snapshot snapshot,
-						int nkeys, ScanKey key,
-						ParallelHeapScanDesc parallel_scan,
-						bool allow_strat,
-						bool allow_sync,
-						bool allow_pagemode,
-						bool is_bitmapscan,
-						bool is_samplescan,
-						bool temp_snap)
+			   int nkeys, ScanKey key,
+			   ParallelHeapScanDesc parallel_scan,
+			   bool allow_strat,
+			   bool allow_sync,
+			   bool allow_pagemode,
+			   bool is_bitmapscan,
+			   bool is_samplescan,
+			   bool temp_snap)
 {
 	HeapScanDesc scan;
 
@@ -1537,9 +1455,16 @@ heap_beginscan_internal(Relation relation, Snapshot snapshot,
  * ----------------
  */
 void
-heap_rescan(HeapScanDesc scan,
-			ScanKey key)
+heap_rescan(HeapScanDesc scan, ScanKey key, bool set_params,
+			bool allow_strat, bool allow_sync, bool allow_pagemode)
 {
+	if (set_params)
+	{
+		scan->rs_allow_strat = allow_strat;
+		scan->rs_allow_sync = allow_sync;
+		scan->rs_pageatatime = allow_pagemode && IsMVCCSnapshot(scan->rs_snapshot);
+	}
+
 	/*
 	 * unpin scan buffers
 	 */
@@ -1550,27 +1475,21 @@ heap_rescan(HeapScanDesc scan,
 	 * reinitialize scan descriptor
 	 */
 	initscan(scan, key, true);
-}
 
-/* ----------------
- *		heap_rescan_set_params	- restart a relation scan after changing params
- *
- * This call allows changing the buffer strategy, syncscan, and pagemode
- * options before starting a fresh scan.  Note that although the actual use
- * of syncscan might change (effectively, enabling or disabling reporting),
- * the previously selected startblock will be kept.
- * ----------------
- */
-void
-heap_rescan_set_params(HeapScanDesc scan, ScanKey key,
-					   bool allow_strat, bool allow_sync, bool allow_pagemode)
-{
-	/* adjust parameters */
-	scan->rs_allow_strat = allow_strat;
-	scan->rs_allow_sync = allow_sync;
-	scan->rs_pageatatime = allow_pagemode && IsMVCCSnapshot(scan->rs_snapshot);
-	/* ... and rescan */
-	heap_rescan(scan, key);
+	/*
+	 * reset parallel scan, if present
+	 */
+	if (scan->rs_parallel != NULL)
+	{
+		ParallelHeapScanDesc parallel_scan;
+
+		/*
+		 * Caller is responsible for making sure that all workers have
+		 * finished the scan before calling this.
+		 */
+		parallel_scan = scan->rs_parallel;
+		pg_atomic_write_u64(&parallel_scan->phs_nallocated, 0);
+	}
 }
 
 /* ----------------
@@ -1659,25 +1578,6 @@ heap_parallelscan_reinitialize(ParallelHeapScanDesc parallel_scan)
 	pg_atomic_write_u64(&parallel_scan->phs_nallocated, 0);
 }
 
-/* ----------------
- *		heap_beginscan_parallel - join a parallel scan
- *
- *		Caller must hold a suitable lock on the correct relation.
- * ----------------
- */
-HeapScanDesc
-heap_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan)
-{
-	Snapshot	snapshot;
-
-	Assert(RelationGetRelid(relation) == parallel_scan->phs_relid);
-	snapshot = RestoreSnapshot(parallel_scan->phs_snapshot_data);
-	RegisterSnapshot(snapshot);
-
-	return heap_beginscan_internal(relation, snapshot, 0, NULL, parallel_scan,
-								   true, true, true, false, false, true);
-}
-
 /* ----------------
  *		heap_parallelscan_startblock_init - find and set the scan's startblock
  *
@@ -1822,8 +1722,7 @@ heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
 #define HEAPDEBUG_3
 #endif							/* !defined(HEAPDEBUGALL) */
 
-
-HeapTuple
+StorageTuple
 heap_getnext(HeapScanDesc scan, ScanDirection direction)
 {
 	/* Note: no locking manipulations needed */
@@ -1853,6 +1752,53 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 	return heap_copytuple(&(scan->rs_ctup));
 }
 
+#ifdef HEAPAMSLOTDEBUGALL
+#define HEAPAMSLOTDEBUG_1 \
+	elog(DEBUG2, "heapam_getnext([%s,nkeys=%d],dir=%d) called", \
+		 RelationGetRelationName(scan->rs_rd), scan->rs_nkeys, (int) direction)
+#define HEAPAMSLOTDEBUG_2 \
+	elog(DEBUG2, "heapam_getnext returning EOS")
+#define HEAPAMSLOTDEBUG_3 \
+	elog(DEBUG2, "heapam_getnext returning tuple")
+#else
+#define HEAPAMSLOTDEBUG_1
+#define HEAPAMSLOTDEBUG_2
+#define HEAPAMSLOTDEBUG_3
+#endif
+
+TupleTableSlot *
+heap_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
+{
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
+	/* Note: no locking manipulations needed */
+
+	HEAPAMSLOTDEBUG_1;			/* heap_getnext( info ) */
+
+	if (scan->rs_pageatatime)
+		heapgettup_pagemode(scan, direction,
+							scan->rs_nkeys, scan->rs_key);
+	else
+		heapgettup(scan, direction, scan->rs_nkeys, scan->rs_key);
+
+	if (scan->rs_ctup.t_data == NULL)
+	{
+		HEAPAMSLOTDEBUG_2;		/* heap_getnext returning EOS */
+		ExecClearTuple(slot);
+		return slot;
+	}
+
+	/*
+	 * if we get here it means we have a new current scan tuple, so point to
+	 * the proper return buffer and return the tuple.
+	 */
+	HEAPAMSLOTDEBUG_3;			/* heap_getnext returning tuple */
+
+	pgstat_count_heap_getnext(scan->rs_rd);
+	return ExecStoreTuple(heap_copytuple(&(scan->rs_ctup)),
+						  slot, InvalidBuffer, true);
+}
+
 /*
  *	heap_fetch		- retrieve tuple with given tid
  *
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index 77fdf7f91a..4ee3def639 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -148,8 +148,6 @@ heapam_heap_delete(Relation relation, ItemPointer tid, CommandId cid,
 	return heap_delete(relation, tid, cid, crosscheck, wait, hufd);
 }
 
-
-
 static HTSU_Result
 heapam_heap_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 				   EState *estate, CommandId cid, Snapshot crosscheck,
@@ -255,6 +253,15 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 
 	amroutine->slot_storageam = heapam_storage_slot_handler;
 
+	amroutine->scan_begin = heap_beginscan;
+	amroutine->scansetlimits = heap_setscanlimits;
+	amroutine->scan_getnext = heap_getnext;
+	amroutine->scan_getnextslot = heap_getnextslot;
+	amroutine->scan_end = heap_endscan;
+	amroutine->scan_rescan = heap_rescan;
+	amroutine->scan_update_snapshot = heap_update_snapshot;
+	amroutine->hot_search_buffer = heap_hot_search_buffer;
+
 	amroutine->tuple_fetch = heapam_fetch;
 	amroutine->tuple_insert = heapam_heap_insert;
 	amroutine->tuple_delete = heapam_heap_delete;
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 01321a2543..26a9ccb657 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -20,6 +20,7 @@
 #include "postgres.h"
 
 #include "access/relscan.h"
+#include "access/storageam.h"
 #include "access/transam.h"
 #include "catalog/index.h"
 #include "lib/stringinfo.h"
@@ -394,9 +395,9 @@ systable_beginscan(Relation heapRelation,
 		 * disadvantage; and there are no compensating advantages, because
 		 * it's unlikely that such scans will occur in parallel.
 		 */
-		sysscan->scan = heap_beginscan_strat(heapRelation, snapshot,
-											 nkeys, key,
-											 true, false);
+		sysscan->scan = storage_beginscan_strat(heapRelation, snapshot,
+												nkeys, key,
+												true, false);
 		sysscan->iscan = NULL;
 	}
 
@@ -432,7 +433,7 @@ systable_getnext(SysScanDesc sysscan)
 			elog(ERROR, "system catalog scans with lossy index conditions are not implemented");
 	}
 	else
-		htup = heap_getnext(sysscan->scan, ForwardScanDirection);
+		htup = storage_getnext(sysscan->scan, ForwardScanDirection);
 
 	return htup;
 }
@@ -504,7 +505,7 @@ systable_endscan(SysScanDesc sysscan)
 		index_close(sysscan->irel, AccessShareLock);
 	}
 	else
-		heap_endscan(sysscan->scan);
+		storage_endscan(sysscan->scan);
 
 	if (sysscan->snapshot)
 		UnregisterSnapshot(sysscan->snapshot);
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index edf4172eb2..0afc00625b 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -71,6 +71,7 @@
 
 #include "access/amapi.h"
 #include "access/relscan.h"
+#include "access/storageam.h"
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "catalog/catalog.h"
@@ -605,12 +606,12 @@ index_fetch_heap(IndexScanDesc scan)
 
 	/* Obtain share-lock on the buffer so we can examine visibility */
 	LockBuffer(scan->xs_cbuf, BUFFER_LOCK_SHARE);
-	got_heap_tuple = heap_hot_search_buffer(tid, scan->heapRelation,
-											scan->xs_cbuf,
-											scan->xs_snapshot,
-											&scan->xs_ctup,
-											&all_dead,
-											!scan->xs_continue_hot);
+	got_heap_tuple = storage_hot_search_buffer(tid, scan->heapRelation,
+											   scan->xs_cbuf,
+											   scan->xs_snapshot,
+											   &scan->xs_ctup,
+											   &all_dead,
+											   !scan->xs_continue_hot);
 	LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK);
 
 	if (got_heap_tuple)
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index 310589da4e..0368a10dbc 100644
--- a/src/backend/access/nbtree/nbtinsert.c
+++ b/src/backend/access/nbtree/nbtinsert.c
@@ -18,6 +18,7 @@
 #include "access/heapam.h"
 #include "access/nbtree.h"
 #include "access/nbtxlog.h"
+#include "access/storageam.h"
 #include "access/transam.h"
 #include "access/xloginsert.h"
 #include "miscadmin.h"
@@ -325,8 +326,8 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
 				 * that satisfies SnapshotDirty.  This is necessary because we
 				 * have just a single index entry for the entire chain.
 				 */
-				else if (heap_hot_search(&htid, heapRel, &SnapshotDirty,
-										 &all_dead))
+				else if (storage_hot_search(&htid, heapRel, &SnapshotDirty,
+											&all_dead))
 				{
 					TransactionId xwait;
 
@@ -379,7 +380,7 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
 					 * entry.
 					 */
 					htid = itup->t_tid;
-					if (heap_hot_search(&htid, heapRel, SnapshotSelf, NULL))
+					if (storage_hot_search(&htid, heapRel, SnapshotSelf, NULL))
 					{
 						/* Normal case --- it's still live */
 					}
diff --git a/src/backend/access/storage/storageam.c b/src/backend/access/storage/storageam.c
index 487ffc9746..c326a54f70 100644
--- a/src/backend/access/storage/storageam.c
+++ b/src/backend/access/storage/storageam.c
@@ -16,7 +16,9 @@
 
 #include "access/storageam.h"
 #include "access/storageamapi.h"
+#include "access/relscan.h"
 #include "utils/rel.h"
+#include "utils/tqual.h"
 
 /*
  *	storage_fetch		- retrieve tuple with given tid
@@ -48,6 +50,174 @@ storage_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * stuple,
 												follow_updates, buffer, hufd);
 }
 
+/* ----------------
+ *		heap_beginscan_parallel - join a parallel scan
+ *
+ *		Caller must hold a suitable lock on the correct relation.
+ * ----------------
+ */
+HeapScanDesc
+storage_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan)
+{
+	Snapshot	snapshot;
+
+	Assert(RelationGetRelid(relation) == parallel_scan->phs_relid);
+	snapshot = RestoreSnapshot(parallel_scan->phs_snapshot_data);
+	RegisterSnapshot(snapshot);
+
+	return relation->rd_stamroutine->scan_begin(relation, snapshot, 0, NULL, parallel_scan,
+												true, true, true, false, false, true);
+}
+
+/*
+ * heap_setscanlimits - restrict range of a heapscan
+ *
+ * startBlk is the page to start at
+ * numBlks is number of pages to scan (InvalidBlockNumber means "all")
+ */
+void
+storage_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
+{
+	sscan->rs_rd->rd_stamroutine->scansetlimits(sscan, startBlk, numBlks);
+}
+
+
+/* ----------------
+ *		heap_beginscan	- begin relation scan
+ *
+ * heap_beginscan is the "standard" case.
+ *
+ * heap_beginscan_catalog differs in setting up its own temporary snapshot.
+ *
+ * heap_beginscan_strat offers an extended API that lets the caller control
+ * whether a nondefault buffer access strategy can be used, and whether
+ * syncscan can be chosen (possibly resulting in the scan not starting from
+ * block zero).  Both of these default to true with plain heap_beginscan.
+ *
+ * heap_beginscan_bm is an alternative entry point for setting up a
+ * HeapScanDesc for a bitmap heap scan.  Although that scan technology is
+ * really quite unlike a standard seqscan, there is just enough commonality
+ * to make it worth using the same data structure.
+ *
+ * heap_beginscan_sampling is an alternative entry point for setting up a
+ * HeapScanDesc for a TABLESAMPLE scan.  As with bitmap scans, it's worth
+ * using the same data structure although the behavior is rather different.
+ * In addition to the options offered by heap_beginscan_strat, this call
+ * also allows control of whether page-mode visibility checking is used.
+ * ----------------
+ */
+HeapScanDesc
+storage_beginscan(Relation relation, Snapshot snapshot,
+				  int nkeys, ScanKey key)
+{
+	return relation->rd_stamroutine->scan_begin(relation, snapshot, nkeys, key, NULL,
+												true, true, true, false, false, false);
+}
+
+HeapScanDesc
+storage_beginscan_catalog(Relation relation, int nkeys, ScanKey key)
+{
+	Oid			relid = RelationGetRelid(relation);
+	Snapshot	snapshot = RegisterSnapshot(GetCatalogSnapshot(relid));
+
+	return relation->rd_stamroutine->scan_begin(relation, snapshot, nkeys, key, NULL,
+												true, true, true, false, false, true);
+}
+
+HeapScanDesc
+storage_beginscan_strat(Relation relation, Snapshot snapshot,
+						int nkeys, ScanKey key,
+						bool allow_strat, bool allow_sync)
+{
+	return relation->rd_stamroutine->scan_begin(relation, snapshot, nkeys, key, NULL,
+												allow_strat, allow_sync, true,
+												false, false, false);
+}
+
+HeapScanDesc
+storage_beginscan_bm(Relation relation, Snapshot snapshot,
+					 int nkeys, ScanKey key)
+{
+	return relation->rd_stamroutine->scan_begin(relation, snapshot, nkeys, key, NULL,
+												false, false, true, true, false, false);
+}
+
+HeapScanDesc
+storage_beginscan_sampling(Relation relation, Snapshot snapshot,
+						   int nkeys, ScanKey key,
+						   bool allow_strat, bool allow_sync, bool allow_pagemode)
+{
+	return relation->rd_stamroutine->scan_begin(relation, snapshot, nkeys, key, NULL,
+												allow_strat, allow_sync, allow_pagemode,
+												false, true, false);
+}
+
+/* ----------------
+ *		heap_rescan		- restart a relation scan
+ * ----------------
+ */
+void
+storage_rescan(HeapScanDesc scan,
+			   ScanKey key)
+{
+	scan->rs_rd->rd_stamroutine->scan_rescan(scan, key, false, false, false, false);
+}
+
+/* ----------------
+ *		heap_rescan_set_params	- restart a relation scan after changing params
+ *
+ * This call allows changing the buffer strategy, syncscan, and pagemode
+ * options before starting a fresh scan.  Note that although the actual use
+ * of syncscan might change (effectively, enabling or disabling reporting),
+ * the previously selected startblock will be kept.
+ * ----------------
+ */
+void
+storage_rescan_set_params(HeapScanDesc scan, ScanKey key,
+						  bool allow_strat, bool allow_sync, bool allow_pagemode)
+{
+	scan->rs_rd->rd_stamroutine->scan_rescan(scan, key, true,
+											 allow_strat, allow_sync, (allow_pagemode && IsMVCCSnapshot(scan->rs_snapshot)));
+}
+
+/* ----------------
+ *		heap_endscan	- end relation scan
+ *
+ *		See how to integrate with index scans.
+ *		Check handling if reldesc caching.
+ * ----------------
+ */
+void
+storage_endscan(HeapScanDesc scan)
+{
+	scan->rs_rd->rd_stamroutine->scan_end(scan);
+}
+
+
+/* ----------------
+ *		heap_update_snapshot
+ *
+ *		Update snapshot info in heap scan descriptor.
+ * ----------------
+ */
+void
+storage_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
+{
+	scan->rs_rd->rd_stamroutine->scan_update_snapshot(scan, snapshot);
+}
+
+StorageTuple
+storage_getnext(HeapScanDesc sscan, ScanDirection direction)
+{
+	return sscan->rs_rd->rd_stamroutine->scan_getnext(sscan, direction);
+}
+
+TupleTableSlot *
+storage_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
+{
+	return sscan->rs_rd->rd_stamroutine->scan_getnextslot(sscan, direction, slot);
+}
+
 /*
  * Insert a tuple from a slot into storage AM routine
  */
@@ -87,6 +257,59 @@ storage_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 												  lockmode, IndexFunc, recheckIndexes);
 }
 
+/*
+ *	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
+ * of a HOT chain), and buffer is the buffer holding this tuple.  We search
+ * for the first chain member satisfying the given snapshot.  If one is
+ * found, we update *tid to reference that tuple's offset number, and
+ * return true.  If no match, return false without modifying *tid.
+ *
+ * heapTuple is a caller-supplied buffer.  When a match is found, we return
+ * the tuple here, in addition to updating *tid.  If no match is found, the
+ * contents of this buffer on return are undefined.
+ *
+ * If all_dead is not NULL, we check non-visible tuples to see if they are
+ * globally dead; *all_dead is set true if all members of the HOT chain
+ * are vacuumable, false if not.
+ *
+ * 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.
+ */
+bool
+storage_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
+						  Snapshot snapshot, HeapTuple heapTuple,
+						  bool *all_dead, bool first_call)
+{
+	return relation->rd_stamroutine->hot_search_buffer(tid, relation, buffer,
+													   snapshot, heapTuple, all_dead, first_call);
+}
+
+/*
+ *	heap_hot_search		- search HOT chain for tuple satisfying snapshot
+ *
+ * This has the same API as heap_hot_search_buffer, except that the caller
+ * does not provide the buffer containing the page, rather we access it
+ * locally.
+ */
+bool
+storage_hot_search(ItemPointer tid, Relation relation, Snapshot snapshot,
+				   bool *all_dead)
+{
+	bool		result;
+	Buffer		buffer;
+	HeapTupleData heapTuple;
+
+	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
+	LockBuffer(buffer, BUFFER_LOCK_SHARE);
+	result = relation->rd_stamroutine->hot_search_buffer(tid, relation, buffer,
+														 snapshot, &heapTuple, all_dead, true);
+	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+	ReleaseBuffer(buffer);
+	return result;
+}
 
 /*
  *	storage_multi_insert	- insert multiple tuple into a storage
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 8287de97a2..a73a363a49 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -18,6 +18,7 @@
 #include <signal.h>
 
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/xact.h"
 #include "access/xlog_internal.h"
 #include "bootstrap/bootstrap.h"
@@ -586,18 +587,18 @@ boot_openrel(char *relname)
 	{
 		/* We can now load the pg_type data */
 		rel = heap_open(TypeRelationId, NoLock);
-		scan = heap_beginscan_catalog(rel, 0, NULL);
+		scan = storage_beginscan_catalog(rel, 0, NULL);
 		i = 0;
-		while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tup = storage_getnext(scan, ForwardScanDirection)) != NULL)
 			++i;
-		heap_endscan(scan);
+		storage_endscan(scan);
 		app = Typ = ALLOC(struct typmap *, i + 1);
 		while (i-- > 0)
 			*app++ = ALLOC(struct typmap, 1);
 		*app = NULL;
-		scan = heap_beginscan_catalog(rel, 0, NULL);
+		scan = storage_beginscan_catalog(rel, 0, NULL);
 		app = Typ;
-		while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tup = storage_getnext(scan, ForwardScanDirection)) != NULL)
 		{
 			(*app)->am_oid = HeapTupleGetOid(tup);
 			memcpy((char *) &(*app)->am_typ,
@@ -605,7 +606,7 @@ boot_openrel(char *relname)
 				   sizeof((*app)->am_typ));
 			app++;
 		}
-		heap_endscan(scan);
+		storage_endscan(scan);
 		heap_close(rel, NoLock);
 	}
 
@@ -916,25 +917,25 @@ gettype(char *type)
 		}
 		elog(DEBUG4, "external type: %s", type);
 		rel = heap_open(TypeRelationId, NoLock);
-		scan = heap_beginscan_catalog(rel, 0, NULL);
+		scan = storage_beginscan_catalog(rel, 0, NULL);
 		i = 0;
-		while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tup = storage_getnext(scan, ForwardScanDirection)) != NULL)
 			++i;
-		heap_endscan(scan);
+		storage_endscan(scan);
 		app = Typ = ALLOC(struct typmap *, i + 1);
 		while (i-- > 0)
 			*app++ = ALLOC(struct typmap, 1);
 		*app = NULL;
-		scan = heap_beginscan_catalog(rel, 0, NULL);
+		scan = storage_beginscan_catalog(rel, 0, NULL);
 		app = Typ;
-		while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tup = storage_getnext(scan, ForwardScanDirection)) != NULL)
 		{
 			(*app)->am_oid = HeapTupleGetOid(tup);
 			memmove((char *) &(*app++)->am_typ,
 					(char *) GETSTRUCT(tup),
 					sizeof((*app)->am_typ));
 		}
-		heap_endscan(scan);
+		storage_endscan(scan);
 		heap_close(rel, NoLock);
 		return gettype(type);
 	}
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index e481cf3d11..0b3be57a88 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -20,6 +20,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "catalog/binary_upgrade.h"
@@ -847,14 +848,14 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 									InvalidOid);
 
 					rel = heap_open(ProcedureRelationId, AccessShareLock);
-					scan = heap_beginscan_catalog(rel, keycount, key);
+					scan = storage_beginscan_catalog(rel, keycount, key);
 
-					while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+					while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 					{
 						objects = lappend_oid(objects, HeapTupleGetOid(tuple));
 					}
 
-					heap_endscan(scan);
+					storage_endscan(scan);
 					heap_close(rel, AccessShareLock);
 				}
 				break;
@@ -892,14 +893,14 @@ getRelationsInNamespace(Oid namespaceId, char relkind)
 				CharGetDatum(relkind));
 
 	rel = heap_open(RelationRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(rel, 2, key);
+	scan = storage_beginscan_catalog(rel, 2, key);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		relations = lappend_oid(relations, HeapTupleGetOid(tuple));
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(rel, AccessShareLock);
 
 	return relations;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index ead8d2abdf..16bb70ef45 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -26,6 +26,7 @@
 #include "access/amapi.h"
 #include "access/multixact.h"
 #include "access/relscan.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/transam.h"
 #include "access/visibilitymap.h"
@@ -1907,10 +1908,10 @@ index_update_stats(Relation rel,
 					BTEqualStrategyNumber, F_OIDEQ,
 					ObjectIdGetDatum(relid));
 
-		pg_class_scan = heap_beginscan_catalog(pg_class, 1, key);
-		tuple = heap_getnext(pg_class_scan, ForwardScanDirection);
+		pg_class_scan = storage_beginscan_catalog(pg_class, 1, key);
+		tuple = storage_getnext(pg_class_scan, ForwardScanDirection);
 		tuple = heap_copytuple(tuple);
-		heap_endscan(pg_class_scan);
+		storage_endscan(pg_class_scan);
 	}
 	else
 	{
@@ -2282,16 +2283,16 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	}
 
 	method = heapRelation->rd_stamroutine;
-	scan = heap_beginscan_strat(heapRelation,	/* relation */
-								snapshot,	/* snapshot */
-								0,	/* number of keys */
-								NULL,	/* scan key */
-								true,	/* buffer access strategy OK */
-								allow_sync);	/* syncscan OK? */
+	scan = storage_beginscan_strat(heapRelation,	/* relation */
+								   snapshot,	/* snapshot */
+								   0,	/* number of keys */
+								   NULL,	/* scan key */
+								   true,	/* buffer access strategy OK */
+								   allow_sync); /* syncscan OK? */
 
 	/* set our scan endpoints */
 	if (!allow_sync)
-		heap_setscanlimits(scan, start_blockno, numblocks);
+		storage_setscanlimits(scan, start_blockno, numblocks);
 	else
 	{
 		/* syncscan can only be requested on whole relation */
@@ -2304,7 +2305,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	/*
 	 * Scan all tuples in the base relation.
 	 */
-	while ((heapTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((heapTuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		bool		tupleIsAlive;
 
@@ -2616,7 +2617,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 		}
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	/* we can now forget our snapshot, if set */
 	if (IsBootstrapProcessingMode() || indexInfo->ii_Concurrent)
@@ -2687,14 +2688,14 @@ IndexCheckExclusion(Relation heapRelation,
 	 * Scan all live tuples in the base relation.
 	 */
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	scan = heap_beginscan_strat(heapRelation,	/* relation */
-								snapshot,	/* snapshot */
-								0,	/* number of keys */
-								NULL,	/* scan key */
-								true,	/* buffer access strategy OK */
-								true);	/* syncscan OK */
-
-	while ((heapTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	scan = storage_beginscan_strat(heapRelation,	/* relation */
+								   snapshot,	/* snapshot */
+								   0,	/* number of keys */
+								   NULL,	/* scan key */
+								   true,	/* buffer access strategy OK */
+								   true);	/* syncscan OK */
+
+	while ((heapTuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		CHECK_FOR_INTERRUPTS();
 
@@ -2730,7 +2731,7 @@ IndexCheckExclusion(Relation heapRelation,
 								   estate, true);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 
 	ExecDropSingleTupleTableSlot(slot);
@@ -3007,17 +3008,17 @@ validate_index_heapscan(Relation heapRelation,
 	 * here, because it's critical that we read from block zero forward to
 	 * match the sorted TIDs.
 	 */
-	scan = heap_beginscan_strat(heapRelation,	/* relation */
-								snapshot,	/* snapshot */
-								0,	/* number of keys */
-								NULL,	/* scan key */
-								true,	/* buffer access strategy OK */
-								false); /* syncscan not OK */
+	scan = storage_beginscan_strat(heapRelation,	/* relation */
+								   snapshot,	/* snapshot */
+								   0,	/* number of keys */
+								   NULL,	/* scan key */
+								   true,	/* buffer access strategy OK */
+								   false);	/* syncscan not OK */
 
 	/*
 	 * Scan all tuples matching the snapshot.
 	 */
-	while ((heapTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((heapTuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		ItemPointer heapcursor = &heapTuple->t_self;
 		ItemPointerData rootTuple;
@@ -3174,7 +3175,7 @@ validate_index_heapscan(Relation heapRelation,
 		}
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	ExecDropSingleTupleTableSlot(slot);
 
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index ef156e449e..d6f1a9cad5 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -19,6 +19,7 @@
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/nbtree.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
@@ -1323,7 +1324,7 @@ check_default_allows_bound(Relation parent, Relation default_rel,
 
 		econtext = GetPerTupleExprContext(estate);
 		snapshot = RegisterSnapshot(GetLatestSnapshot());
-		scan = heap_beginscan(part_rel, snapshot, 0, NULL);
+		scan = storage_beginscan(part_rel, snapshot, 0, NULL);
 		tupslot = MakeSingleTupleTableSlot(tupdesc);
 
 		/*
@@ -1332,7 +1333,7 @@ check_default_allows_bound(Relation parent, Relation default_rel,
 		 */
 		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
 
-		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 		{
 			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
 			econtext->ecxt_scantuple = tupslot;
@@ -1348,7 +1349,7 @@ check_default_allows_bound(Relation parent, Relation default_rel,
 		}
 
 		MemoryContextSwitchTo(oldCxt);
-		heap_endscan(scan);
+		storage_endscan(scan);
 		UnregisterSnapshot(snapshot);
 		ExecDropSingleTupleTableSlot(tupslot);
 		FreeExecutorState(estate);
diff --git a/src/backend/catalog/pg_conversion.c b/src/backend/catalog/pg_conversion.c
index 5746dc349a..1d048e6394 100644
--- a/src/backend/catalog/pg_conversion.c
+++ b/src/backend/catalog/pg_conversion.c
@@ -16,6 +16,7 @@
 
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
@@ -161,14 +162,14 @@ RemoveConversionById(Oid conversionOid)
 	/* open pg_conversion */
 	rel = heap_open(ConversionRelationId, RowExclusiveLock);
 
-	scan = heap_beginscan_catalog(rel, 1, &scanKeyData);
+	scan = storage_beginscan_catalog(rel, 1, &scanKeyData);
 
 	/* search for the target tuple */
-	if (HeapTupleIsValid(tuple = heap_getnext(scan, ForwardScanDirection)))
+	if (HeapTupleIsValid(tuple = storage_getnext(scan, ForwardScanDirection)))
 		CatalogTupleDelete(rel, &tuple->t_self);
 	else
 		elog(ERROR, "could not find tuple for conversion %u", conversionOid);
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(rel, RowExclusiveLock);
 }
 
diff --git a/src/backend/catalog/pg_db_role_setting.c b/src/backend/catalog/pg_db_role_setting.c
index 323471bc83..517e3101cd 100644
--- a/src/backend/catalog/pg_db_role_setting.c
+++ b/src/backend/catalog/pg_db_role_setting.c
@@ -13,6 +13,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_db_role_setting.h"
@@ -196,12 +197,12 @@ DropSetting(Oid databaseid, Oid roleid)
 		numkeys++;
 	}
 
-	scan = heap_beginscan_catalog(relsetting, numkeys, keys);
-	while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+	scan = storage_beginscan_catalog(relsetting, numkeys, keys);
+	while (HeapTupleIsValid(tup = storage_getnext(scan, ForwardScanDirection)))
 	{
 		CatalogTupleDelete(relsetting, &tup->t_self);
 	}
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	heap_close(relsetting, RowExclusiveLock);
 }
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 3ef7ba8cd5..145e3c1d65 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -21,6 +21,7 @@
 #include "access/hash.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/xact.h"
 
 #include "catalog/catalog.h"
@@ -324,9 +325,9 @@ GetAllTablesPublicationRelations(void)
 				BTEqualStrategyNumber, F_CHAREQ,
 				CharGetDatum(RELKIND_RELATION));
 
-	scan = heap_beginscan_catalog(classRel, 1, key);
+	scan = storage_beginscan_catalog(classRel, 1, key);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Oid			relid = HeapTupleGetOid(tuple);
 		Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
@@ -335,7 +336,7 @@ GetAllTablesPublicationRelations(void)
 			result = lappend_oid(result, relid);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(classRel, AccessShareLock);
 
 	return result;
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index fb53d71cd6..a51f2e4dfc 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -19,6 +19,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/xact.h"
 
 #include "catalog/indexing.h"
@@ -402,12 +403,12 @@ RemoveSubscriptionRel(Oid subid, Oid relid)
 	}
 
 	/* Do the search and delete what we found. */
-	scan = heap_beginscan_catalog(rel, nkeys, skey);
-	while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+	scan = storage_beginscan_catalog(rel, nkeys, skey);
+	while (HeapTupleIsValid(tup = storage_getnext(scan, ForwardScanDirection)))
 	{
 		CatalogTupleDelete(rel, &tup->t_self);
 	}
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	heap_close(rel, RowExclusiveLock);
 }
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index dbcc5bc172..e0f6973a3f 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -21,6 +21,7 @@
 #include "access/multixact.h"
 #include "access/relscan.h"
 #include "access/rewriteheap.h"
+#include "access/storageam.h"
 #include "access/storageamapi.h"
 #include "access/transam.h"
 #include "access/tuptoaster.h"
@@ -909,7 +910,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	}
 	else
 	{
-		heapScan = heap_beginscan(OldHeap, SnapshotAny, 0, (ScanKey) NULL);
+		heapScan = storage_beginscan(OldHeap, SnapshotAny, 0, (ScanKey) NULL);
 		indexScan = NULL;
 	}
 
@@ -959,7 +960,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 		}
 		else
 		{
-			tuple = heap_getnext(heapScan, ForwardScanDirection);
+			tuple = storage_getnext(heapScan, ForwardScanDirection);
 			if (tuple == NULL)
 				break;
 
@@ -1045,7 +1046,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	if (indexScan != NULL)
 		index_endscan(indexScan);
 	if (heapScan != NULL)
-		heap_endscan(heapScan);
+		storage_endscan(heapScan);
 
 	/*
 	 * In scan-and-sort mode, complete the sort, then read out all live tuples
@@ -1656,8 +1657,8 @@ get_tables_to_cluster(MemoryContext cluster_context)
 				Anum_pg_index_indisclustered,
 				BTEqualStrategyNumber, F_BOOLEQ,
 				BoolGetDatum(true));
-	scan = heap_beginscan_catalog(indRelation, 1, &entry);
-	while ((indexTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	scan = storage_beginscan_catalog(indRelation, 1, &entry);
+	while ((indexTuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		index = (Form_pg_index) GETSTRUCT(indexTuple);
 
@@ -1677,7 +1678,7 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 		MemoryContextSwitchTo(old_context);
 	}
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	relation_close(indRelation, AccessShareLock);
 
diff --git a/src/backend/commands/constraint.c b/src/backend/commands/constraint.c
index e2544e51ed..6727d154e1 100644
--- a/src/backend/commands/constraint.c
+++ b/src/backend/commands/constraint.c
@@ -13,6 +13,7 @@
  */
 #include "postgres.h"
 
+#include "access/storageam.h"
 #include "catalog/index.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
@@ -102,7 +103,7 @@ unique_key_recheck(PG_FUNCTION_ARGS)
 	 * removed.
 	 */
 	tmptid = new_row->t_self;
-	if (!heap_hot_search(&tmptid, trigdata->tg_relation, SnapshotSelf, NULL))
+	if (!storage_hot_search(&tmptid, trigdata->tg_relation, SnapshotSelf, NULL))
 	{
 		/*
 		 * 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 62d1ac98d5..f2209d9f81 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2038,10 +2038,10 @@ CopyTo(CopyState cstate)
 		values = (Datum *) palloc(num_phys_attrs * sizeof(Datum));
 		nulls = (bool *) palloc(num_phys_attrs * sizeof(bool));
 
-		scandesc = heap_beginscan(cstate->rel, GetActiveSnapshot(), 0, NULL);
+		scandesc = storage_beginscan(cstate->rel, GetActiveSnapshot(), 0, NULL);
 
 		processed = 0;
-		while ((tuple = heap_getnext(scandesc, ForwardScanDirection)) != NULL)
+		while ((tuple = storage_getnext(scandesc, ForwardScanDirection)) != NULL)
 		{
 			CHECK_FOR_INTERRUPTS();
 
@@ -2053,7 +2053,7 @@ CopyTo(CopyState cstate)
 			processed++;
 		}
 
-		heap_endscan(scandesc);
+		storage_endscan(scandesc);
 
 		pfree(values);
 		pfree(nulls);
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index eb1a4695c0..87d651002e 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -26,6 +26,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -590,8 +591,8 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 		 * each one to the new database.
 		 */
 		rel = heap_open(TableSpaceRelationId, AccessShareLock);
-		scan = heap_beginscan_catalog(rel, 0, NULL);
-		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		scan = storage_beginscan_catalog(rel, 0, NULL);
+		while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 		{
 			Oid			srctablespace = HeapTupleGetOid(tuple);
 			Oid			dsttablespace;
@@ -643,7 +644,7 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 								  XLOG_DBASE_CREATE | XLR_SPECIAL_REL_UPDATE);
 			}
 		}
-		heap_endscan(scan);
+		storage_endscan(scan);
 		heap_close(rel, AccessShareLock);
 
 		/*
@@ -1875,8 +1876,8 @@ remove_dbtablespaces(Oid db_id)
 	HeapTuple	tuple;
 
 	rel = heap_open(TableSpaceRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(rel, 0, NULL);
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	scan = storage_beginscan_catalog(rel, 0, NULL);
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Oid			dsttablespace = HeapTupleGetOid(tuple);
 		char	   *dstpath;
@@ -1917,7 +1918,7 @@ remove_dbtablespaces(Oid db_id)
 		pfree(dstpath);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(rel, AccessShareLock);
 }
 
@@ -1942,8 +1943,8 @@ check_db_file_conflict(Oid db_id)
 	HeapTuple	tuple;
 
 	rel = heap_open(TableSpaceRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(rel, 0, NULL);
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	scan = storage_beginscan_catalog(rel, 0, NULL);
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Oid			dsttablespace = HeapTupleGetOid(tuple);
 		char	   *dstpath;
@@ -1966,7 +1967,7 @@ check_db_file_conflict(Oid db_id)
 		pfree(dstpath);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(rel, AccessShareLock);
 
 	return result;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 97091dd9fb..400f936a2f 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -18,6 +18,7 @@
 #include "access/amapi.h"
 #include "access/htup_details.h"
 #include "access/reloptions.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
@@ -1964,8 +1965,8 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 	 * rels will be processed indirectly by reindex_relation).
 	 */
 	relationRelation = heap_open(RelationRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(relationRelation, num_keys, scan_keys);
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	scan = storage_beginscan_catalog(relationRelation, num_keys, scan_keys);
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Form_pg_class classtuple = (Form_pg_class) GETSTRUCT(tuple);
 		Oid			relid = HeapTupleGetOid(tuple);
@@ -2005,7 +2006,7 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 
 		MemoryContextSwitchTo(old);
 	}
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(relationRelation, AccessShareLock);
 
 	/* Now reindex each rel in a separate transaction */
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 73b68826f2..3d4fa831b3 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4551,7 +4551,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		 * checking all the constraints.
 		 */
 		snapshot = RegisterSnapshot(GetLatestSnapshot());
-		scan = heap_beginscan(oldrel, snapshot, 0, NULL);
+		scan = storage_beginscan(oldrel, snapshot, 0, NULL);
 
 		/*
 		 * Switch to per-tuple memory context and reset it for each tuple
@@ -4559,7 +4559,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		 */
 		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
 
-		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 		{
 			if (tab->rewrite > 0)
 			{
@@ -4673,7 +4673,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 
 		MemoryContextSwitchTo(oldCxt);
-		heap_endscan(scan);
+		storage_endscan(scan);
 		UnregisterSnapshot(snapshot);
 
 		ExecDropSingleTupleTableSlot(oldslot);
@@ -5076,9 +5076,9 @@ find_typed_table_dependencies(Oid typeOid, const char *typeName, DropBehavior be
 				BTEqualStrategyNumber, F_OIDEQ,
 				ObjectIdGetDatum(typeOid));
 
-	scan = heap_beginscan_catalog(classRel, 1, key);
+	scan = storage_beginscan_catalog(classRel, 1, key);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		if (behavior == DROP_RESTRICT)
 			ereport(ERROR,
@@ -5090,7 +5090,7 @@ find_typed_table_dependencies(Oid typeOid, const char *typeName, DropBehavior be
 			result = lappend_oid(result, HeapTupleGetOid(tuple));
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(classRel, AccessShareLock);
 
 	return result;
@@ -8260,7 +8260,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	econtext->ecxt_scantuple = slot;
 
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	scan = heap_beginscan(rel, snapshot, 0, NULL);
+	scan = storage_beginscan(rel, snapshot, 0, NULL);
 
 	/*
 	 * Switch to per-tuple memory context and reset it for each tuple
@@ -8268,7 +8268,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	 */
 	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
@@ -8283,7 +8283,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	}
 
 	MemoryContextSwitchTo(oldcxt);
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 	ExecDropSingleTupleTableSlot(slot);
 	FreeExecutorState(estate);
@@ -8338,9 +8338,9 @@ validateForeignKeyConstraint(char *conname,
 	 * ereport(ERROR) and that's that.
 	 */
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	scan = heap_beginscan(rel, snapshot, 0, NULL);
+	scan = storage_beginscan(rel, snapshot, 0, NULL);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		FunctionCallInfoData fcinfo;
 		TriggerData trigdata;
@@ -8369,7 +8369,7 @@ validateForeignKeyConstraint(char *conname,
 		RI_FKey_check_ins(&fcinfo);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 }
 
@@ -10874,8 +10874,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 				ObjectIdGetDatum(orig_tablespaceoid));
 
 	rel = heap_open(RelationRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(rel, 1, key);
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	scan = storage_beginscan_catalog(rel, 1, key);
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Oid			relOid = HeapTupleGetOid(tuple);
 		Form_pg_class relForm;
@@ -10934,7 +10934,7 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 		relations = lappend_oid(relations, relOid);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(rel, AccessShareLock);
 
 	if (relations == NIL)
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index d574e4dd00..6804182db7 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -53,6 +53,7 @@
 #include "access/heapam.h"
 #include "access/reloptions.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "access/xlog.h"
@@ -416,8 +417,8 @@ DropTableSpace(DropTableSpaceStmt *stmt)
 				Anum_pg_tablespace_spcname,
 				BTEqualStrategyNumber, F_NAMEEQ,
 				CStringGetDatum(tablespacename));
-	scandesc = heap_beginscan_catalog(rel, 1, entry);
-	tuple = heap_getnext(scandesc, ForwardScanDirection);
+	scandesc = storage_beginscan_catalog(rel, 1, entry);
+	tuple = storage_getnext(scandesc, ForwardScanDirection);
 
 	if (!HeapTupleIsValid(tuple))
 	{
@@ -434,7 +435,7 @@ DropTableSpace(DropTableSpaceStmt *stmt)
 					(errmsg("tablespace \"%s\" does not exist, skipping",
 							tablespacename)));
 			/* XXX I assume I need one or both of these next two calls */
-			heap_endscan(scandesc);
+			storage_endscan(scandesc);
 			heap_close(rel, NoLock);
 		}
 		return;
@@ -461,7 +462,7 @@ DropTableSpace(DropTableSpaceStmt *stmt)
 	 */
 	CatalogTupleDelete(rel, &tuple->t_self);
 
-	heap_endscan(scandesc);
+	storage_endscan(scandesc);
 
 	/*
 	 * Remove any comments or security labels on this tablespace.
@@ -925,8 +926,8 @@ RenameTableSpace(const char *oldname, const char *newname)
 				Anum_pg_tablespace_spcname,
 				BTEqualStrategyNumber, F_NAMEEQ,
 				CStringGetDatum(oldname));
-	scan = heap_beginscan_catalog(rel, 1, entry);
-	tup = heap_getnext(scan, ForwardScanDirection);
+	scan = storage_beginscan_catalog(rel, 1, entry);
+	tup = storage_getnext(scan, ForwardScanDirection);
 	if (!HeapTupleIsValid(tup))
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
@@ -937,7 +938,7 @@ RenameTableSpace(const char *oldname, const char *newname)
 	newtuple = heap_copytuple(tup);
 	newform = (Form_pg_tablespace) GETSTRUCT(newtuple);
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	/* Must be owner */
 	if (!pg_tablespace_ownercheck(HeapTupleGetOid(newtuple), GetUserId()))
@@ -955,15 +956,15 @@ RenameTableSpace(const char *oldname, const char *newname)
 				Anum_pg_tablespace_spcname,
 				BTEqualStrategyNumber, F_NAMEEQ,
 				CStringGetDatum(newname));
-	scan = heap_beginscan_catalog(rel, 1, entry);
-	tup = heap_getnext(scan, ForwardScanDirection);
+	scan = storage_beginscan_catalog(rel, 1, entry);
+	tup = storage_getnext(scan, ForwardScanDirection);
 	if (HeapTupleIsValid(tup))
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("tablespace \"%s\" already exists",
 						newname)));
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	/* OK, update the entry */
 	namestrcpy(&(newform->spcname), newname);
@@ -1005,8 +1006,8 @@ AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt)
 				Anum_pg_tablespace_spcname,
 				BTEqualStrategyNumber, F_NAMEEQ,
 				CStringGetDatum(stmt->tablespacename));
-	scandesc = heap_beginscan_catalog(rel, 1, entry);
-	tup = heap_getnext(scandesc, ForwardScanDirection);
+	scandesc = storage_beginscan_catalog(rel, 1, entry);
+	tup = storage_getnext(scandesc, ForwardScanDirection);
 	if (!HeapTupleIsValid(tup))
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
@@ -1047,7 +1048,7 @@ AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt)
 	heap_freetuple(newtuple);
 
 	/* Conclude heap scan. */
-	heap_endscan(scandesc);
+	storage_endscan(scandesc);
 	heap_close(rel, NoLock);
 
 	return tablespaceoid;
@@ -1396,8 +1397,8 @@ get_tablespace_oid(const char *tablespacename, bool missing_ok)
 				Anum_pg_tablespace_spcname,
 				BTEqualStrategyNumber, F_NAMEEQ,
 				CStringGetDatum(tablespacename));
-	scandesc = heap_beginscan_catalog(rel, 1, entry);
-	tuple = heap_getnext(scandesc, ForwardScanDirection);
+	scandesc = storage_beginscan_catalog(rel, 1, entry);
+	tuple = storage_getnext(scandesc, ForwardScanDirection);
 
 	/* We assume that there can be at most one matching tuple */
 	if (HeapTupleIsValid(tuple))
@@ -1405,7 +1406,7 @@ get_tablespace_oid(const char *tablespacename, bool missing_ok)
 	else
 		result = InvalidOid;
 
-	heap_endscan(scandesc);
+	storage_endscan(scandesc);
 	heap_close(rel, AccessShareLock);
 
 	if (!OidIsValid(result) && !missing_ok)
@@ -1442,8 +1443,8 @@ get_tablespace_name(Oid spc_oid)
 				ObjectIdAttributeNumber,
 				BTEqualStrategyNumber, F_OIDEQ,
 				ObjectIdGetDatum(spc_oid));
-	scandesc = heap_beginscan_catalog(rel, 1, entry);
-	tuple = heap_getnext(scandesc, ForwardScanDirection);
+	scandesc = storage_beginscan_catalog(rel, 1, entry);
+	tuple = storage_getnext(scandesc, ForwardScanDirection);
 
 	/* We assume that there can be at most one matching tuple */
 	if (HeapTupleIsValid(tuple))
@@ -1451,7 +1452,7 @@ get_tablespace_name(Oid spc_oid)
 	else
 		result = NULL;
 
-	heap_endscan(scandesc);
+	storage_endscan(scandesc);
 	heap_close(rel, AccessShareLock);
 
 	return result;
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index f86af4c054..8a339d16be 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -32,6 +32,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/xact.h"
 #include "catalog/binary_upgrade.h"
 #include "catalog/catalog.h"
@@ -2387,8 +2388,8 @@ AlterDomainNotNull(List *names, bool notNull)
 
 			/* Scan all tuples in this relation */
 			snapshot = RegisterSnapshot(GetLatestSnapshot());
-			scan = heap_beginscan(testrel, snapshot, 0, NULL);
-			while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+			scan = storage_beginscan(testrel, snapshot, 0, NULL);
+			while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 			{
 				int			i;
 
@@ -2417,7 +2418,7 @@ AlterDomainNotNull(List *names, bool notNull)
 					}
 				}
 			}
-			heap_endscan(scan);
+			storage_endscan(scan);
 			UnregisterSnapshot(snapshot);
 
 			/* Close each rel after processing, but keep lock */
@@ -2783,8 +2784,8 @@ validateDomainConstraint(Oid domainoid, char *ccbin)
 
 		/* Scan all tuples in this relation */
 		snapshot = RegisterSnapshot(GetLatestSnapshot());
-		scan = heap_beginscan(testrel, snapshot, 0, NULL);
-		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		scan = storage_beginscan(testrel, snapshot, 0, NULL);
+		while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 		{
 			int			i;
 
@@ -2827,7 +2828,7 @@ validateDomainConstraint(Oid domainoid, char *ccbin)
 
 			ResetExprContext(econtext);
 		}
-		heap_endscan(scan);
+		storage_endscan(scan);
 		UnregisterSnapshot(snapshot);
 
 		/* Hold relation lock till commit (XXX bad for concurrency) */
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 4abe6b15e0..3a8dc8eb0c 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -28,6 +28,7 @@
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/multixact.h"
+#include "access/storageam.h"
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
@@ -533,9 +534,9 @@ get_all_vacuum_rels(void)
 
 	pgclass = heap_open(RelationRelationId, AccessShareLock);
 
-	scan = heap_beginscan_catalog(pgclass, 0, NULL);
+	scan = storage_beginscan_catalog(pgclass, 0, NULL);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
 		MemoryContext oldcontext;
@@ -562,7 +563,7 @@ get_all_vacuum_rels(void)
 		MemoryContextSwitchTo(oldcontext);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(pgclass, AccessShareLock);
 
 	return vacrels;
@@ -1213,9 +1214,9 @@ vac_truncate_clog(TransactionId frozenXID,
 	 */
 	relation = heap_open(DatabaseRelationId, AccessShareLock);
 
-	scan = heap_beginscan_catalog(relation, 0, NULL);
+	scan = storage_beginscan_catalog(relation, 0, NULL);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		volatile FormData_pg_database *dbform = (Form_pg_database) GETSTRUCT(tuple);
 		TransactionId datfrozenxid = dbform->datfrozenxid;
@@ -1252,7 +1253,7 @@ vac_truncate_clog(TransactionId frozenXID,
 		}
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	heap_close(relation, AccessShareLock);
 
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index f1636a5b88..dde843174e 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -544,7 +544,7 @@ static bool
 IndexSupportsBackwardScan(Oid indexid)
 {
 	bool		result;
-	HeapTuple	ht_idxrel;
+	StorageTuple ht_idxrel;
 	Form_pg_class idxrelrec;
 	IndexAmRoutine *amroutine;
 
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index ab533cf9c7..e852718100 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -650,7 +650,7 @@ check_exclusion_or_unique_constraint(Relation heap, Relation index,
 	Oid		   *index_collations = index->rd_indcollation;
 	int			index_natts = index->rd_index->indnatts;
 	IndexScanDesc index_scan;
-	HeapTuple	tup;
+	StorageTuple tup;
 	ScanKeyData scankeys[INDEX_MAX_KEYS];
 	SnapshotData DirtySnapshot;
 	int			i;
@@ -732,12 +732,13 @@ retry:
 		bool		existing_isnull[INDEX_MAX_KEYS];
 		char	   *error_new;
 		char	   *error_existing;
+		tuple_data	t_data = storage_tuple_get_data(heap, tup, TID);
 
 		/*
 		 * Ignore the entry for the tuple we're trying to check.
 		 */
 		if (ItemPointerIsValid(tupleid) &&
-			ItemPointerEquals(tupleid, &tup->t_self))
+			ItemPointerEquals(tupleid, &(t_data.tid)))
 		{
 			if (found_self)		/* should not happen */
 				elog(ERROR, "found self tuple multiple times in index \"%s\"",
@@ -785,7 +786,8 @@ retry:
 			  DirtySnapshot.speculativeToken &&
 			  TransactionIdPrecedes(GetCurrentTransactionId(), xwait))))
 		{
-			ctid_wait = tup->t_data->t_ctid;
+			t_data = storage_tuple_get_data(heap, tup, CTID);
+			ctid_wait = t_data.tid;
 			reason_wait = indexInfo->ii_ExclusionOps ?
 				XLTW_RecheckExclusionConstr : XLTW_InsertIndex;
 			index_endscan(index_scan);
@@ -805,7 +807,10 @@ retry:
 		{
 			conflict = true;
 			if (conflictTid)
-				*conflictTid = tup->t_self;
+			{
+				t_data = storage_tuple_get_data(heap, tup, TID);
+				*conflictTid = t_data.tid;
+			}
 			break;
 		}
 
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index fcb58a0421..d5492fd260 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -118,7 +118,7 @@ RelationFindReplTupleByIndex(Relation rel, Oid idxoid,
 							 TupleTableSlot *searchslot,
 							 TupleTableSlot *outslot)
 {
-	HeapTuple	scantuple;
+	StorageTuple scantuple;
 	ScanKeyData skey[INDEX_MAX_KEYS];
 	IndexScanDesc scan;
 	SnapshotData snap;
@@ -228,8 +228,7 @@ RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 						 TupleTableSlot *searchslot, TupleTableSlot *outslot)
 {
 	TupleTableSlot *scanslot;
-	HeapTuple	scantuple;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	SnapshotData snap;
 	TransactionId xwait;
 	bool		found;
@@ -239,19 +238,19 @@ RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 
 	/* Start a heap scan. */
 	InitDirtySnapshot(snap);
-	scan = heap_beginscan(rel, &snap, 0, NULL);
+	scan = storage_beginscan(rel, &snap, 0, NULL);
 
 	scanslot = MakeSingleTupleTableSlot(desc);
 
 retry:
 	found = false;
 
-	heap_rescan(scan, NULL);
+	storage_rescan(scan, NULL);
 
 	/* Try to find the tuple */
-	while ((scantuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((scanslot = storage_getnextslot(scan, ForwardScanDirection, scanslot))
+		   && !TupIsNull(scanslot))
 	{
-		ExecStoreTuple(scantuple, scanslot, InvalidBuffer, false);
 		if (!ExecSlotCompare(scanslot, searchslot))
 			continue;
 
@@ -313,7 +312,7 @@ retry:
 		}
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	ExecDropSingleTupleTableSlot(scanslot);
 
 	return found;
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 01ca94f9e5..e9a042c02c 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -682,7 +682,7 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 Datum
 ExecFetchSlotTupleDatum(TupleTableSlot *slot)
 {
-	HeapTuple	tup;
+	StorageTuple tup;
 	TupleDesc	tupdesc;
 
 	/* Fetch slot's contents in regular-physical-tuple form */
@@ -766,7 +766,7 @@ ExecHeapifySlot(TupleTableSlot *slot)
 TupleTableSlot *
 ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
 {
-	HeapTuple	newTuple;
+	StorageTuple newTuple;
 	MemoryContext oldContext;
 
 	/*
@@ -1086,7 +1086,7 @@ TupleDescGetAttInMetadata(TupleDesc tupdesc)
  * values is an array of C strings, one for each attribute of the return tuple.
  * A NULL string pointer indicates we want to create a NULL field.
  */
-HeapTuple
+StorageTuple
 BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values)
 {
 	TupleDesc	tupdesc = attinmeta->tupdesc;
@@ -1094,7 +1094,7 @@ BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values)
 	Datum	   *dvalues;
 	bool	   *nulls;
 	int			i;
-	HeapTuple	tuple;
+	StorageTuple tuple;
 
 	dvalues = (Datum *) palloc(natts * sizeof(Datum));
 	nulls = (bool *) palloc(natts * sizeof(bool));
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 3caa343723..a8048c3884 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -181,7 +181,7 @@ static void sqlfunction_destroy(DestReceiver *self);
  * polymorphic arguments.
  */
 SQLFunctionParseInfoPtr
-prepare_sql_fn_parse_info(HeapTuple procedureTuple,
+prepare_sql_fn_parse_info(StorageTuple procedureTuple,
 						  Node *call_expr,
 						  Oid inputCollation)
 {
@@ -598,7 +598,7 @@ init_sql_fcache(FmgrInfo *finfo, Oid collation, bool lazyEvalOK)
 	MemoryContext fcontext;
 	MemoryContext oldcontext;
 	Oid			rettype;
-	HeapTuple	procedureTuple;
+	StorageTuple procedureTuple;
 	Form_pg_proc procedureStruct;
 	SQLFunctionCachePtr fcache;
 	List	   *raw_parsetree_list;
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index da6ef1a94c..ae4b4769ca 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -3130,7 +3130,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 		Oid			inputTypes[FUNC_MAX_ARGS];
 		int			numArguments;
 		int			numDirectArgs;
-		HeapTuple	aggTuple;
+		StorageTuple aggTuple;
 		Form_pg_aggregate aggform;
 		AclResult	aclresult;
 		Oid			transfn_oid,
@@ -3255,7 +3255,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 
 		/* Check that aggregate owner has permission to call component fns */
 		{
-			HeapTuple	procTuple;
+			StorageTuple procTuple;
 			Oid			aggOwner;
 
 			procTuple = SearchSysCache1(PROCOID,
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index 2c5c95d425..0c5ae8fa41 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -38,6 +38,7 @@
 #include <math.h>
 
 #include "access/relscan.h"
+#include "access/storageam.h"
 #include "access/transam.h"
 #include "access/visibilitymap.h"
 #include "executor/execdebug.h"
@@ -433,8 +434,8 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 			HeapTupleData heapTuple;
 
 			ItemPointerSet(&tid, page, offnum);
-			if (heap_hot_search_buffer(&tid, scan->rs_rd, buffer, snapshot,
-									   &heapTuple, NULL, true))
+			if (storage_hot_search_buffer(&tid, scan->rs_rd, buffer, snapshot,
+										  &heapTuple, NULL, true))
 				scan->rs_vistuples[ntup++] = ItemPointerGetOffsetNumber(&tid);
 		}
 	}
@@ -747,7 +748,7 @@ ExecReScanBitmapHeapScan(BitmapHeapScanState *node)
 	PlanState  *outerPlan = outerPlanState(node);
 
 	/* rescan to release any page pin */
-	heap_rescan(node->ss.ss_currentScanDesc, NULL);
+	storage_rescan(node->ss.ss_currentScanDesc, NULL);
 
 	/* release bitmaps and buffers if any */
 	if (node->tbmiterator)
@@ -837,7 +838,7 @@ ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 	/*
 	 * close heap scan
 	 */
-	heap_endscan(scanDesc);
+	storage_endscan(scanDesc);
 
 	/*
 	 * close the heap relation.
@@ -952,10 +953,10 @@ ExecInitBitmapHeapScan(BitmapHeapScan *node, EState *estate, int eflags)
 	 * Even though we aren't going to do a conventional seqscan, it is useful
 	 * to create a HeapScanDesc --- most of the fields in it are usable.
 	 */
-	scanstate->ss.ss_currentScanDesc = heap_beginscan_bm(currentRelation,
-														 estate->es_snapshot,
-														 0,
-														 NULL);
+	scanstate->ss.ss_currentScanDesc = storage_beginscan_bm(currentRelation,
+															estate->es_snapshot,
+															0,
+															NULL);
 
 	/*
 	 * get the scan type from the relation descriptor.
@@ -1123,5 +1124,5 @@ ExecBitmapHeapInitializeWorker(BitmapHeapScanState *node,
 	node->pstate = pstate;
 
 	snapshot = RestoreSnapshot(pstate->phs_snapshot_data);
-	heap_update_snapshot(node->ss.ss_currentScanDesc, snapshot);
+	storage_update_snapshot(node->ss.ss_currentScanDesc, snapshot);
 }
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 690308845c..353dcdc9ca 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -62,9 +62,9 @@ ForeignNext(ForeignScanState *node)
 	 */
 	if (plan->fsSystemCol && !TupIsNull(slot))
 	{
-		HeapTuple	tup = ExecHeapifySlot(slot);
-
-		tup->t_tableOid = RelationGetRelid(node->ss.ss_currentRelation);
+		ExecMaterializeSlot(slot);
+		ExecSlotUpdateTupleTableoid(slot,
+									RelationGetRelid(node->ss.ss_currentRelation));
 	}
 
 	return slot;
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index a44cf8409a..aabcc03013 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -46,7 +46,7 @@
 
 static TupleTableSlot *ExecGather(PlanState *pstate);
 static TupleTableSlot *gather_getnext(GatherState *gatherstate);
-static HeapTuple gather_readnext(GatherState *gatherstate);
+static StorageTuple gather_readnext(GatherState *gatherstate);
 static void ExecShutdownGatherWorkers(GatherState *node);
 
 
@@ -254,7 +254,7 @@ gather_getnext(GatherState *gatherstate)
 	PlanState  *outerPlan = outerPlanState(gatherstate);
 	TupleTableSlot *outerTupleSlot;
 	TupleTableSlot *fslot = gatherstate->funnel_slot;
-	HeapTuple	tup;
+	StorageTuple tup;
 
 	while (gatherstate->nreaders > 0 || gatherstate->need_to_scan_locally)
 	{
@@ -292,7 +292,7 @@ gather_getnext(GatherState *gatherstate)
 /*
  * Attempt to read a tuple from one of our parallel workers.
  */
-static HeapTuple
+static StorageTuple
 gather_readnext(GatherState *gatherstate)
 {
 	int			nvisited = 0;
@@ -300,7 +300,7 @@ gather_readnext(GatherState *gatherstate)
 	for (;;)
 	{
 		TupleQueueReader *reader;
-		HeapTuple	tup;
+		StorageTuple tup;
 		bool		readerdone;
 
 		/* Check for async events, particularly messages from workers. */
diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c
index 4a8a59eabf..470ac1c3a0 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -45,7 +45,7 @@
  */
 typedef struct GMReaderTupleBuffer
 {
-	HeapTuple  *tuple;			/* array of length MAX_TUPLE_STORE */
+	StorageTuple *tuple;		/* array of length MAX_TUPLE_STORE */
 	int			nTuples;		/* number of tuples currently stored */
 	int			readCounter;	/* index of next tuple to extract */
 	bool		done;			/* true if reader is known exhausted */
@@ -54,8 +54,8 @@ typedef struct GMReaderTupleBuffer
 static TupleTableSlot *ExecGatherMerge(PlanState *pstate);
 static int32 heap_compare_slots(Datum a, Datum b, void *arg);
 static TupleTableSlot *gather_merge_getnext(GatherMergeState *gm_state);
-static HeapTuple gm_readnext_tuple(GatherMergeState *gm_state, int nreader,
-				  bool nowait, bool *done);
+static StorageTuple gm_readnext_tuple(GatherMergeState *gm_state, int nreader,
+									  bool nowait, bool *done);
 static void ExecShutdownGatherMergeWorkers(GatherMergeState *node);
 static void gather_merge_setup(GatherMergeState *gm_state);
 static void gather_merge_init(GatherMergeState *gm_state);
@@ -407,7 +407,7 @@ gather_merge_setup(GatherMergeState *gm_state)
 	{
 		/* Allocate the tuple array with length MAX_TUPLE_STORE */
 		gm_state->gm_tuple_buffers[i].tuple =
-			(HeapTuple *) palloc0(sizeof(HeapTuple) * MAX_TUPLE_STORE);
+			(StorageTuple *) palloc0(sizeof(StorageTuple) * MAX_TUPLE_STORE);
 
 		/* Initialize tuple slot for worker */
 		gm_state->gm_slots[i + 1] = ExecInitExtraTupleSlot(gm_state->ps.state);
@@ -625,7 +625,7 @@ static bool
 gather_merge_readnext(GatherMergeState *gm_state, int reader, bool nowait)
 {
 	GMReaderTupleBuffer *tuple_buffer;
-	HeapTuple	tup;
+	StorageTuple tup;
 
 	/*
 	 * If we're being asked to generate a tuple from the leader, then we just
@@ -696,12 +696,12 @@ gather_merge_readnext(GatherMergeState *gm_state, int reader, bool nowait)
 /*
  * Attempt to read a tuple from given worker.
  */
-static HeapTuple
+static StorageTuple
 gm_readnext_tuple(GatherMergeState *gm_state, int nreader, bool nowait,
 				  bool *done)
 {
 	TupleQueueReader *reader;
-	HeapTuple	tup;
+	StorageTuple tup;
 
 	/* Check for async events, particularly messages from workers. */
 	CHECK_FOR_INTERRUPTS();
diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c
index c54c5aa659..e00c9bdbfc 100644
--- a/src/backend/executor/nodeIndexonlyscan.c
+++ b/src/backend/executor/nodeIndexonlyscan.c
@@ -117,7 +117,7 @@ IndexOnlyNext(IndexOnlyScanState *node)
 	 */
 	while ((tid = index_getnext_tid(scandesc, direction)) != NULL)
 	{
-		HeapTuple	tuple = NULL;
+		StorageTuple tuple = NULL;
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -186,9 +186,9 @@ IndexOnlyNext(IndexOnlyScanState *node)
 
 		/*
 		 * Fill the scan tuple slot with data from the index.  This might be
-		 * provided in either HeapTuple or IndexTuple format.  Conceivably an
-		 * index AM might fill both fields, in which case we prefer the heap
-		 * format, since it's probably a bit cheaper to fill a slot from.
+		 * provided in either StorageTuple or IndexTuple format.  Conceivably
+		 * an index AM might fill both fields, in which case we prefer the
+		 * heap format, since it's probably a bit cheaper to fill a slot from.
 		 */
 		if (scandesc->xs_hitup)
 		{
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 2ffef23107..60780e4a99 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -51,7 +51,7 @@
 typedef struct
 {
 	pairingheap_node ph_node;
-	HeapTuple	htup;
+	StorageTuple htup;
 	Datum	   *orderbyvals;
 	bool	   *orderbynulls;
 } ReorderTuple;
@@ -65,9 +65,9 @@ static int cmp_orderbyvals(const Datum *adist, const bool *anulls,
 				IndexScanState *node);
 static int reorderqueue_cmp(const pairingheap_node *a,
 				 const pairingheap_node *b, void *arg);
-static void reorderqueue_push(IndexScanState *node, HeapTuple tuple,
+static void reorderqueue_push(IndexScanState *node, StorageTuple tuple,
 				  Datum *orderbyvals, bool *orderbynulls);
-static HeapTuple reorderqueue_pop(IndexScanState *node);
+static StorageTuple reorderqueue_pop(IndexScanState *node);
 
 
 /* ----------------------------------------------------------------
@@ -84,7 +84,7 @@ IndexNext(IndexScanState *node)
 	ExprContext *econtext;
 	ScanDirection direction;
 	IndexScanDesc scandesc;
-	HeapTuple	tuple;
+	StorageTuple tuple;
 	TupleTableSlot *slot;
 
 	/*
@@ -185,7 +185,7 @@ IndexNextWithReorder(IndexScanState *node)
 	EState	   *estate;
 	ExprContext *econtext;
 	IndexScanDesc scandesc;
-	HeapTuple	tuple;
+	StorageTuple tuple;
 	TupleTableSlot *slot;
 	ReorderTuple *topmost = NULL;
 	bool		was_exact;
@@ -483,7 +483,7 @@ reorderqueue_cmp(const pairingheap_node *a, const pairingheap_node *b,
  * Helper function to push a tuple to the reorder queue.
  */
 static void
-reorderqueue_push(IndexScanState *node, HeapTuple tuple,
+reorderqueue_push(IndexScanState *node, StorageTuple tuple,
 				  Datum *orderbyvals, bool *orderbynulls)
 {
 	IndexScanDesc scandesc = node->iss_ScanDesc;
@@ -516,10 +516,10 @@ reorderqueue_push(IndexScanState *node, HeapTuple tuple,
 /*
  * Helper function to pop the next tuple from the reorder queue.
  */
-static HeapTuple
+static StorageTuple
 reorderqueue_pop(IndexScanState *node)
 {
-	HeapTuple	result;
+	StorageTuple result;
 	ReorderTuple *topmost;
 	int			i;
 
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index 6a118d1883..7e990dc35e 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -29,10 +29,12 @@
 static void InitScanRelation(SampleScanState *node, EState *estate, int eflags);
 static TupleTableSlot *SampleNext(SampleScanState *node);
 static void tablesample_init(SampleScanState *scanstate);
-static HeapTuple tablesample_getnext(SampleScanState *scanstate);
-static bool SampleTupleVisible(HeapTuple tuple, OffsetNumber tupoffset,
+static StorageTuple tablesample_getnext(SampleScanState *scanstate);
+static bool SampleTupleVisible(StorageTuple tuple, OffsetNumber tupoffset,
 				   HeapScanDesc scan);
 
+/* hari */
+
 /* ----------------------------------------------------------------
  *						Scan Support
  * ----------------------------------------------------------------
@@ -47,7 +49,7 @@ static bool SampleTupleVisible(HeapTuple tuple, OffsetNumber tupoffset,
 static TupleTableSlot *
 SampleNext(SampleScanState *node)
 {
-	HeapTuple	tuple;
+	StorageTuple tuple;
 	TupleTableSlot *slot;
 
 	/*
@@ -244,7 +246,7 @@ ExecEndSampleScan(SampleScanState *node)
 	 * close heap scan
 	 */
 	if (node->ss.ss_currentScanDesc)
-		heap_endscan(node->ss.ss_currentScanDesc);
+		storage_endscan(node->ss.ss_currentScanDesc);
 
 	/*
 	 * close the heap relation.
@@ -349,19 +351,19 @@ tablesample_init(SampleScanState *scanstate)
 	if (scanstate->ss.ss_currentScanDesc == NULL)
 	{
 		scanstate->ss.ss_currentScanDesc =
-			heap_beginscan_sampling(scanstate->ss.ss_currentRelation,
-									scanstate->ss.ps.state->es_snapshot,
-									0, NULL,
-									scanstate->use_bulkread,
-									allow_sync,
-									scanstate->use_pagemode);
+			storage_beginscan_sampling(scanstate->ss.ss_currentRelation,
+									   scanstate->ss.ps.state->es_snapshot,
+									   0, NULL,
+									   scanstate->use_bulkread,
+									   allow_sync,
+									   scanstate->use_pagemode);
 	}
 	else
 	{
-		heap_rescan_set_params(scanstate->ss.ss_currentScanDesc, NULL,
-							   scanstate->use_bulkread,
-							   allow_sync,
-							   scanstate->use_pagemode);
+		storage_rescan_set_params(scanstate->ss.ss_currentScanDesc, NULL,
+								  scanstate->use_bulkread,
+								  allow_sync,
+								  scanstate->use_pagemode);
 	}
 
 	pfree(params);
@@ -376,7 +378,7 @@ tablesample_init(SampleScanState *scanstate)
  * Note: an awful lot of this is copied-and-pasted from heapam.c.  It would
  * perhaps be better to refactor to share more code.
  */
-static HeapTuple
+static StorageTuple
 tablesample_getnext(SampleScanState *scanstate)
 {
 	TsmRoutine *tsm = scanstate->tsmroutine;
@@ -554,7 +556,7 @@ tablesample_getnext(SampleScanState *scanstate)
  * Check visibility of the tuple.
  */
 static bool
-SampleTupleVisible(HeapTuple tuple, OffsetNumber tupoffset, HeapScanDesc scan)
+SampleTupleVisible(StorageTuple tuple, OffsetNumber tupoffset, HeapScanDesc scan) //hari
 {
 	if (scan->rs_pageatatime)
 	{
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index a5bd60e579..7cfc2107f6 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -28,6 +28,7 @@
 #include "postgres.h"
 
 #include "access/relscan.h"
+#include "access/storageam.h"
 #include "executor/execdebug.h"
 #include "executor/nodeSeqscan.h"
 #include "utils/rel.h"
@@ -49,8 +50,7 @@ static TupleTableSlot *SeqNext(SeqScanState *node);
 static TupleTableSlot *
 SeqNext(SeqScanState *node)
 {
-	HeapTuple	tuple;
-	HeapScanDesc scandesc;
+	StorageScanDesc scandesc;
 	EState	   *estate;
 	ScanDirection direction;
 	TupleTableSlot *slot;
@@ -69,35 +69,16 @@ SeqNext(SeqScanState *node)
 		 * We reach here if the scan is not parallel, or if we're executing a
 		 * scan that was intended to be parallel serially.
 		 */
-		scandesc = heap_beginscan(node->ss.ss_currentRelation,
-								  estate->es_snapshot,
-								  0, NULL);
+		scandesc = storage_beginscan(node->ss.ss_currentRelation,
+									 estate->es_snapshot,
+									 0, NULL);
 		node->ss.ss_currentScanDesc = scandesc;
 	}
 
 	/*
 	 * get the next tuple from the table
 	 */
-	tuple = heap_getnext(scandesc, direction);
-
-	/*
-	 * save the tuple and the buffer returned to us by the access methods in
-	 * our scan tuple slot and return the slot.  Note: we pass 'false' because
-	 * tuples returned by heap_getnext() are pointers onto disk pages and were
-	 * not created with palloc() and so should not be pfree()'d.  Note also
-	 * that ExecStoreTuple will increment the refcount of the buffer; the
-	 * refcount will not be dropped until the tuple table slot is cleared.
-	 */
-	if (tuple)
-		ExecStoreTuple(tuple,	/* tuple to store */
-					   slot,	/* slot to store in */
-					   scandesc->rs_cbuf,	/* buffer associated with this
-											 * tuple */
-					   false);	/* don't pfree this pointer */
-	else
-		ExecClearTuple(slot);
-
-	return slot;
+	return storage_getnextslot(scandesc, direction, slot);
 }
 
 /*
@@ -225,7 +206,7 @@ void
 ExecEndSeqScan(SeqScanState *node)
 {
 	Relation	relation;
-	HeapScanDesc scanDesc;
+	StorageScanDesc scanDesc;
 
 	/*
 	 * get information from node
@@ -248,7 +229,7 @@ ExecEndSeqScan(SeqScanState *node)
 	 * close heap scan
 	 */
 	if (scanDesc != NULL)
-		heap_endscan(scanDesc);
+		storage_endscan(scanDesc);
 
 	/*
 	 * close the heap relation.
@@ -270,13 +251,13 @@ ExecEndSeqScan(SeqScanState *node)
 void
 ExecReScanSeqScan(SeqScanState *node)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 
 	scan = node->ss.ss_currentScanDesc;
 
 	if (scan != NULL)
-		heap_rescan(scan,		/* scan desc */
-					NULL);		/* new scan keys */
+		storage_rescan(scan,	/* scan desc */
+					   NULL);	/* new scan keys */
 
 	ExecScanReScan((ScanState *) node);
 }
@@ -323,7 +304,7 @@ ExecSeqScanInitializeDSM(SeqScanState *node,
 								 estate->es_snapshot);
 	shm_toc_insert(pcxt->toc, node->ss.ps.plan->plan_node_id, pscan);
 	node->ss.ss_currentScanDesc =
-		heap_beginscan_parallel(node->ss.ss_currentRelation, pscan);
+		storage_beginscan_parallel(node->ss.ss_currentRelation, pscan);
 }
 
 /* ----------------------------------------------------------------
@@ -355,5 +336,5 @@ ExecSeqScanInitializeWorker(SeqScanState *node,
 
 	pscan = shm_toc_lookup(pwcxt->toc, node->ss.ps.plan->plan_node_id, false);
 	node->ss.ss_currentScanDesc =
-		heap_beginscan_parallel(node->ss.ss_currentRelation, pscan);
+		storage_beginscan_parallel(node->ss.ss_currentRelation, pscan);
 }
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 02868749f6..a68584378b 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -2092,7 +2092,7 @@ initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc,
 {
 	Oid			inputTypes[FUNC_MAX_ARGS];
 	int			numArguments;
-	HeapTuple	aggTuple;
+	StorageTuple aggTuple;
 	Form_pg_aggregate aggform;
 	Oid			aggtranstype;
 	AttrNumber	initvalAttNo;
@@ -2175,7 +2175,7 @@ initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc,
 
 	/* Check that aggregate owner has permission to call component fns */
 	{
-		HeapTuple	procTuple;
+		StorageTuple procTuple;
 		Oid			aggOwner;
 
 		procTuple = SearchSysCache1(PROCOID,
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index f3da2ddd08..31e0d7ef97 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -633,11 +633,11 @@ SPI_freeplan(SPIPlanPtr plan)
 	return 0;
 }
 
-HeapTuple
-SPI_copytuple(HeapTuple tuple)
+StorageTuple
+SPI_copytuple(StorageTuple tuple)
 {
 	MemoryContext oldcxt;
-	HeapTuple	ctuple;
+	StorageTuple ctuple;
 
 	if (tuple == NULL)
 	{
@@ -661,7 +661,7 @@ SPI_copytuple(HeapTuple tuple)
 }
 
 HeapTupleHeader
-SPI_returntuple(HeapTuple tuple, TupleDesc tupdesc)
+SPI_returntuple(StorageTuple tuple, TupleDesc tupdesc)
 {
 	MemoryContext oldcxt;
 	HeapTupleHeader dtup;
@@ -692,7 +692,7 @@ SPI_returntuple(HeapTuple tuple, TupleDesc tupdesc)
 	return dtup;
 }
 
-HeapTuple
+StorageTuple
 SPI_modifytuple(Relation rel, HeapTuple tuple, int natts, int *attnum,
 				Datum *Values, const char *Nulls)
 {
@@ -860,7 +860,7 @@ char *
 SPI_gettype(TupleDesc tupdesc, int fnumber)
 {
 	Oid			typoid;
-	HeapTuple	typeTuple;
+	StorageTuple typeTuple;
 	char	   *result;
 
 	SPI_result = 0;
@@ -968,7 +968,7 @@ SPI_datumTransfer(Datum value, bool typByVal, int typLen)
 }
 
 void
-SPI_freetuple(HeapTuple tuple)
+SPI_freetuple(StorageTuple tuple)
 {
 	/* No longer need to worry which context tuple was in... */
 	heap_freetuple(tuple);
@@ -1689,7 +1689,7 @@ spi_dest_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 
 	/* set up initial allocations */
 	tuptable->alloced = tuptable->free = 128;
-	tuptable->vals = (HeapTuple *) palloc(tuptable->alloced * sizeof(HeapTuple));
+	tuptable->vals = (StorageTuple *) palloc(tuptable->alloced * sizeof(StorageTuple));
 	tuptable->tupdesc = CreateTupleDescCopy(typeinfo);
 
 	MemoryContextSwitchTo(oldcxt);
@@ -1720,8 +1720,8 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self)
 		/* Double the size of the pointer array */
 		tuptable->free = tuptable->alloced;
 		tuptable->alloced += tuptable->free;
-		tuptable->vals = (HeapTuple *) repalloc_huge(tuptable->vals,
-													 tuptable->alloced * sizeof(HeapTuple));
+		tuptable->vals = (StorageTuple *) repalloc_huge(tuptable->vals,
+														tuptable->alloced * sizeof(StorageTuple));
 	}
 
 	tuptable->vals[tuptable->alloced - tuptable->free] =
diff --git a/src/backend/executor/tqueue.c b/src/backend/executor/tqueue.c
index ef681e2ec2..41d94774cb 100644
--- a/src/backend/executor/tqueue.c
+++ b/src/backend/executor/tqueue.c
@@ -168,7 +168,7 @@ DestroyTupleQueueReader(TupleQueueReader *reader)
  * accumulate bytes from a partially-read message, so it's useful to call
  * this with nowait = true even if nothing is returned.
  */
-HeapTuple
+StorageTuple
 TupleQueueReaderNext(TupleQueueReader *reader, bool nowait, bool *done)
 {
 	HeapTupleData htup;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 48765bb01b..0d2e1733bf 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1883,9 +1883,9 @@ get_database_list(void)
 	(void) GetTransactionSnapshot();
 
 	rel = heap_open(DatabaseRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(rel, 0, NULL);
+	scan = storage_beginscan_catalog(rel, 0, NULL);
 
-	while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+	while (HeapTupleIsValid(tup = storage_getnext(scan, ForwardScanDirection)))
 	{
 		Form_pg_database pgdatabase = (Form_pg_database) GETSTRUCT(tup);
 		avw_dbase  *avdb;
@@ -1912,7 +1912,7 @@ get_database_list(void)
 		MemoryContextSwitchTo(oldcxt);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(rel, AccessShareLock);
 
 	CommitTransactionCommand();
@@ -2043,13 +2043,13 @@ do_autovacuum(void)
 	 * wide tables there might be proportionally much more activity in the
 	 * TOAST table than in its parent.
 	 */
-	relScan = heap_beginscan_catalog(classRel, 0, NULL);
+	relScan = storage_beginscan_catalog(classRel, 0, NULL);
 
 	/*
 	 * On the first pass, we collect main tables to vacuum, and also the main
 	 * table relid to TOAST relid mapping.
 	 */
-	while ((tuple = heap_getnext(relScan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(relScan, ForwardScanDirection)) != NULL)
 	{
 		Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
 		PgStat_StatTabEntry *tabentry;
@@ -2135,7 +2135,7 @@ do_autovacuum(void)
 		}
 	}
 
-	heap_endscan(relScan);
+	storage_endscan(relScan);
 
 	/* second pass: check TOAST tables */
 	ScanKeyInit(&key,
@@ -2143,8 +2143,8 @@ do_autovacuum(void)
 				BTEqualStrategyNumber, F_CHAREQ,
 				CharGetDatum(RELKIND_TOASTVALUE));
 
-	relScan = heap_beginscan_catalog(classRel, 1, &key);
-	while ((tuple = heap_getnext(relScan, ForwardScanDirection)) != NULL)
+	relScan = storage_beginscan_catalog(classRel, 1, &key);
+	while ((tuple = storage_getnext(relScan, ForwardScanDirection)) != NULL)
 	{
 		Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
 		PgStat_StatTabEntry *tabentry;
@@ -2190,7 +2190,7 @@ do_autovacuum(void)
 			table_oids = lappend_oid(table_oids, relid);
 	}
 
-	heap_endscan(relScan);
+	storage_endscan(relScan);
 	heap_close(classRel, AccessShareLock);
 
 	/*
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 5c256ff8ab..2b56b740a2 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -36,6 +36,7 @@
 
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/transam.h"
 #include "access/twophase_rmgr.h"
 #include "access/xact.h"
@@ -1221,8 +1222,8 @@ pgstat_collect_oids(Oid catalogid)
 
 	rel = heap_open(catalogid, AccessShareLock);
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	scan = heap_beginscan(rel, snapshot, 0, NULL);
-	while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	scan = storage_beginscan(rel, snapshot, 0, NULL);
+	while ((tup = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Oid			thisoid = HeapTupleGetOid(tup);
 
@@ -1230,7 +1231,7 @@ pgstat_collect_oids(Oid catalogid)
 
 		(void) hash_search(htab, (void *) &thisoid, HASH_ENTER, NULL);
 	}
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 	heap_close(rel, AccessShareLock);
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index a613ef4757..83ec2dfcbe 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -24,6 +24,7 @@
 #include "access/heapam.h"
 #include "access/htup.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/xact.h"
 
 #include "catalog/pg_subscription.h"
@@ -124,9 +125,9 @@ get_subscription_list(void)
 	(void) GetTransactionSnapshot();
 
 	rel = heap_open(SubscriptionRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(rel, 0, NULL);
+	scan = storage_beginscan_catalog(rel, 0, NULL);
 
-	while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+	while (HeapTupleIsValid(tup = storage_getnext(scan, ForwardScanDirection)))
 	{
 		Form_pg_subscription subform = (Form_pg_subscription) GETSTRUCT(tup);
 		Subscription *sub;
@@ -152,7 +153,7 @@ get_subscription_list(void)
 		MemoryContextSwitchTo(oldcxt);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(rel, AccessShareLock);
 
 	CommitTransactionCommand();
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 247809927a..c6791c758c 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -17,6 +17,7 @@
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/multixact.h"
+#include "access/storageam.h"
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
@@ -435,13 +436,13 @@ DefineQueryRewrite(const char *rulename,
 								RelationGetRelationName(event_relation))));
 
 			snapshot = RegisterSnapshot(GetLatestSnapshot());
-			scanDesc = heap_beginscan(event_relation, snapshot, 0, NULL);
-			if (heap_getnext(scanDesc, ForwardScanDirection) != NULL)
+			scanDesc = storage_beginscan(event_relation, snapshot, 0, NULL);
+			if (storage_getnext(scanDesc, ForwardScanDirection) != NULL)
 				ereport(ERROR,
 						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 						 errmsg("could not convert table \"%s\" to a view because it is not empty",
 								RelationGetRelationName(event_relation))));
-			heap_endscan(scanDesc);
+			storage_endscan(scanDesc);
 			UnregisterSnapshot(snapshot);
 
 			if (event_relation->rd_rel->relhastriggers)
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 20f1d279e9..03b7cc76d7 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -22,6 +22,7 @@
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/session.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "access/xlog.h"
@@ -1212,10 +1213,10 @@ ThereIsAtLeastOneRole(void)
 
 	pg_authid_rel = heap_open(AuthIdRelationId, AccessShareLock);
 
-	scan = heap_beginscan_catalog(pg_authid_rel, 0, NULL);
-	result = (heap_getnext(scan, ForwardScanDirection) != NULL);
+	scan = storage_beginscan_catalog(pg_authid_rel, 0, NULL);
+	result = (storage_getnext(scan, ForwardScanDirection) != NULL);
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(pg_authid_rel, AccessShareLock);
 
 	return result;
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 251342b01b..be62547195 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -108,26 +108,25 @@ typedef struct ParallelHeapScanDescData *ParallelHeapScanDesc;
 #define HeapScanIsValid(scan) PointerIsValid(scan)
 
 extern HeapScanDesc heap_beginscan(Relation relation, Snapshot snapshot,
-			   int nkeys, ScanKey key);
-extern HeapScanDesc heap_beginscan_catalog(Relation relation, int nkeys,
-					   ScanKey key);
-extern HeapScanDesc heap_beginscan_strat(Relation relation, Snapshot snapshot,
-					 int nkeys, ScanKey key,
-					 bool allow_strat, bool allow_sync);
-extern HeapScanDesc heap_beginscan_bm(Relation relation, Snapshot snapshot,
-				  int nkeys, ScanKey key);
-extern HeapScanDesc heap_beginscan_sampling(Relation relation,
-						Snapshot snapshot, int nkeys, ScanKey key,
-						bool allow_strat, bool allow_sync, bool allow_pagemode);
+			   int nkeys, ScanKey key,
+			   ParallelHeapScanDesc parallel_scan,
+			   bool allow_strat,
+			   bool allow_sync,
+			   bool allow_pagemode,
+			   bool is_bitmapscan,
+			   bool is_samplescan,
+			   bool temp_snap);
 extern void heap_setscanlimits(HeapScanDesc scan, BlockNumber startBlk,
 				   BlockNumber endBlk);
 extern void heapgetpage(HeapScanDesc scan, BlockNumber page);
-extern void heap_rescan(HeapScanDesc scan, ScanKey key);
+extern void heap_rescan(HeapScanDesc scan, ScanKey key, bool set_params,
+			bool allow_strat, bool allow_sync, bool allow_pagemode);
 extern void heap_rescan_set_params(HeapScanDesc scan, ScanKey key,
 					   bool allow_strat, bool allow_sync, bool allow_pagemode);
 extern void heap_endscan(HeapScanDesc scan);
-extern HeapTuple heap_getnext(HeapScanDesc scan, ScanDirection direction);
-
+extern StorageTuple heap_getnext(HeapScanDesc scan, ScanDirection direction);
+extern TupleTableSlot *heap_getnextslot(HeapScanDesc sscan, ScanDirection direction,
+				 TupleTableSlot *slot);
 extern Size heap_parallelscan_estimate(Snapshot snapshot);
 extern void heap_parallelscan_initialize(ParallelHeapScanDesc target,
 							 Relation relation, Snapshot snapshot);
diff --git a/src/include/access/storage_common.h b/src/include/access/storage_common.h
index ff28b4dfec..7d70de4e33 100644
--- a/src/include/access/storage_common.h
+++ b/src/include/access/storage_common.h
@@ -27,6 +27,7 @@
 
 /* A physical tuple coming from a storage AM scan */
 typedef void *StorageTuple;
+typedef void *StorageScanDesc;
 
 /*
  * slot storage routine functions
diff --git a/src/include/access/storageam.h b/src/include/access/storageam.h
index e0658a1ddd..e79a24055d 100644
--- a/src/include/access/storageam.h
+++ b/src/include/access/storageam.h
@@ -15,10 +15,59 @@
 #define STORAGEAM_H
 
 #include "access/heapam.h"
-#include "access/storageamapi.h"
+#include "access/storage_common.h"
 #include "executor/tuptable.h"
 #include "nodes/execnodes.h"
 
+typedef union tuple_data
+{
+	TransactionId xid;
+	CommandId	cid;
+	ItemPointerData tid;
+}			tuple_data;
+
+typedef enum tuple_data_flags
+{
+	XMIN = 0,
+	UPDATED_XID,
+	CMIN,
+	TID,
+	CTID
+}			tuple_data_flags;
+
+/* Function pointer to let the index tuple insert from storage am */
+typedef List *(*InsertIndexTuples) (TupleTableSlot *slot, EState *estate, bool noDupErr,
+									bool *specConflict, List *arbiterIndexes);
+
+
+extern HeapScanDesc storage_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan);
+
+extern void storage_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
+extern HeapScanDesc storage_beginscan(Relation relation, Snapshot snapshot,
+				  int nkeys, ScanKey key);
+extern HeapScanDesc storage_beginscan_catalog(Relation relation, int nkeys, ScanKey key);
+extern HeapScanDesc storage_beginscan_strat(Relation relation, Snapshot snapshot,
+						int nkeys, ScanKey key,
+						bool allow_strat, bool allow_sync);
+extern HeapScanDesc storage_beginscan_bm(Relation relation, Snapshot snapshot,
+					 int nkeys, ScanKey key);
+extern HeapScanDesc storage_beginscan_sampling(Relation relation, Snapshot snapshot,
+						   int nkeys, ScanKey key,
+						   bool allow_strat, bool allow_sync, bool allow_pagemode);
+
+extern void storage_endscan(HeapScanDesc scan);
+extern void storage_rescan(HeapScanDesc scan, ScanKey key);
+extern void storage_rescan_set_params(HeapScanDesc scan, ScanKey key,
+						  bool allow_strat, bool allow_sync, bool allow_pagemode);
+extern void storage_update_snapshot(HeapScanDesc scan, Snapshot snapshot);
+
+extern StorageTuple storage_getnext(HeapScanDesc sscan, ScanDirection direction);
+extern TupleTableSlot *storage_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *slot);
+
+extern void storage_get_latest_tid(Relation relation,
+					   Snapshot snapshot,
+					   ItemPointer tid);
+
 extern bool storage_fetch(Relation relation,
 			  ItemPointer tid,
 			  Snapshot snapshot,
@@ -27,6 +76,13 @@ extern bool storage_fetch(Relation relation,
 			  bool keep_buf,
 			  Relation stats_relation);
 
+extern bool storage_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
+						  Snapshot snapshot, HeapTuple heapTuple,
+						  bool *all_dead, bool first_call);
+
+extern bool storage_hot_search(ItemPointer tid, Relation relation, Snapshot snapshot,
+				   bool *all_dead);
+
 extern HTSU_Result storage_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * stuple,
 				   CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 				   bool follow_updates,
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
index 22bc2493ac..bda6b46a94 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -12,32 +12,12 @@
 #define STORAGEAMAPI_H
 
 #include "access/heapam.h"
-#include "access/storage_common.h"
+#include "access/storageam.h"
 #include "nodes/execnodes.h"
 #include "nodes/nodes.h"
 #include "utils/snapshot.h"
 #include "fmgr.h"
 
-typedef union tuple_data
-{
-	TransactionId xid;
-	CommandId	cid;
-	ItemPointerData tid;
-}			tuple_data;
-
-typedef enum tuple_data_flags
-{
-	XMIN = 0,
-	UPDATED_XID,
-	CMIN,
-	TID,
-	CTID
-}			tuple_data_flags;
-
-/* Function pointer to let the index tuple insert from storage am */
-typedef List *(*InsertIndexTuples) (TupleTableSlot *slot, EState *estate, bool noDupErr,
-									bool *specConflict, List *arbiterIndexes);
-
 /*
  * Storage routine functions
  */
@@ -103,6 +83,39 @@ typedef void (*SpeculativeAbort_function) (Relation rel,
 
 typedef void (*RelationSync_function) (Relation relation);
 
+
+typedef HeapScanDesc (*ScanBegin_function) (Relation relation,
+											Snapshot snapshot,
+											int nkeys, ScanKey key,
+											ParallelHeapScanDesc parallel_scan,
+											bool allow_strat,
+											bool allow_sync,
+											bool allow_pagemode,
+											bool is_bitmapscan,
+											bool is_samplescan,
+											bool temp_snap);
+typedef void (*ScanSetlimits_function) (HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
+
+/* must return a TupleTableSlot? */
+typedef StorageTuple(*ScanGetnext_function) (HeapScanDesc scan,
+											 ScanDirection direction);
+
+typedef TupleTableSlot *(*ScanGetnextSlot_function) (HeapScanDesc scan,
+													 ScanDirection direction, TupleTableSlot *slot);
+
+typedef void (*ScanEnd_function) (HeapScanDesc scan);
+
+
+typedef void (*ScanGetpage_function) (HeapScanDesc scan, BlockNumber page);
+typedef void (*ScanRescan_function) (HeapScanDesc scan, ScanKey key, bool set_params,
+									 bool allow_strat, bool allow_sync, bool allow_pagemode);
+typedef void (*ScanUpdateSnapshot_function) (HeapScanDesc scan, Snapshot snapshot);
+
+typedef bool (*HotSearchBuffer_function) (ItemPointer tid, Relation relation,
+										  Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
+										  bool *all_dead, bool first_call);
+
+
 /*
  * API struct for a storage AM.  Note this must be stored in a single palloc'd
  * chunk of memory.
@@ -145,6 +158,17 @@ typedef struct StorageAmRoutine
 
 	RelationSync_function relation_sync;	/* heap_sync */
 
+	/* Operations on relation scans */
+	ScanBegin_function scan_begin;
+	ScanSetlimits_function scansetlimits;
+	ScanGetnext_function scan_getnext;
+	ScanGetnextSlot_function scan_getnextslot;
+	ScanEnd_function scan_end;
+	ScanGetpage_function scan_getpage;
+	ScanRescan_function scan_rescan;
+	ScanUpdateSnapshot_function scan_update_snapshot;
+	HotSearchBuffer_function hot_search_buffer; /* heap_hot_search_buffer */
+
 }			StorageAmRoutine;
 
 extern StorageAmRoutine * GetStorageAmRoutine(Oid amhandler);
diff --git a/src/include/executor/functions.h b/src/include/executor/functions.h
index 718d8947a3..7f9bef1374 100644
--- a/src/include/executor/functions.h
+++ b/src/include/executor/functions.h
@@ -22,7 +22,7 @@ typedef struct SQLFunctionParseInfo *SQLFunctionParseInfoPtr;
 
 extern Datum fmgr_sql(PG_FUNCTION_ARGS);
 
-extern SQLFunctionParseInfoPtr prepare_sql_fn_parse_info(HeapTuple procedureTuple,
+extern SQLFunctionParseInfoPtr prepare_sql_fn_parse_info(StorageTuple procedureTuple,
 						  Node *call_expr,
 						  Oid inputCollation);
 
diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h
index acade7e92e..2781975530 100644
--- a/src/include/executor/spi.h
+++ b/src/include/executor/spi.h
@@ -25,7 +25,7 @@ typedef struct SPITupleTable
 	uint64		alloced;		/* # of alloced vals */
 	uint64		free;			/* # of free vals */
 	TupleDesc	tupdesc;		/* tuple descriptor */
-	HeapTuple  *vals;			/* tuples */
+	StorageTuple *vals;			/* tuples */
 	slist_node	next;			/* link for internal bookkeeping */
 	SubTransactionId subid;		/* subxact in which tuptable was created */
 } SPITupleTable;
@@ -117,10 +117,10 @@ extern const char *SPI_result_code_string(int code);
 extern List *SPI_plan_get_plan_sources(SPIPlanPtr plan);
 extern CachedPlan *SPI_plan_get_cached_plan(SPIPlanPtr plan);
 
-extern HeapTuple SPI_copytuple(HeapTuple tuple);
-extern HeapTupleHeader SPI_returntuple(HeapTuple tuple, TupleDesc tupdesc);
-extern HeapTuple SPI_modifytuple(Relation rel, HeapTuple tuple, int natts,
-				int *attnum, Datum *Values, const char *Nulls);
+extern StorageTuple SPI_copytuple(StorageTuple tuple);
+extern HeapTupleHeader SPI_returntuple(StorageTuple tuple, TupleDesc tupdesc);
+extern StorageTuple SPI_modifytuple(Relation rel, HeapTuple tuple, int natts,
+									int *attnum, Datum *Values, const char *Nulls);
 extern int	SPI_fnumber(TupleDesc tupdesc, const char *fname);
 extern char *SPI_fname(TupleDesc tupdesc, int fnumber);
 extern char *SPI_getvalue(HeapTuple tuple, TupleDesc tupdesc, int fnumber);
@@ -133,7 +133,7 @@ extern void *SPI_palloc(Size size);
 extern void *SPI_repalloc(void *pointer, Size size);
 extern void SPI_pfree(void *pointer);
 extern Datum SPI_datumTransfer(Datum value, bool typByVal, int typLen);
-extern void SPI_freetuple(HeapTuple pointer);
+extern void SPI_freetuple(StorageTuple pointer);
 extern void SPI_freetuptable(SPITupleTable *tuptable);
 
 extern Portal SPI_cursor_open(const char *name, SPIPlanPtr plan,
diff --git a/src/include/executor/tqueue.h b/src/include/executor/tqueue.h
index fdc9deb2b2..9905ecd8ba 100644
--- a/src/include/executor/tqueue.h
+++ b/src/include/executor/tqueue.h
@@ -26,7 +26,7 @@ extern DestReceiver *CreateTupleQueueDestReceiver(shm_mq_handle *handle);
 /* Use these to receive tuples from a shm_mq. */
 extern TupleQueueReader *CreateTupleQueueReader(shm_mq_handle *handle);
 extern void DestroyTupleQueueReader(TupleQueueReader *reader);
-extern HeapTuple TupleQueueReaderNext(TupleQueueReader *reader,
-					 bool nowait, bool *done);
+extern StorageTuple TupleQueueReaderNext(TupleQueueReader *reader,
+										 bool nowait, bool *done);
 
 #endif							/* TQUEUE_H */
diff --git a/src/include/funcapi.h b/src/include/funcapi.h
index 223eef28d1..54b37f7db5 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -237,7 +237,7 @@ extern TupleDesc TypeGetTupleDesc(Oid typeoid, List *colaliases);
 /* from execTuples.c */
 extern TupleDesc BlessTupleDesc(TupleDesc tupdesc);
 extern AttInMetadata *TupleDescGetAttInMetadata(TupleDesc tupdesc);
-extern HeapTuple BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values);
+extern StorageTuple BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values);
 extern Datum HeapTupleHeaderGetDatum(HeapTupleHeader tuple);
 extern TupleTableSlot *TupleDescGetSlot(TupleDesc tupdesc);
 
-- 
2.15.0.windows.1



^ permalink  raw  reply  [nested|flat] 21+ messages in thread

* Re: [HACKERS] Pluggable storage
  2017-12-12 04:06 Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
@ 2017-12-27 03:54 ` Haribabu Kommi <[email protected]>
  2017-12-27 12:33   ` Re: [HACKERS] Pluggable storage Alexander Korotkov <[email protected]>
  0 siblings, 1 reply; 21+ messages in thread

From: Haribabu Kommi @ 2017-12-27 03:54 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: Robert Haas <[email protected]>; Amit Kapila <[email protected]>; Alexander Korotkov <[email protected]>; Thomas Munro <[email protected]>; Alvaro Herrera <[email protected]>; pgsql-hackers

On Tue, Dec 12, 2017 at 3:06 PM, Haribabu Kommi <[email protected]>
wrote:

>
> I restructured that patch files to avoid showing unnecessary modifications,
> and also it will be easy for adding of new API's based on the all the
> functions
> that are exposed by heapam module easily compared earlier.
>
> Attached are the latest set of patches. I will work on the remaining
> pending
> items.
>

Apart from rebase to the latest master code, following are the additional
changes,

1. Added API for bulk insert and rewrite functionality(Logical rewrite is
not touched yet)
2. Tuple lock API interface redesign to remove the traversal logic from
executor module.

The tuple lock API interface changes are from "Alexander Korotkov" from
"PostgresPro".
Thanks Alexander. Currently we both are doing joint development for faster
closure of
open items that are pending to bring the "pluggable storage API" into a
good shape.

Regards,
Hari Babu
Fujitsu Australia


Attachments:

  [application/octet-stream] 0010-storage-rewrite-functionality.patch (11.1K, ../../CAJrrPGc951F-R4Kfa4W47B5vHKeHsB2Y34zewp=b+AWSkF9RVA@mail.gmail.com/3-0010-storage-rewrite-functionality.patch)
  download | inline diff:
From 2caf22da57885cdd96929cfa02b5c8910c58baad Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Wed, 27 Dec 2017 13:04:24 +1100
Subject: [PATCH 10/11] storage rewrite functionality

All the heap rewrite functionality is moved into heap storage
and exposed them with storage API. Currenlty these API's are used
only by the cluster command operation.

The logical rewrite mapping code is currently left as it is,
this needs to be handled seperately.
---
 src/backend/access/heap/heapam_storage.c |  6 ++++++
 src/backend/access/storage/storageam.c   | 33 ++++++++++++++++++++++++++++++++
 src/backend/commands/cluster.c           | 32 +++++++++++++++----------------
 src/include/access/heapam.h              |  9 +++++++++
 src/include/access/rewriteheap.h         | 11 -----------
 src/include/access/storage_common.h      |  2 ++
 src/include/access/storageam.h           |  8 ++++++++
 src/include/access/storageamapi.h        | 13 +++++++++++++
 8 files changed, 86 insertions(+), 28 deletions(-)

diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index b89069bf74..2a39e0cc1e 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -22,6 +22,7 @@
 
 #include "access/heapam.h"
 #include "access/relscan.h"
+#include "access/rewriteheap.h"
 #include "access/storageamapi.h"
 #include "pgstat.h"
 #include "utils/builtins.h"
@@ -331,5 +332,10 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 	amroutine->freebulkinsertstate = FreeBulkInsertState;
 	amroutine->releasebulkinsertstate = ReleaseBulkInsertStatePin;
 
+	amroutine->begin_heap_rewrite = begin_heap_rewrite;
+	amroutine->end_heap_rewrite = end_heap_rewrite;
+	amroutine->rewrite_heap_tuple = rewrite_heap_tuple;
+	amroutine->rewrite_heap_dead_tuple = rewrite_heap_dead_tuple;
+
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/storage/storageam.c b/src/backend/access/storage/storageam.c
index d884b3633c..014dc6d265 100644
--- a/src/backend/access/storage/storageam.c
+++ b/src/backend/access/storage/storageam.c
@@ -401,3 +401,36 @@ storage_releasebulkinsertstate(Relation rel, BulkInsertState bistate)
 {
 	rel->rd_stamroutine->releasebulkinsertstate(bistate);
 }
+
+/*
+ * -------------------
+ * storage tuple rewrite functions
+ * -------------------
+ */
+RewriteState
+storage_begin_rewrite(Relation OldHeap, Relation NewHeap,
+				   TransactionId OldestXmin, TransactionId FreezeXid,
+				   MultiXactId MultiXactCutoff, bool use_wal)
+{
+	return NewHeap->rd_stamroutine->begin_heap_rewrite(OldHeap, NewHeap,
+			OldestXmin, FreezeXid, MultiXactCutoff, use_wal);
+}
+
+void
+storage_end_rewrite(Relation rel, RewriteState state)
+{
+	rel->rd_stamroutine->end_heap_rewrite(state);
+}
+
+void
+storage_rewrite_tuple(Relation rel, RewriteState state, HeapTuple oldTuple,
+				   HeapTuple newTuple)
+{
+	rel->rd_stamroutine->rewrite_heap_tuple(state, oldTuple, newTuple);
+}
+
+bool
+storage_rewrite_dead_tuple(Relation rel, RewriteState state, HeapTuple oldTuple)
+{
+	return rel->rd_stamroutine->rewrite_heap_dead_tuple(state, oldTuple);
+}
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index ccdbe70ff6..1f9cfe9908 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -75,9 +75,8 @@ static void copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 			   TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
 static void reform_and_rewrite_tuple(HeapTuple tuple,
-						 TupleDesc oldTupDesc, TupleDesc newTupDesc,
-						 Datum *values, bool *isnull,
-						 bool newRelHasOids, RewriteState rwstate);
+						 Relation OldHeap, Relation NewHeap,
+						 Datum *values, bool *isnull, RewriteState rwstate);
 
 
 /*---------------------------------------------------------------------------
@@ -875,7 +874,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	is_system_catalog = IsSystemRelation(OldHeap);
 
 	/* Initialize the rewrite operation */
-	rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin, FreezeXid,
+	rwstate = storage_begin_rewrite(OldHeap, NewHeap, OldestXmin, FreezeXid,
 								 MultiXactCutoff, use_wal);
 
 	/*
@@ -1024,7 +1023,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 		{
 			tups_vacuumed += 1;
 			/* heap rewrite module still needs to see it... */
-			if (rewrite_heap_dead_tuple(rwstate, tuple))
+			if (storage_rewrite_dead_tuple(NewHeap, rwstate, tuple))
 			{
 				/* A previous recently-dead tuple is now known dead */
 				tups_vacuumed += 1;
@@ -1038,9 +1037,8 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 			tuplesort_putheaptuple(tuplesort, tuple);
 		else
 			reform_and_rewrite_tuple(tuple,
-									 oldTupDesc, newTupDesc,
-									 values, isnull,
-									 NewHeap->rd_rel->relhasoids, rwstate);
+									 OldHeap, NewHeap,
+									 values, isnull, rwstate);
 	}
 
 	if (indexScan != NULL)
@@ -1067,16 +1065,15 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 				break;
 
 			reform_and_rewrite_tuple(tuple,
-									 oldTupDesc, newTupDesc,
-									 values, isnull,
-									 NewHeap->rd_rel->relhasoids, rwstate);
+									 OldHeap, NewHeap,
+									 values, isnull, rwstate);
 		}
 
 		tuplesort_end(tuplesort);
 	}
 
 	/* Write out any remaining tuples, and fsync if needed */
-	end_heap_rewrite(rwstate);
+	storage_end_rewrite(NewHeap, rwstate);
 
 	/* Reset rd_toastoid just to be tidy --- it shouldn't be looked at again */
 	NewHeap->rd_toastoid = InvalidOid;
@@ -1704,10 +1701,11 @@ get_tables_to_cluster(MemoryContext cluster_context)
  */
 static void
 reform_and_rewrite_tuple(HeapTuple tuple,
-						 TupleDesc oldTupDesc, TupleDesc newTupDesc,
-						 Datum *values, bool *isnull,
-						 bool newRelHasOids, RewriteState rwstate)
+						 Relation OldHeap, Relation NewHeap,
+						 Datum *values, bool *isnull, RewriteState rwstate)
 {
+	TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
+	TupleDesc newTupDesc = RelationGetDescr(NewHeap);
 	HeapTuple	copiedTuple;
 	int			i;
 
@@ -1723,11 +1721,11 @@ reform_and_rewrite_tuple(HeapTuple tuple,
 	copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
 
 	/* Preserve OID, if any */
-	if (newRelHasOids)
+	if (NewHeap->rd_rel->relhasoids)
 		HeapTupleSetOid(copiedTuple, HeapTupleGetOid(tuple));
 
 	/* The heap rewrite module does the rest */
-	rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+	storage_rewrite_tuple(NewHeap, rwstate, tuple, copiedTuple);
 
 	heap_freetuple(copiedTuple);
 }
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 99b8cf67d8..9d54515c1e 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -211,4 +211,13 @@ extern HTSU_Result HeapTupleSatisfiesUpdate(StorageTuple stup, CommandId curcid,
 extern HTSV_Result HeapTupleSatisfiesVacuum(StorageTuple stup, TransactionId OldestXmin,
 						 Buffer buffer);
 
+/* in heap/rewriteheap.c */
+extern RewriteState begin_heap_rewrite(Relation OldHeap, Relation NewHeap,
+				   TransactionId OldestXmin, TransactionId FreezeXid,
+				   MultiXactId MultiXactCutoff, bool use_wal);
+extern void end_heap_rewrite(RewriteState state);
+extern void rewrite_heap_tuple(RewriteState state, HeapTuple oldTuple,
+				   HeapTuple newTuple);
+extern bool rewrite_heap_dead_tuple(RewriteState state, HeapTuple oldTuple);
+
 #endif							/* HEAPAM_H */
diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h
index 91ff36707a..8b1ee7a5a7 100644
--- a/src/include/access/rewriteheap.h
+++ b/src/include/access/rewriteheap.h
@@ -18,17 +18,6 @@
 #include "storage/relfilenode.h"
 #include "utils/relcache.h"
 
-/* struct definition is private to rewriteheap.c */
-typedef struct RewriteStateData *RewriteState;
-
-extern RewriteState begin_heap_rewrite(Relation OldHeap, Relation NewHeap,
-				   TransactionId OldestXmin, TransactionId FreezeXid,
-				   MultiXactId MultiXactCutoff, bool use_wal);
-extern void end_heap_rewrite(RewriteState state);
-extern void rewrite_heap_tuple(RewriteState state, HeapTuple oldTuple,
-				   HeapTuple newTuple);
-extern bool rewrite_heap_dead_tuple(RewriteState state, HeapTuple oldTuple);
-
 /*
  * On-Disk data format for an individual logical rewrite mapping.
  */
diff --git a/src/include/access/storage_common.h b/src/include/access/storage_common.h
index 2d0b1a3ccd..2676fc53e2 100644
--- a/src/include/access/storage_common.h
+++ b/src/include/access/storage_common.h
@@ -30,6 +30,8 @@ typedef void *StorageTuple;
 
 typedef struct BulkInsertStateData *BulkInsertState;
 
+/* struct definition is private to rewriteheap.c */
+typedef struct RewriteStateData *RewriteState;
 
 /*
  * slot storage routine functions
diff --git a/src/include/access/storageam.h b/src/include/access/storageam.h
index 8ced6c6a1f..1ee2d2ee66 100644
--- a/src/include/access/storageam.h
+++ b/src/include/access/storageam.h
@@ -120,4 +120,12 @@ extern BulkInsertState storage_getbulkinsertstate(Relation rel);
 extern void storage_freebulkinsertstate(Relation rel, BulkInsertState bistate);
 extern void storage_releasebulkinsertstate(Relation rel, BulkInsertState bistate);
 
+extern RewriteState storage_begin_rewrite(Relation OldHeap, Relation NewHeap,
+				   TransactionId OldestXmin, TransactionId FreezeXid,
+				   MultiXactId MultiXactCutoff, bool use_wal);
+extern void storage_end_rewrite(Relation rel, RewriteState state);
+extern void storage_rewrite_tuple(Relation rel, RewriteState state, HeapTuple oldTuple,
+				   HeapTuple newTuple);
+extern bool storage_rewrite_dead_tuple(Relation rel, RewriteState state, HeapTuple oldTuple);
+
 #endif
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
index d79aad71df..c112a97c03 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -84,6 +84,14 @@ typedef BulkInsertState (*GetBulkInsertState_function) (void);
 typedef void (*FreeBulkInsertState_function) (BulkInsertState bistate);
 typedef void (*ReleaseBulkInsertState_function) (BulkInsertState bistate);
 
+typedef RewriteState (*BeginHeapRewrite_function) (Relation OldHeap, Relation NewHeap,
+				   TransactionId OldestXmin, TransactionId FreezeXid,
+				   MultiXactId MultiXactCutoff, bool use_wal);
+typedef void (*EndHeapRewrite_function) (RewriteState state);
+typedef void (*RewriteHeapTuple_function) (RewriteState state, HeapTuple oldTuple,
+				   HeapTuple newTuple);
+typedef bool (*RewriteHeapDeadTuple_function) (RewriteState state, HeapTuple oldTuple);
+
 typedef StorageScanDesc(*ScanBegin_function) (Relation relation,
 											  Snapshot snapshot,
 											  int nkeys, ScanKey key,
@@ -158,6 +166,11 @@ typedef struct StorageAmRoutine
 	FreeBulkInsertState_function freebulkinsertstate;
 	ReleaseBulkInsertState_function releasebulkinsertstate;
 
+	BeginHeapRewrite_function begin_heap_rewrite;
+	EndHeapRewrite_function end_heap_rewrite;
+	RewriteHeapTuple_function rewrite_heap_tuple;
+	RewriteHeapDeadTuple_function rewrite_heap_dead_tuple;
+
 	/* Operations on relation scans */
 	ScanBegin_function scan_begin;
 	ScanGetParallelheapscandesc_function scan_get_parallelheapscandesc;
-- 
2.15.0.windows.1



  [application/octet-stream] 0011-Improve-tuple-locking-interface.patch (58.8K, ../../CAJrrPGc951F-R4Kfa4W47B5vHKeHsB2Y34zewp=b+AWSkF9RVA@mail.gmail.com/4-0011-Improve-tuple-locking-interface.patch)
  download | inline diff:
From 9b573b0ecd59086916e8af822234c3c640904c49 Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Wed, 27 Dec 2017 13:05:13 +1100
Subject: [PATCH 11/11] 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 storage 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 storage access methods.  Similar changes are
upcoming to tuple_update() and tuple_delete() interface methods.
---
 src/backend/access/heap/heapam.c            | 125 +++++++------
 src/backend/access/heap/heapam_storage.c    | 217 +++++++++++++++++++++-
 src/backend/access/heap/heapam_visibility.c |  20 +-
 src/backend/access/storage/storageam.c      |  11 +-
 src/backend/commands/trigger.c              |  67 +++----
 src/backend/executor/execMain.c             | 278 +---------------------------
 src/backend/executor/execReplication.c      |  36 ++--
 src/backend/executor/nodeLockRows.c         |  67 +++----
 src/backend/executor/nodeModifyTable.c      | 152 ++++++++++-----
 src/include/access/heapam.h                 |   9 +-
 src/include/access/storage_common.h         |   3 +
 src/include/access/storageam.h              |   8 +-
 src/include/access/storageamapi.h           |   6 +-
 src/include/executor/executor.h             |   6 +-
 src/include/nodes/lockoptions.h             |   5 +
 src/include/utils/snapshot.h                |   1 +
 16 files changed, 516 insertions(+), 495 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 04d9fabd9a..1dadf6fc34 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3204,6 +3204,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;
@@ -3217,6 +3218,8 @@ l1:
 			UnlockTupleTuplock(relation, &(tp.t_self), LockTupleExclusive);
 		if (vmbuffer != InvalidBuffer)
 			ReleaseBuffer(vmbuffer);
+		if (result == HeapTupleUpdated && ItemPointerEquals(tid, &hufd->ctid))
+			result = HeapTupleDeleted;
 		return result;
 	}
 
@@ -3426,6 +3429,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;
@@ -3845,6 +3852,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;
@@ -3863,6 +3871,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;
 	}
 
@@ -4481,6 +4491,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;
@@ -4533,6 +4547,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
@@ -4545,12 +4560,13 @@ get_mxact_status_for_lock(LockTupleMode mode, bool is_update)
  * See README.tuplock for a thorough explanation of this mechanism.
  */
 HTSU_Result
-heap_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * stuple,
+heap_lock_tuple(Relation relation, HeapTuple tuple,
 				CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 				bool follow_updates,
 				Buffer *buffer, HeapUpdateFailureData *hufd)
 {
 	HTSU_Result result;
+	ItemPointer tid = &(tuple->t_self);
 	ItemId		lp;
 	Page		page;
 	Buffer		vmbuffer = InvalidBuffer;
@@ -4563,9 +4579,6 @@ heap_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * stuple,
 	bool		first_time = true;
 	bool		have_tuple_lock = false;
 	bool		cleared_all_frozen = false;
-	HeapTupleData tuple;
-
-	Assert(stuple != NULL);
 
 	*buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
 	block = ItemPointerGetBlockNumber(tid);
@@ -4585,13 +4598,12 @@ heap_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * stuple,
 	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid));
 	Assert(ItemIdIsNormal(lp));
 
-	tuple.t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	tuple.t_len = ItemIdGetLength(lp);
-	tuple.t_tableOid = RelationGetRelid(relation);
-	ItemPointerCopy(tid, &tuple.t_self);
+	tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
+	tuple->t_len = ItemIdGetLength(lp);
+	tuple->t_tableOid = RelationGetRelid(relation);
 
 l3:
-	result = HeapTupleSatisfiesUpdate(&tuple, cid, *buffer);
+	result = HeapTupleSatisfiesUpdate(tuple, cid, *buffer);
 
 	if (result == HeapTupleInvisible)
 	{
@@ -4604,7 +4616,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;
@@ -4613,10 +4625,10 @@ l3:
 		ItemPointerData t_ctid;
 
 		/* must copy state data before unlocking buffer */
-		xwait = HeapTupleHeaderGetRawXmax(tuple.t_data);
-		infomask = tuple.t_data->t_infomask;
-		infomask2 = tuple.t_data->t_infomask2;
-		ItemPointerCopy(&tuple.t_data->t_ctid, &t_ctid);
+		xwait = HeapTupleHeaderGetRawXmax(tuple->t_data);
+		infomask = tuple->t_data->t_infomask;
+		infomask2 = tuple->t_data->t_infomask2;
+		ItemPointerCopy(&tuple->t_data->t_ctid, &t_ctid);
 
 		LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
 
@@ -4748,7 +4760,7 @@ l3:
 				{
 					HTSU_Result res;
 
-					res = heap_lock_updated_tuple(relation, &tuple, &t_ctid,
+					res = heap_lock_updated_tuple(relation, tuple, &t_ctid,
 												  GetCurrentTransactionId(),
 												  mode);
 					if (res != HeapTupleMayBeUpdated)
@@ -4769,8 +4781,8 @@ l3:
 				 * now need to follow the update chain to lock the new
 				 * versions.
 				 */
-				if (!HeapTupleHeaderIsOnlyLocked(tuple.t_data) &&
-					((tuple.t_data->t_infomask2 & HEAP_KEYS_UPDATED) ||
+				if (!HeapTupleHeaderIsOnlyLocked(tuple->t_data) &&
+					((tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED) ||
 					 !updated))
 					goto l3;
 
@@ -4801,8 +4813,8 @@ l3:
 				 * Make sure it's still an appropriate lock, else start over.
 				 * See above about allowing xmax to change.
 				 */
-				if (!HEAP_XMAX_IS_LOCKED_ONLY(tuple.t_data->t_infomask) ||
-					HEAP_XMAX_IS_EXCL_LOCKED(tuple.t_data->t_infomask))
+				if (!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask) ||
+					HEAP_XMAX_IS_EXCL_LOCKED(tuple->t_data->t_infomask))
 					goto l3;
 				require_sleep = false;
 			}
@@ -4824,8 +4836,8 @@ l3:
 					 * meantime, start over.
 					 */
 					LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-					if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
-						!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple.t_data),
+					if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
+						!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple->t_data),
 											 xwait))
 						goto l3;
 
@@ -4838,9 +4850,9 @@ l3:
 				LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
 
 				/* if the xmax changed in the meantime, start over */
-				if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
+				if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
 					!TransactionIdEquals(
-										 HeapTupleHeaderGetRawXmax(tuple.t_data),
+										 HeapTupleHeaderGetRawXmax(tuple->t_data),
 										 xwait))
 					goto l3;
 				/* otherwise, we're good */
@@ -4865,11 +4877,11 @@ l3:
 		{
 			/* ... but if the xmax changed in the meantime, start over */
 			LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-			if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
-				!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple.t_data),
+			if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
+				!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple->t_data),
 									 xwait))
 				goto l3;
-			Assert(HEAP_XMAX_IS_LOCKED_ONLY(tuple.t_data->t_infomask));
+			Assert(HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask));
 			require_sleep = false;
 		}
 
@@ -4884,7 +4896,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;
@@ -4926,7 +4938,7 @@ l3:
 				{
 					case LockWaitBlock:
 						MultiXactIdWait((MultiXactId) xwait, status, infomask,
-										relation, &tuple.t_self, XLTW_Lock, NULL);
+										relation, &tuple->t_self, XLTW_Lock, NULL);
 						break;
 					case LockWaitSkip:
 						if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
@@ -4967,7 +4979,7 @@ l3:
 				switch (wait_policy)
 				{
 					case LockWaitBlock:
-						XactLockTableWait(xwait, relation, &tuple.t_self,
+						XactLockTableWait(xwait, relation, &tuple->t_self,
 										  XLTW_Lock);
 						break;
 					case LockWaitSkip:
@@ -4994,7 +5006,7 @@ l3:
 			{
 				HTSU_Result res;
 
-				res = heap_lock_updated_tuple(relation, &tuple, &t_ctid,
+				res = heap_lock_updated_tuple(relation, tuple, &t_ctid,
 											  GetCurrentTransactionId(),
 											  mode);
 				if (res != HeapTupleMayBeUpdated)
@@ -5013,8 +5025,8 @@ l3:
 			 * other xact could update this tuple before we get to this point.
 			 * Check for xmax change, and start over if so.
 			 */
-			if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
-				!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple.t_data),
+			if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
+				!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple->t_data),
 									 xwait))
 				goto l3;
 
@@ -5028,7 +5040,7 @@ l3:
 				 * don't check for this in the multixact case, because some
 				 * locker transactions might still be running.
 				 */
-				UpdateXmaxHintBits(tuple.t_data, *buffer, xwait);
+				UpdateXmaxHintBits(tuple->t_data, *buffer, xwait);
 			}
 		}
 
@@ -5040,10 +5052,12 @@ l3:
 		 * at all for whatever reason.
 		 */
 		if (!require_sleep ||
-			(tuple.t_data->t_infomask & HEAP_XMAX_INVALID) ||
-			HEAP_XMAX_IS_LOCKED_ONLY(tuple.t_data->t_infomask) ||
-			HeapTupleHeaderIsOnlyLocked(tuple.t_data))
+			(tuple->t_data->t_infomask & HEAP_XMAX_INVALID) ||
+			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;
 	}
@@ -5052,12 +5066,12 @@ failed:
 	if (result != HeapTupleMayBeUpdated)
 	{
 		Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated ||
-			   result == HeapTupleWouldBlock);
-		Assert(!(tuple.t_data->t_infomask & HEAP_XMAX_INVALID));
-		hufd->ctid = tuple.t_data->t_ctid;
-		hufd->xmax = HeapTupleHeaderGetUpdateXid(tuple.t_data);
+			   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);
 		if (result == HeapTupleSelfUpdated)
-			hufd->cmax = HeapTupleHeaderGetCmax(tuple.t_data);
+			hufd->cmax = HeapTupleHeaderGetCmax(tuple->t_data);
 		else
 			hufd->cmax = InvalidCommandId;
 		goto out_locked;
@@ -5080,8 +5094,8 @@ failed:
 		goto l3;
 	}
 
-	xmax = HeapTupleHeaderGetRawXmax(tuple.t_data);
-	old_infomask = tuple.t_data->t_infomask;
+	xmax = HeapTupleHeaderGetRawXmax(tuple->t_data);
+	old_infomask = tuple->t_data->t_infomask;
 
 	/*
 	 * If this is the first possibly-multixact-able operation in the current
@@ -5098,7 +5112,7 @@ failed:
 	 * not modify the tuple just yet, because that would leave it in the wrong
 	 * state if multixact.c elogs.
 	 */
-	compute_new_xmax_infomask(xmax, old_infomask, tuple.t_data->t_infomask2,
+	compute_new_xmax_infomask(xmax, old_infomask, tuple->t_data->t_infomask2,
 							  GetCurrentTransactionId(), mode, false,
 							  &xid, &new_infomask, &new_infomask2);
 
@@ -5114,13 +5128,13 @@ failed:
 	 * Also reset the HOT UPDATE bit, but only if there's no update; otherwise
 	 * we would break the HOT chain.
 	 */
-	tuple.t_data->t_infomask &= ~HEAP_XMAX_BITS;
-	tuple.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
-	tuple.t_data->t_infomask |= new_infomask;
-	tuple.t_data->t_infomask2 |= new_infomask2;
+	tuple->t_data->t_infomask &= ~HEAP_XMAX_BITS;
+	tuple->t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+	tuple->t_data->t_infomask |= new_infomask;
+	tuple->t_data->t_infomask2 |= new_infomask2;
 	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
-		HeapTupleHeaderClearHotUpdated(tuple.t_data);
-	HeapTupleHeaderSetXmax(tuple.t_data, xid);
+		HeapTupleHeaderClearHotUpdated(tuple->t_data);
+	HeapTupleHeaderSetXmax(tuple->t_data, xid);
 
 	/*
 	 * Make sure there is no forward chain link in t_ctid.  Note that in the
@@ -5130,7 +5144,7 @@ failed:
 	 * the tuple as well.
 	 */
 	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
-		tuple.t_data->t_ctid = *tid;
+		tuple->t_data->t_ctid = *tid;
 
 	/* Clear only the all-frozen bit on visibility map if needed */
 	if (PageIsAllVisible(page) &&
@@ -5161,10 +5175,10 @@ failed:
 		XLogBeginInsert();
 		XLogRegisterBuffer(0, *buffer, REGBUF_STANDARD);
 
-		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple.t_self);
+		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self);
 		xlrec.locking_xid = xid;
 		xlrec.infobits_set = compute_infobits(new_infomask,
-											  tuple.t_data->t_infomask2);
+											  tuple->t_data->t_infomask2);
 		xlrec.flags = cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
 		XLogRegisterData((char *) &xlrec, SizeOfHeapLock);
 
@@ -5198,7 +5212,6 @@ out_unlocked:
 	if (have_tuple_lock)
 		UnlockTupleTuplock(relation, tid, mode);
 
-	*stuple = heap_copytuple(&tuple);
 	return result;
 }
 
@@ -5932,6 +5945,10 @@ next:
 	result = HeapTupleMayBeUpdated;
 
 out_locked:
+
+	if (result == HeapTupleUpdated && ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid))
+		result = HeapTupleDeleted;
+
 	UnlockReleaseBuffer(buf);
 
 	if (vmbuffer != InvalidBuffer)
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index 2a39e0cc1e..a787f6fe6b 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -25,8 +25,10 @@
 #include "access/rewriteheap.h"
 #include "access/storageamapi.h"
 #include "pgstat.h"
+#include "storage/lmgr.h"
 #include "utils/builtins.h"
 #include "utils/rel.h"
+#include "utils/tqual.h"
 
 
 /* ----------------------------------------------------------------
@@ -281,6 +283,219 @@ heapam_fetch_tuple_from_offset(StorageScanDesc sscan, BlockNumber blkno, OffsetN
 	return &(scan->rs_ctup);
 }
 
+/*
+ * Locks tuple and fetches its newest version and TID.
+ *
+ *	relation - table containing tuple
+ *	*tid - TID of tuple to lock (rest of struct need not be valid)
+ *	snapshot - snapshot indentifying required version (used for assert check only)
+ *	*stuple - tuple to be returned
+ *	cid - current command ID (used for visibility test, and stored into
+ *		  tuple's cmax if lock is successful)
+ *	mode - indicates if shared or exclusive tuple lock is desired
+ *	wait_policy - what to do if tuple lock is not available
+ *	flags – indicating how do we handle updated tuples
+ *	*hufd - filled in failure cases
+ *
+ * Function result may be:
+ *	HeapTupleMayBeUpdated: lock was successfully acquired
+ *	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
+ * *hufd with the tuple's t_ctid, t_xmax (resolving a possible MultiXact,
+ * if necessary), and t_cmax (the last only for HeapTupleSelfUpdated,
+ * since we cannot obtain cmax from a combocid generated by another
+ * transaction).
+ * See comments for struct HeapUpdateFailureData for additional info.
+ */
+static HTSU_Result
+heapam_lock_tuple(Relation relation, ItemPointer tid, Snapshot snapshot,
+				StorageTuple *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,
+		(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 (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((StorageTuple) &tuple, snapshot, InvalidBuffer));
+
+	*stuple = heap_copytuple(&tuple);
+	ReleaseBuffer(buffer);
+
+	return result;
+}
+
 
 Datum
 heapam_storage_handler(PG_FUNCTION_ARGS)
@@ -320,7 +535,7 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 	amroutine->tuple_insert = heapam_heap_insert;
 	amroutine->tuple_delete = heapam_heap_delete;
 	amroutine->tuple_update = heapam_heap_update;
-	amroutine->tuple_lock = heap_lock_tuple;
+	amroutine->tuple_lock = heapam_lock_tuple;
 	amroutine->multi_insert = heap_multi_insert;
 
 	amroutine->get_tuple_data = heapam_get_tuple_data;
diff --git a/src/backend/access/heap/heapam_visibility.c b/src/backend/access/heap/heapam_visibility.c
index 2841197960..6a56a694fa 100644
--- a/src/backend/access/heap/heapam_visibility.c
+++ b/src/backend/access/heap/heapam_visibility.c
@@ -548,7 +548,11 @@ HeapTupleSatisfiesUpdate(StorageTuple 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)
@@ -589,7 +593,12 @@ HeapTupleSatisfiesUpdate(StorageTuple 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
@@ -645,7 +654,12 @@ HeapTupleSatisfiesUpdate(StorageTuple 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/storage/storageam.c b/src/backend/access/storage/storageam.c
index 014dc6d265..cb6533de66 100644
--- a/src/backend/access/storage/storageam.c
+++ b/src/backend/access/storage/storageam.c
@@ -41,13 +41,14 @@ storage_fetch(Relation relation,
  *	storage_lock_tuple - lock a tuple in shared or exclusive mode
  */
 HTSU_Result
-storage_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * stuple,
-				   CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
-				   bool follow_updates, Buffer *buffer, HeapUpdateFailureData *hufd)
+storage_lock_tuple(Relation relation, ItemPointer tid, Snapshot snapshot,
+				   StorageTuple *stuple, CommandId cid, LockTupleMode mode,
+				   LockWaitPolicy wait_policy, uint8 flags,
+				   HeapUpdateFailureData *hufd)
 {
-	return relation->rd_stamroutine->tuple_lock(relation, tid, stuple,
+	return relation->rd_stamroutine->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 0a58ea16b6..7d76589659 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -3019,8 +3019,6 @@ GetTupleForTrigger(EState *estate,
 	Relation	relation = relinfo->ri_RelationDesc;
 	StorageTuple tuple;
 	HeapTuple	result;
-	Buffer		buffer;
-	tuple_data	t_data;
 
 	if (newSlot != NULL)
 	{
@@ -3035,11 +3033,11 @@ GetTupleForTrigger(EState *estate,
 		/*
 		 * lock tuple for update
 		 */
-ltrmark:;
-		test = storage_lock_tuple(relation, tid, &tuple,
+		test = storage_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)
 		{
@@ -3060,63 +3058,54 @@ 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")));
-				t_data = relation->rd_stamroutine->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;
@@ -3146,9 +3135,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 22f225ac8a..432b84c9fb 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -2462,9 +2462,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)
@@ -2477,32 +2475,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, StorageTuple tuple)
 {
 	TupleTableSlot *slot;
-	StorageTuple 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 = storage_tuple_get_data(relation, copyTuple, TID);
-	*tid = t_data.tid;
-
 	/*
 	 * Need to run a recheck subquery.  Initialize or reinitialize EPQ state.
 	 */
@@ -2512,7 +2490,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
@@ -2544,256 +2522,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.
- */
-StorageTuple
-EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
-				  LockWaitPolicy wait_policy,
-				  ItemPointer tid, TransactionId priorXmax)
-{
-	StorageTuple 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 (storage_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 = storage_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 = storage_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 0 //hari
-					/* Should not encounter speculative tuple on recheck */
-					Assert(!HeapTupleHeaderIsSpeculative(tuple.t_data));
-#endif
-					t_data = storage_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.
-		 */
-		t_data = storage_tuple_get_data(relation, tuple, CTID);
-		ctid = t_data.tid;
-		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 = storage_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 d5492fd260..9b4500822c 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;
 		StorageTuple locktup;
 
 		PushActiveSnapshot(GetLatestSnapshot());
 
-		res = storage_lock_tuple(rel, &(outslot->tts_tid), &locktup, GetCurrentCommandId(false),
+		res = storage_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();
@@ -196,6 +194,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:
@@ -274,21 +278,19 @@ retry:
 	/* Found tuple, try to lock it in the lockmode. */
 	if (found)
 	{
-		Buffer		buf;
 		HeapUpdateFailureData hufd;
 		HTSU_Result res;
 		StorageTuple locktup;
 
 		PushActiveSnapshot(GetLatestSnapshot());
 
-		res = storage_lock_tuple(rel, &(outslot->tts_tid), &locktup, GetCurrentCommandId(false),
+		res = storage_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);
 
@@ -304,6 +306,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 2da5240d24..bc85dc6e95 100644
--- a/src/backend/executor/nodeLockRows.c
+++ b/src/backend/executor/nodeLockRows.c
@@ -79,13 +79,11 @@ lnext:
 		Datum		datum;
 		bool		isNull;
 		StorageTuple tuple;
-		Buffer		buffer;
 		HeapUpdateFailureData hufd;
 		LockTupleMode lockmode;
 		HTSU_Result test;
 		StorageTuple copyTuple;
 		ItemPointerData tid;
-		tuple_data	t_data;
 
 		/* clear any leftover test tuple for this rel */
 		testTuple = (StorageTuple) (&(node->lr_curtuples[erm->rti - 1]));
@@ -183,12 +181,12 @@ lnext:
 				break;
 		}
 
-		test = storage_lock_tuple(erm->relation, &tid, &tuple,
-								  estate->es_output_cid,
-								  lockmode, erm->waitPolicy, true,
-								  &buffer, &hufd);
-		if (BufferIsValid(buffer))
-			ReleaseBuffer(buffer);
+		test = storage_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,38 +231,19 @@ lnext:
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("could not serialize access due to concurrent update")));
-				t_data = erm->relation->rd_stamroutine->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_stamroutine->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;
+				/* skip lock */
+				goto lnext;
 
-				/* Continue loop until we have all target tuples */
-				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; 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 86bf30bd06..32c9d9a85f 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -203,7 +203,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_stamroutine, tuple, estate->es_snapshot, buffer))
 	{
 		tuple_data	t_data = storage_tuple_get_data(rel, tuple, XMIN);
@@ -219,7 +220,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);
 }
 
 /*
@@ -678,6 +680,7 @@ ExecDelete(ModifyTableState *mtstate,
 	HTSU_Result result;
 	HeapUpdateFailureData hufd;
 	TupleTableSlot *slot = NULL;
+	StorageTuple	tuple;
 
 	/*
 	 * get information on the (current) result relation
@@ -759,6 +762,35 @@ ldelete:;
 								estate->es_crosscheck_snapshot,
 								true /* wait for commit */ ,
 								&hufd);
+
+		if (result == HeapTupleUpdated && !IsolationUsesXactSnapshot())
+		{
+			result = storage_lock_tuple(resultRelationDesc, tupleid,
+										estate->es_snapshot,
+										&tuple, estate->es_output_cid,
+										LockTupleExclusive, LockWaitBlock,
+										TUPLE_LOCK_FLAG_FIND_LAST_VERSION,
+										&hufd);
+
+			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:
@@ -804,23 +836,16 @@ ldelete:;
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("could not serialize access 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;
-					}
-				}
+				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("could not serialize access due to concurrent delete")));
 				/* tuple already deleted; nothing to do */
 				return NULL;
 
@@ -1059,6 +1084,37 @@ lreplace:;
 								&hufd, &lockmode,
 								ExecInsertIndexTuples,
 								&recheckIndexes);
+
+		if (result == HeapTupleUpdated && !IsolationUsesXactSnapshot())
+		{
+			result = storage_lock_tuple(resultRelationDesc, tupleid,
+										estate->es_snapshot,
+										&tuple, estate->es_output_cid,
+										lockmode, LockWaitBlock,
+										TUPLE_LOCK_FLAG_FIND_LAST_VERSION,
+										&hufd);
+
+			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:
@@ -1103,25 +1159,16 @@ lreplace:;
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("could not serialize access 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;
-					}
-				}
+				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("could not serialize access due to concurrent delete")));
 				/* tuple already deleted; nothing to do */
 				return NULL;
 
@@ -1190,8 +1237,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);
@@ -1202,8 +1249,12 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	 * previous conclusion that the tuple is conclusively committed is not
 	 * true anymore.
 	 */
-	test = storage_lock_tuple(relation, conflictTid, &tuple, estate->es_output_cid,
-							  lockmode, LockWaitBlock, false, &buffer, &hufd);
+	InitDirtySnapshot(snapshot);
+	test = storage_lock_tuple(relation, conflictTid,
+							  &snapshot,
+							  /*estate->es_snapshot,*/
+							  &tuple, estate->es_output_cid,
+							  lockmode, LockWaitBlock, 0, &hufd);
 	switch (test)
 	{
 		case HeapTupleMayBeUpdated:
@@ -1260,8 +1311,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;
 
@@ -1290,10 +1348,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
@@ -1308,8 +1366,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 */
@@ -1355,8 +1411,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 9d54515c1e..d824c8571e 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;
 
 
@@ -162,10 +163,10 @@ extern HTSU_Result heap_update(Relation relation, ItemPointer otid,
 			HeapTuple newtup,
 			CommandId cid, Snapshot crosscheck, bool wait,
 			HeapUpdateFailureData *hufd, LockTupleMode *lockmode);
-extern HTSU_Result heap_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * tuple,
-				CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
-				bool follow_update,
-				Buffer *buffer, HeapUpdateFailureData *hufd);
+extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
+			CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
+			bool follow_updates,
+			Buffer *buffer, HeapUpdateFailureData *hufd);
 
 extern void heap_inplace_update(Relation relation, HeapTuple tuple);
 extern bool heap_freeze_tuple(HeapTupleHeader tuple,
diff --git a/src/include/access/storage_common.h b/src/include/access/storage_common.h
index 2676fc53e2..ff4125b5e4 100644
--- a/src/include/access/storage_common.h
+++ b/src/include/access/storage_common.h
@@ -119,6 +119,9 @@ static inline void
 SetHintBits(HeapTupleHeader tuple, Buffer buffer,
 			uint16 infomask, TransactionId xid)
 {
+	if (!BufferIsValid(buffer))
+		return;
+
 	if (TransactionIdIsValid(xid))
 	{
 		/* NB: xid must be known committed here! */
diff --git a/src/include/access/storageam.h b/src/include/access/storageam.h
index 1ee2d2ee66..1983806581 100644
--- a/src/include/access/storageam.h
+++ b/src/include/access/storageam.h
@@ -85,10 +85,10 @@ extern bool storage_hot_search_buffer(ItemPointer tid, Relation relation, Buffer
 extern bool storage_hot_search(ItemPointer tid, Relation relation, Snapshot snapshot,
 				   bool *all_dead);
 
-extern HTSU_Result storage_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * stuple,
-				   CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
-				   bool follow_updates,
-				   Buffer *buffer, HeapUpdateFailureData *hufd);
+extern HTSU_Result storage_lock_tuple(Relation relation, ItemPointer tid, Snapshot snapshot,
+				   StorageTuple *stuple, CommandId cid, LockTupleMode mode,
+				   LockWaitPolicy wait_policy, uint8 flags,
+				   HeapUpdateFailureData *hufd);
 
 extern Oid storage_insert(Relation relation, TupleTableSlot *slot, CommandId cid,
 			   int options, BulkInsertState bistate, InsertIndexTuples IndexFunc,
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
index c112a97c03..4c1a6eefb8 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -59,12 +59,12 @@ typedef bool (*TupleFetch_function) (Relation relation,
 
 typedef HTSU_Result (*TupleLock_function) (Relation relation,
 										   ItemPointer tid,
-										   StorageTuple * tuple,
+										   Snapshot snapshot,
+										   StorageTuple *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 cea687d328..87a710e3f8 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -196,11 +196,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 StorageTuple EvalPlanQualFetch(EState *estate, Relation relation,
-									  int lockmode, LockWaitPolicy wait_policy, ItemPointer tid,
-									  TransactionId priorXmax);
+			 Relation relation, Index rti, StorageTuple 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 e0981dac6f..ae9a2d3ac8 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 3896349b39..9590744683 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;
-- 
2.15.0.windows.1



  [application/octet-stream] 0001-Change-Create-Access-method-to-include-storage-handl.patch (9.3K, ../../CAJrrPGc951F-R4Kfa4W47B5vHKeHsB2Y34zewp=b+AWSkF9RVA@mail.gmail.com/5-0001-Change-Create-Access-method-to-include-storage-handl.patch)
  download | inline diff:
From 88ab3906d59cb28eda0bfb97f9ea3f05976c20fd Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Tue, 29 Aug 2017 19:45:30 +1000
Subject: [PATCH 01/11] Change Create Access method to include storage handler

Add the support of storage handler as an access method
---
 src/backend/commands/amcmds.c            | 17 ++++++++++++++---
 src/backend/parser/gram.y                | 11 +++++++++--
 src/backend/utils/adt/pseudotypes.c      |  1 +
 src/include/catalog/pg_am.h              |  1 +
 src/include/catalog/pg_proc.h            |  4 ++++
 src/include/catalog/pg_type.h            |  2 ++
 src/test/regress/expected/opr_sanity.out | 19 ++++++++++++++++---
 src/test/regress/sql/opr_sanity.sql      | 16 +++++++++++++---
 8 files changed, 60 insertions(+), 11 deletions(-)

diff --git a/src/backend/commands/amcmds.c b/src/backend/commands/amcmds.c
index 7e0a9aa0fd..33079c1c16 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -29,7 +29,7 @@
 #include "utils/syscache.h"
 
 
-static Oid	lookup_index_am_handler_func(List *handler_name, char amtype);
+static Oid	lookup_am_handler_func(List *handler_name, char amtype);
 static const char *get_am_type_string(char amtype);
 
 
@@ -72,7 +72,7 @@ CreateAccessMethod(CreateAmStmt *stmt)
 	/*
 	 * Get the handler function oid, verifying the AM type while at it.
 	 */
-	amhandler = lookup_index_am_handler_func(stmt->handler_name, stmt->amtype);
+	amhandler = lookup_am_handler_func(stmt->handler_name, stmt->amtype);
 
 	/*
 	 * Insert tuple into pg_am.
@@ -225,6 +225,8 @@ get_am_type_string(char amtype)
 	{
 		case AMTYPE_INDEX:
 			return "INDEX";
+		case AMTYPE_STORAGE:
+			return "STORAGE";
 		default:
 			/* shouldn't happen */
 			elog(ERROR, "invalid access method type '%c'", amtype);
@@ -239,7 +241,7 @@ get_am_type_string(char amtype)
  * This function either return valid function Oid or throw an error.
  */
 static Oid
-lookup_index_am_handler_func(List *handler_name, char amtype)
+lookup_am_handler_func(List *handler_name, char amtype)
 {
 	Oid			handlerOid;
 	static const Oid funcargtypes[1] = {INTERNALOID};
@@ -263,6 +265,15 @@ lookup_index_am_handler_func(List *handler_name, char amtype)
 								NameListToString(handler_name),
 								"index_am_handler")));
 			break;
+			/* XXX refactor duplicate error */
+		case AMTYPE_STORAGE:
+			if (get_func_rettype(handlerOid) != STORAGE_AM_HANDLEROID)
+				ereport(ERROR,
+						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+						 errmsg("function %s must return type %s",
+								NameListToString(handler_name),
+								"storage_am_handler")));
+			break;
 		default:
 			elog(ERROR, "unrecognized access method type \"%c\"", amtype);
 	}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ebfc94f896..25f397be6a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -321,6 +321,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		OptSchemaName
 %type <list>	OptSchemaEltList
 
+%type <chr>		am_type
+
 %type <boolean> TriggerForSpec TriggerForType
 %type <ival>	TriggerActionTime
 %type <list>	TriggerEvents TriggerOneEvent
@@ -5279,16 +5281,21 @@ row_security_cmd:
  *
  *****************************************************************************/
 
-CreateAmStmt: CREATE ACCESS METHOD name TYPE_P INDEX HANDLER handler_name
+CreateAmStmt: CREATE ACCESS METHOD name TYPE_P am_type HANDLER handler_name
 				{
 					CreateAmStmt *n = makeNode(CreateAmStmt);
 					n->amname = $4;
 					n->handler_name = $8;
-					n->amtype = AMTYPE_INDEX;
+					n->amtype = $6;
 					$$ = (Node *) n;
 				}
 		;
 
+am_type:
+			INDEX			{ $$ = AMTYPE_INDEX; }
+		|	STORAGE			{ $$ = AMTYPE_STORAGE; }
+		;
+
 /*****************************************************************************
  *
  *		QUERIES :
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index be793539a3..0a7e0a33e8 100644
--- a/src/backend/utils/adt/pseudotypes.c
+++ b/src/backend/utils/adt/pseudotypes.c
@@ -418,3 +418,4 @@ PSEUDOTYPE_DUMMY_IO_FUNCS(internal);
 PSEUDOTYPE_DUMMY_IO_FUNCS(opaque);
 PSEUDOTYPE_DUMMY_IO_FUNCS(anyelement);
 PSEUDOTYPE_DUMMY_IO_FUNCS(anynonarray);
+PSEUDOTYPE_DUMMY_IO_FUNCS(storage_am_handler);
diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h
index e021f5b894..dd9c263ade 100644
--- a/src/include/catalog/pg_am.h
+++ b/src/include/catalog/pg_am.h
@@ -59,6 +59,7 @@ typedef FormData_pg_am *Form_pg_am;
  * ----------------
  */
 #define AMTYPE_INDEX					'i' /* index access method */
+#define AMTYPE_STORAGE                  's' /* storage access method */
 
 /* ----------------
  *		initial contents of pg_am
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 830bab37ea..38085d0b53 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3877,6 +3877,10 @@ DATA(insert OID = 326  (  index_am_handler_in	PGNSP PGUID 12 1 0 0 0 f f f f f f
 DESCR("I/O");
 DATA(insert OID = 327  (  index_am_handler_out	PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2275 "325" _null_ _null_ _null_ _null_ _null_ index_am_handler_out _null_ _null_ _null_ ));
 DESCR("I/O");
+DATA(insert OID = 3425  (  storage_am_handler_in	PGNSP PGUID 12 1 0 0 0 f f f f f f i s 1 0 3998 "2275" _null_ _null_ _null_ _null_ _null_ storage_am_handler_in _null_ _null_ _null_ ));
+DESCR("I/O");
+DATA(insert OID = 3426  (  storage_am_handler_out	PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2275 "3998" _null_ _null_ _null_ _null_ _null_ storage_am_handler_out _null_ _null_ _null_ ));
+DESCR("I/O");
 DATA(insert OID = 3311 (  tsm_handler_in	PGNSP PGUID 12 1 0 0 0 f f f f f f i s 1 0 3310 "2275" _null_ _null_ _null_ _null_ _null_ tsm_handler_in _null_ _null_ _null_ ));
 DESCR("I/O");
 DATA(insert OID = 3312 (  tsm_handler_out	PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2275 "3310" _null_ _null_ _null_ _null_ _null_ tsm_handler_out _null_ _null_ _null_ ));
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index e3551440a0..750b8b4533 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -708,6 +708,8 @@ DATA(insert OID = 3115 ( fdw_handler	PGNSP PGUID  4 t p P f t \054 0 0 0 fdw_han
 #define FDW_HANDLEROID	3115
 DATA(insert OID = 325 ( index_am_handler	PGNSP PGUID  4 t p P f t \054 0 0 0 index_am_handler_in index_am_handler_out - - - - - i p f 0 -1 0 0 _null_ _null_ _null_ ));
 #define INDEX_AM_HANDLEROID 325
+DATA(insert OID = 3998 ( storage_am_handler	PGNSP PGUID 4 t p P f t \054 0 0 0 storage_am_handler_in storage_am_handler_out - - - - - i p f 0 -1 0 0 _null_ _null_ _null_ ));
+#define STORAGE_AM_HANDLEROID	3998
 DATA(insert OID = 3310 ( tsm_handler	PGNSP PGUID  4 t p P f t \054 0 0 0 tsm_handler_in tsm_handler_out - - - - - i p f 0 -1 0 0 _null_ _null_ _null_ ));
 #define TSM_HANDLEROID	3310
 DATA(insert OID = 3831 ( anyrange		PGNSP PGUID  -1 f p P f t \054 0 0 0 anyrange_in anyrange_out - - - - - d x f 0 -1 0 0 _null_ _null_ _null_ ));
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 684f7f20a8..3113966415 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1713,11 +1713,24 @@ WHERE p1.amhandler = 0;
 -----+--------
 (0 rows)
 
--- Check for amhandler functions with the wrong signature
+-- Check for index amhandler functions with the wrong signature
 SELECT p1.oid, p1.amname, p2.oid, p2.proname
 FROM pg_am AS p1, pg_proc AS p2
-WHERE p2.oid = p1.amhandler AND
-    (p2.prorettype != 'index_am_handler'::regtype OR p2.proretset
+WHERE p2.oid = p1.amhandler AND p1.amtype = 'i' AND
+    (p2.prorettype != 'index_am_handler'::regtype
+     OR p2.proretset
+     OR p2.pronargs != 1
+     OR p2.proargtypes[0] != 'internal'::regtype);
+ oid | amname | oid | proname 
+-----+--------+-----+---------
+(0 rows)
+
+-- Check for storage amhandler functions with the wrong signature
+SELECT p1.oid, p1.amname, p2.oid, p2.proname
+FROM pg_am AS p1, pg_proc AS p2
+WHERE p2.oid = p1.amhandler AND p1.amtype = 's' AND
+    (p2.prorettype != 'storage_am_handler'::regtype
+     OR p2.proretset
      OR p2.pronargs != 1
      OR p2.proargtypes[0] != 'internal'::regtype);
  oid | amname | oid | proname 
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index e8fdf8454d..bb1570c94f 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -1155,15 +1155,25 @@ SELECT p1.oid, p1.amname
 FROM pg_am AS p1
 WHERE p1.amhandler = 0;
 
--- Check for amhandler functions with the wrong signature
+-- Check for index amhandler functions with the wrong signature
 
 SELECT p1.oid, p1.amname, p2.oid, p2.proname
 FROM pg_am AS p1, pg_proc AS p2
-WHERE p2.oid = p1.amhandler AND
-    (p2.prorettype != 'index_am_handler'::regtype OR p2.proretset
+WHERE p2.oid = p1.amhandler AND p1.amtype = 'i' AND
+    (p2.prorettype != 'index_am_handler'::regtype
+     OR p2.proretset
      OR p2.pronargs != 1
      OR p2.proargtypes[0] != 'internal'::regtype);
 
+-- Check for storage amhandler functions with the wrong signature
+
+SELECT p1.oid, p1.amname, p2.oid, p2.proname
+FROM pg_am AS p1, pg_proc AS p2
+WHERE p2.oid = p1.amhandler AND p1.amtype = 's' AND
+    (p2.prorettype != 'storage_am_handler'::regtype
+     OR p2.proretset
+     OR p2.pronargs != 1
+     OR p2.proargtypes[0] != 'internal'::regtype);
 
 -- **************** pg_amop ****************
 
-- 
2.15.0.windows.1



  [application/octet-stream] 0002-Storage-AM-folder-and-init-functions.patch (11.0K, ../../CAJrrPGc951F-R4Kfa4W47B5vHKeHsB2Y34zewp=b+AWSkF9RVA@mail.gmail.com/6-0002-Storage-AM-folder-and-init-functions.patch)
  download | inline diff:
From a6fa7e7a1d31b31d66ec8f2722f18a2d80aa1353 Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Wed, 30 Aug 2017 12:41:15 +1000
Subject: [PATCH 02/11] Storage AM folder and init functions

---
 src/backend/access/Makefile               |   2 +-
 src/backend/access/heap/Makefile          |   3 +-
 src/backend/access/heap/heapam_storage.c  |  33 ++++++++++
 src/backend/access/storage/Makefile       |  17 +++++
 src/backend/access/storage/storageam.c    |  15 +++++
 src/backend/access/storage/storageamapi.c | 103 ++++++++++++++++++++++++++++++
 src/include/access/storageamapi.h         |  39 +++++++++++
 src/include/catalog/pg_am.h               |   3 +
 src/include/catalog/pg_proc.h             |   5 ++
 src/include/nodes/nodes.h                 |   1 +
 10 files changed, 219 insertions(+), 2 deletions(-)
 create mode 100644 src/backend/access/heap/heapam_storage.c
 create mode 100644 src/backend/access/storage/Makefile
 create mode 100644 src/backend/access/storage/storageam.c
 create mode 100644 src/backend/access/storage/storageamapi.c
 create mode 100644 src/include/access/storageamapi.h

diff --git a/src/backend/access/Makefile b/src/backend/access/Makefile
index bd93a6a8d1..e72ad6c86c 100644
--- a/src/backend/access/Makefile
+++ b/src/backend/access/Makefile
@@ -9,6 +9,6 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 SUBDIRS	    = brin common gin gist hash heap index nbtree rmgrdesc spgist \
-			  tablesample transam
+			  storage tablesample transam
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/heap/Makefile b/src/backend/access/heap/Makefile
index b83d496bcd..816f03a86f 100644
--- a/src/backend/access/heap/Makefile
+++ b/src/backend/access/heap/Makefile
@@ -12,6 +12,7 @@ subdir = src/backend/access/heap
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = heapam.o hio.o pruneheap.o rewriteheap.o syncscan.o tuptoaster.o visibilitymap.o
+OBJS = heapam.o hio.o heapam_storage.o pruneheap.o rewriteheap.o \
+	syncscan.o tuptoaster.o visibilitymap.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
new file mode 100644
index 0000000000..792e9cb436
--- /dev/null
+++ b/src/backend/access/heap/heapam_storage.c
@@ -0,0 +1,33 @@
+/*-------------------------------------------------------------------------
+ *
+ * heapam_storage.c
+ *	  heap storage access method code
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/heap/heapam_storage.c
+ *
+ *
+ * NOTES
+ *	  This file contains the heap_ routines which implement
+ *	  the POSTGRES heap access method used for all POSTGRES
+ *	  relations.
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/storageamapi.h"
+#include "utils/builtins.h"
+
+
+Datum
+heapam_storage_handler(PG_FUNCTION_ARGS)
+{
+	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
+
+	PG_RETURN_POINTER(amroutine);
+}
diff --git a/src/backend/access/storage/Makefile b/src/backend/access/storage/Makefile
new file mode 100644
index 0000000000..2a05c7ce66
--- /dev/null
+++ b/src/backend/access/storage/Makefile
@@ -0,0 +1,17 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+#    Makefile for access/storage
+#
+# IDENTIFICATION
+#    src/backend/access/storage/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/access/storage
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = storageam.o storageamapi.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/storage/storageam.c b/src/backend/access/storage/storageam.c
new file mode 100644
index 0000000000..8541c75782
--- /dev/null
+++ b/src/backend/access/storage/storageam.c
@@ -0,0 +1,15 @@
+/*-------------------------------------------------------------------------
+ *
+ * storageam.c
+ *	  storage access method code
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/storage/storageam.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
diff --git a/src/backend/access/storage/storageamapi.c b/src/backend/access/storage/storageamapi.c
new file mode 100644
index 0000000000..bcbe14588b
--- /dev/null
+++ b/src/backend/access/storage/storageamapi.c
@@ -0,0 +1,103 @@
+/*----------------------------------------------------------------------
+ *
+ * storageamapi.c
+ *		Support routines for API for Postgres storage access methods
+ *
+ * FIXME: looks like this should be in amapi.c.
+ *
+ * Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * src/backend/access/heap/storageamapi.c
+ *----------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/storageamapi.h"
+#include "catalog/pg_am.h"
+#include "catalog/pg_proc.h"
+#include "utils/syscache.h"
+#include "utils/memutils.h"
+
+
+/*
+ * GetStorageAmRoutine
+ *		Call the specified access method handler routine to get its
+ *		StorageAmRoutine struct, which will be palloc'd in the caller's
+ *		memory context.
+ */
+StorageAmRoutine *
+GetStorageAmRoutine(Oid amhandler)
+{
+	Datum		datum;
+	StorageAmRoutine *routine;
+
+	datum = OidFunctionCall0(amhandler);
+	routine = (StorageAmRoutine *) DatumGetPointer(datum);
+
+	if (routine == NULL || !IsA(routine, StorageAmRoutine))
+		elog(ERROR, "storage access method handler %u did not return a StorageAmRoutine struct",
+			 amhandler);
+
+	return routine;
+}
+
+/* A crock */
+StorageAmRoutine *
+GetHeapamStorageAmRoutine(void)
+{
+	Datum		datum;
+	static StorageAmRoutine * HeapamStorageAmRoutine = NULL;
+
+	if (HeapamStorageAmRoutine == NULL)
+	{
+		MemoryContext oldcxt;
+
+		oldcxt = MemoryContextSwitchTo(TopMemoryContext);
+		datum = OidFunctionCall0(HEAPAM_STORAGE_AM_HANDLER_OID);
+		HeapamStorageAmRoutine = (StorageAmRoutine *) DatumGetPointer(datum);
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	return HeapamStorageAmRoutine;
+}
+
+/*
+ * GetStorageAmRoutineByAmId - look up the handler of the storage access
+ * method with the given OID, and get its StorageAmRoutine struct.
+ */
+StorageAmRoutine *
+GetStorageAmRoutineByAmId(Oid amoid)
+{
+	regproc		amhandler;
+	HeapTuple	tuple;
+	Form_pg_am	amform;
+
+	/* Get handler function OID for the access method */
+	tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(amoid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for access method %u",
+			 amoid);
+	amform = (Form_pg_am) GETSTRUCT(tuple);
+
+	/* Check that it is a storage access method */
+	if (amform->amtype != AMTYPE_STORAGE)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("access method \"%s\" is not of type %s",
+						NameStr(amform->amname), "STORAGE")));
+
+	amhandler = amform->amhandler;
+
+	/* Complain if handler OID is invalid */
+	if (!RegProcedureIsValid(amhandler))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("storage access method \"%s\" does not have a handler",
+						NameStr(amform->amname))));
+
+	ReleaseSysCache(tuple);
+
+	/* And finally, call the handler function to get the API struct. */
+	return GetStorageAmRoutine(amhandler);
+}
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
new file mode 100644
index 0000000000..6fae4eea5c
--- /dev/null
+++ b/src/include/access/storageamapi.h
@@ -0,0 +1,39 @@
+/*---------------------------------------------------------------------
+ *
+ * storageamapi.h
+ *		API for Postgres storage access methods
+ *
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ *
+ * src/include/access/storageamapi.h
+ *---------------------------------------------------------------------
+ */
+#ifndef STORAGEAMAPI_H
+#define STORAGEAMAPI_H
+
+#include "nodes/nodes.h"
+#include "fmgr.h"
+
+/* A physical tuple coming from a storage AM scan */
+typedef void *StorageTuple;
+
+/*
+ * API struct for a storage AM.  Note this must be stored in a single palloc'd
+ * chunk of memory.
+ *
+ * XXX currently all functions are together in a single struct.  Would it be
+ * worthwhile to split the slot-accessor functions to a different struct?
+ * That way, MinimalTuple could be handled without a complete StorageAmRoutine
+ * for them -- it'd only have a few functions in TupleTableSlotAmRoutine or so.
+ */
+typedef struct StorageAmRoutine
+{
+	NodeTag		type;
+
+}			StorageAmRoutine;
+
+extern StorageAmRoutine * GetStorageAmRoutine(Oid amhandler);
+extern StorageAmRoutine * GetStorageAmRoutineByAmId(Oid amoid);
+extern StorageAmRoutine * GetHeapamStorageAmRoutine(void);
+
+#endif							/* STORAGEAMAPI_H */
diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h
index dd9c263ade..2c3e33c104 100644
--- a/src/include/catalog/pg_am.h
+++ b/src/include/catalog/pg_am.h
@@ -84,5 +84,8 @@ DESCR("SP-GiST index access method");
 DATA(insert OID = 3580 (  brin		brinhandler i ));
 DESCR("block range index (BRIN) access method");
 #define BRIN_AM_OID 3580
+DATA(insert OID = 4001 (  heapam         heapam_storage_handler s ));
+DESCR("heapam storage access method");
+#define HEAPAM_STORAGE_AM_OID 4001
 
 #endif							/* PG_AM_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 38085d0b53..de05b2e56d 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -558,6 +558,11 @@ DESCR("convert int4 to float4");
 DATA(insert OID = 319 (  int4			   PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1  0 23 "700" _null_ _null_ _null_ _null_ _null_	ftoi4 _null_ _null_ _null_ ));
 DESCR("convert float4 to int4");
 
+/* Storage access method handlers */
+DATA(insert OID = 4002 (  heapam_storage_handler		PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 3998 "2281" _null_ _null_ _null_ _null_ _null_	heapam_storage_handler _null_ _null_ _null_ ));
+DESCR("row-oriented storage access method handler");
+#define HEAPAM_STORAGE_AM_HANDLER_OID	4002
+
 /* Index access method handlers */
 DATA(insert OID = 330 (  bthandler		PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 325 "2281" _null_ _null_ _null_ _null_ _null_	bthandler _null_ _null_ _null_ ));
 DESCR("btree index access method handler");
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index c5b5115f5b..927126f729 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -499,6 +499,7 @@ typedef enum NodeTag
 	T_InlineCodeBlock,			/* in nodes/parsenodes.h */
 	T_FdwRoutine,				/* in foreign/fdwapi.h */
 	T_IndexAmRoutine,			/* in access/amapi.h */
+	T_StorageAmRoutine,			/* in access/storageamapi.h */
 	T_TsmRoutine,				/* in access/tsmapi.h */
 	T_ForeignKeyCacheInfo		/* in utils/rel.h */
 } NodeTag;
-- 
2.15.0.windows.1



  [application/octet-stream] 0003-Adding-storageam-hanlder-to-relation-structure.patch (7.0K, ../../CAJrrPGc951F-R4Kfa4W47B5vHKeHsB2Y34zewp=b+AWSkF9RVA@mail.gmail.com/7-0003-Adding-storageam-hanlder-to-relation-structure.patch)
  download | inline diff:
From 2273ccbaf5733227da7e0a1582992e99d7c8a5a9 Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Wed, 30 Aug 2017 12:49:46 +1000
Subject: [PATCH 03/11] Adding storageam hanlder to relation structure

And also the necessary functions to initialize
the storageam handler
---
 src/backend/utils/cache/relcache.c | 119 ++++++++++++++++++++++++++++++++++++-
 src/include/utils/rel.h            |  12 ++++
 src/include/utils/relcache.h       |   2 +
 3 files changed, 130 insertions(+), 3 deletions(-)

diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index e2760daac4..41bbcc7645 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -35,6 +35,7 @@
 #include "access/multixact.h"
 #include "access/nbtree.h"
 #include "access/reloptions.h"
+#include "access/storageamapi.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "access/xlog.h"
@@ -1320,10 +1321,27 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	}
 
 	/*
-	 * if it's an index, initialize index-related information
+	 * initialize access method information
 	 */
-	if (OidIsValid(relation->rd_rel->relam))
-		RelationInitIndexAccessInfo(relation);
+	switch (relation->rd_rel->relkind)
+	{
+		case RELKIND_INDEX:
+			Assert(relation->rd_rel->relkind != InvalidOid);
+			RelationInitIndexAccessInfo(relation);
+			break;
+		case RELKIND_RELATION:
+		case RELKIND_SEQUENCE:
+		case RELKIND_TOASTVALUE:
+		case RELKIND_VIEW:		/* Not exactly the storage, but underlying
+								 * tuple access, it is required */
+		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
+			RelationInitStorageAccessInfo(relation);
+			break;
+		default:
+			/* nothing to do in other cases */
+			break;
+	}
 
 	/* extract reloptions if any */
 	RelationParseRelOptions(relation, pg_class_tuple);
@@ -1821,6 +1839,71 @@ LookupOpclassInfo(Oid operatorClassOid,
 	return opcentry;
 }
 
+/*
+ * Fill in the StorageAmRoutine for a relation
+ *
+ * relation's rd_amhandler and rd_indexcxt (XXX?) must be valid already.
+ */
+static void
+InitStorageAmRoutine(Relation relation)
+{
+	StorageAmRoutine *cached,
+			   *tmp;
+
+	/*
+	 * Call the amhandler in current, short-lived memory context, just in case
+	 * it leaks anything (it probably won't, but let's be paranoid).
+	 */
+	tmp = GetStorageAmRoutine(relation->rd_amhandler);
+
+	/* XXX do we need a separate memory context for this? */
+	/* OK, now transfer the data into cache context */
+	cached = (StorageAmRoutine *) MemoryContextAlloc(CacheMemoryContext,
+													 sizeof(StorageAmRoutine));
+	memcpy(cached, tmp, sizeof(StorageAmRoutine));
+	relation->rd_stamroutine = cached;
+
+	pfree(tmp);
+}
+
+/*
+ * Initialize storage-access-method support data for a heap relation
+ */
+void
+RelationInitStorageAccessInfo(Relation relation)
+{
+	HeapTuple	tuple;
+	Form_pg_am	aform;
+
+	/*
+	 * Relations that don't have a catalogued storage access method use the
+	 * standard heapam module; otherwise a catalog lookup is in order.
+	 */
+	if (!OidIsValid(relation->rd_rel->relam))
+	{
+		relation->rd_amhandler = HEAPAM_STORAGE_AM_HANDLER_OID;
+	}
+	else
+	{
+		/*
+		 * Look up the storage access method, save the OID of its handler
+		 * function.
+		 */
+		tuple = SearchSysCache1(AMOID,
+								ObjectIdGetDatum(relation->rd_rel->relam));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for access method %u",
+				 relation->rd_rel->relam);
+		aform = (Form_pg_am) GETSTRUCT(tuple);
+		relation->rd_amhandler = aform->amhandler;
+		ReleaseSysCache(tuple);
+	}
+
+	/*
+	 * Now we can fetch the storage AM's API struct
+	 */
+	InitStorageAmRoutine(relation);
+}
 
 /*
  *		formrdesc
@@ -1979,6 +2062,11 @@ formrdesc(const char *relationName, Oid relationReltype,
 	 */
 	RelationInitPhysicalAddr(relation);
 
+	/*
+	 * initialize the storage am handler
+	 */
+	relation->rd_stamroutine = GetHeapamStorageAmRoutine();
+
 	/*
 	 * initialize the rel-has-index flag, using hardwired knowledge
 	 */
@@ -2307,6 +2395,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		pfree(relation->rd_pubactions);
 	if (relation->rd_options)
 		pfree(relation->rd_options);
+	if (relation->rd_stamroutine)
+		pfree(relation->rd_stamroutine);
 	if (relation->rd_indextuple)
 		pfree(relation->rd_indextuple);
 	if (relation->rd_indexcxt)
@@ -3321,6 +3411,14 @@ RelationBuildLocalRelation(const char *relname,
 
 	RelationInitPhysicalAddr(rel);
 
+	if (relkind == RELKIND_RELATION ||
+		relkind == RELKIND_MATVIEW ||
+		relkind == RELKIND_VIEW ||	/* Not exactly the storage, but underlying
+									 * tuple access, it is required */
+		relkind == RELKIND_PARTITIONED_TABLE ||
+		relkind == RELKIND_TOASTVALUE)
+		RelationInitStorageAccessInfo(rel);
+
 	/*
 	 * Okay to insert into the relcache hash table.
 	 *
@@ -3842,6 +3940,18 @@ RelationCacheInitializePhase3(void)
 			restart = true;
 		}
 
+		if (relation->rd_stamroutine == NULL &&
+			(relation->rd_rel->relkind == RELKIND_RELATION ||
+			 relation->rd_rel->relkind == RELKIND_MATVIEW ||
+			 relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+			 relation->rd_rel->relkind == RELKIND_TOASTVALUE))
+		{
+			RelationInitStorageAccessInfo(relation);
+			Assert(relation->rd_stamroutine != NULL);
+
+			restart = true;
+		}
+
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -5562,6 +5672,9 @@ load_relcache_init_file(bool shared)
 			if (rel->rd_isnailed)
 				nailed_rels++;
 
+			/* Load storage AM stuff */
+			RelationInitStorageAccessInfo(rel);
+
 			Assert(rel->rd_index == NULL);
 			Assert(rel->rd_indextuple == NULL);
 			Assert(rel->rd_indexcxt == NULL);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 68fd6fbd54..72a15db9a4 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -160,6 +160,12 @@ typedef struct RelationData
 	/* use "struct" here to avoid needing to include htup.h: */
 	struct HeapTupleData *rd_indextuple;	/* all of pg_index tuple */
 
+	/*
+	 * Underlying storage support
+	 */
+	Oid			rd_storageam;	/* OID of storage AM handler function */
+	struct StorageAmRoutine *rd_stamroutine;	/* storage AM's API struct */
+
 	/*
 	 * index access support info (used only for an index relation)
 	 *
@@ -436,6 +442,12 @@ typedef struct ViewOptions
  */
 #define RelationGetDescr(relation) ((relation)->rd_att)
 
+/*
+ * RelationGetStorageRoutine
+ *		Returns the storage AM routine for a relation.
+ */
+#define RelationGetStorageRoutine(relation) ((relation)->rd_stamroutine)
+
 /*
  * RelationGetRelationName
  *		Returns the rel's name.
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 29c6d9bae3..8f7529a7c5 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -76,6 +76,8 @@ extern void RelationInitIndexAccessInfo(Relation relation);
 struct PublicationActions;
 extern struct PublicationActions *GetRelationPublicationActions(Relation relation);
 
+extern void RelationInitStorageAccessInfo(Relation relation);
+
 /*
  * Routines to support ereport() reports of relation-related errors
  */
-- 
2.15.0.windows.1



  [application/octet-stream] 0004-Adding-tuple-visibility-function-to-storage-AM.patch (67.5K, ../../CAJrrPGc951F-R4Kfa4W47B5vHKeHsB2Y34zewp=b+AWSkF9RVA@mail.gmail.com/8-0004-Adding-tuple-visibility-function-to-storage-AM.patch)
  download | inline diff:
From 088073894d888c363c71d92614ec6be675ed2bd0 Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Fri, 1 Dec 2017 14:49:18 +1100
Subject: [PATCH 04/11] Adding tuple visibility function to storage AM

Tuple visibility functions are now part of the
heap storage AM routine. The visibilty execution
procedure is changed accoridngly.

The snapshot satifies function is changed to an
enum to represent what type of snapshot is it
and this enum value is used to call the corresponding
visibilty function from the storage AM when the
visibilty of the tuple is required.

The common code is that is part of both server
and pluggable storages is now moved into storage_common.c
and storage_common.h files.
---
 contrib/pg_visibility/pg_visibility.c              |  11 +-
 contrib/pgrowlocks/pgrowlocks.c                    |   7 +-
 contrib/pgstattuple/pgstatapprox.c                 |   7 +-
 contrib/pgstattuple/pgstattuple.c                  |   3 +-
 src/backend/access/heap/Makefile                   |   2 +-
 src/backend/access/heap/heapam.c                   |  61 ++-
 src/backend/access/heap/heapam_storage.c           |   6 +
 .../tqual.c => access/heap/heapam_visibility.c}    | 444 +++++----------------
 src/backend/access/heap/pruneheap.c                |   4 +-
 src/backend/access/index/genam.c                   |   4 +-
 src/backend/access/storage/Makefile                |   2 +-
 src/backend/access/storage/storage_common.c        | 277 +++++++++++++
 src/backend/catalog/index.c                        |   6 +-
 src/backend/commands/analyze.c                     |   6 +-
 src/backend/commands/cluster.c                     |   3 +-
 src/backend/commands/vacuumlazy.c                  |   4 +-
 src/backend/executor/nodeBitmapHeapscan.c          |   2 +-
 src/backend/executor/nodeModifyTable.c             |   7 +-
 src/backend/executor/nodeSamplescan.c              |   3 +-
 src/backend/replication/logical/snapbuild.c        |   6 +-
 src/backend/storage/lmgr/predicate.c               |   2 +-
 src/backend/utils/adt/ri_triggers.c                |   2 +-
 src/backend/utils/time/Makefile                    |   2 +-
 src/backend/utils/time/snapmgr.c                   |  10 +-
 src/include/access/heapam.h                        |   8 +
 src/include/access/storage_common.h                | 101 +++++
 src/include/access/storageamapi.h                  |  16 +-
 src/include/storage/bufmgr.h                       |   5 +-
 src/include/utils/snapshot.h                       |  14 +-
 src/include/utils/tqual.h                          |  54 +--
 30 files changed, 611 insertions(+), 468 deletions(-)
 rename src/backend/{utils/time/tqual.c => access/heap/heapam_visibility.c} (80%)
 create mode 100644 src/backend/access/storage/storage_common.c
 create mode 100644 src/include/access/storage_common.h

diff --git a/contrib/pg_visibility/pg_visibility.c b/contrib/pg_visibility/pg_visibility.c
index 2cc9575d9f..01ff3fed5d 100644
--- a/contrib/pg_visibility/pg_visibility.c
+++ b/contrib/pg_visibility/pg_visibility.c
@@ -11,6 +11,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/storageamapi.h"
 #include "access/visibilitymap.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage_xlog.h"
@@ -51,7 +52,7 @@ static vbits *collect_visibility_data(Oid relid, bool include_pd);
 static corrupt_items *collect_corrupt_items(Oid relid, bool all_visible,
 					  bool all_frozen);
 static void record_corrupt_item(corrupt_items *items, ItemPointer tid);
-static bool tuple_all_visible(HeapTuple tup, TransactionId OldestXmin,
+static bool tuple_all_visible(Relation rel, HeapTuple tup, TransactionId OldestXmin,
 				  Buffer buffer);
 static void check_relation_relkind(Relation rel);
 
@@ -656,7 +657,7 @@ collect_corrupt_items(Oid relid, bool all_visible, bool all_frozen)
 			 * the tuple to be all-visible.
 			 */
 			if (check_visible &&
-				!tuple_all_visible(&tuple, OldestXmin, buffer))
+				!tuple_all_visible(rel, &tuple, OldestXmin, buffer))
 			{
 				TransactionId RecomputedOldestXmin;
 
@@ -681,7 +682,7 @@ collect_corrupt_items(Oid relid, bool all_visible, bool all_frozen)
 				else
 				{
 					OldestXmin = RecomputedOldestXmin;
-					if (!tuple_all_visible(&tuple, OldestXmin, buffer))
+					if (!tuple_all_visible(rel, &tuple, OldestXmin, buffer))
 						record_corrupt_item(items, &tuple.t_self);
 				}
 			}
@@ -739,12 +740,12 @@ record_corrupt_item(corrupt_items *items, ItemPointer tid)
  * The buffer should contain the tuple and should be locked and pinned.
  */
 static bool
-tuple_all_visible(HeapTuple tup, TransactionId OldestXmin, Buffer buffer)
+tuple_all_visible(Relation rel, HeapTuple tup, TransactionId OldestXmin, Buffer buffer)
 {
 	HTSV_Result state;
 	TransactionId xmin;
 
-	state = HeapTupleSatisfiesVacuum(tup, OldestXmin, buffer);
+	state = rel->rd_stamroutine->snapshot_satisfiesVacuum(tup, OldestXmin, buffer);
 	if (state != HEAPTUPLE_LIVE)
 		return false;			/* all-visible implies live */
 
diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index eabca65bd2..830e74fd07 100644
--- a/contrib/pgrowlocks/pgrowlocks.c
+++ b/contrib/pgrowlocks/pgrowlocks.c
@@ -26,6 +26,7 @@
 
 #include "access/multixact.h"
 #include "access/relscan.h"
+#include "access/storageamapi.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
@@ -149,9 +150,9 @@ pgrowlocks(PG_FUNCTION_ARGS)
 		/* must hold a buffer lock to call HeapTupleSatisfiesUpdate */
 		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 
-		htsu = HeapTupleSatisfiesUpdate(tuple,
-										GetCurrentCommandId(false),
-										scan->rs_cbuf);
+		htsu = rel->rd_stamroutine->snapshot_satisfiesUpdate(tuple,
+															 GetCurrentCommandId(false),
+															 scan->rs_cbuf);
 		xmax = HeapTupleHeaderGetRawXmax(tuple->t_data);
 		infomask = tuple->t_data->t_infomask;
 
diff --git a/contrib/pgstattuple/pgstatapprox.c b/contrib/pgstattuple/pgstatapprox.c
index 5bf06138a5..284eabc970 100644
--- a/contrib/pgstattuple/pgstatapprox.c
+++ b/contrib/pgstattuple/pgstatapprox.c
@@ -12,12 +12,13 @@
  */
 #include "postgres.h"
 
-#include "access/visibilitymap.h"
 #include "access/transam.h"
+#include "access/visibilitymap.h"
 #include "access/xact.h"
 #include "access/multixact.h"
 #include "access/htup_details.h"
 #include "catalog/namespace.h"
+#include "commands/vacuum.h"
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
@@ -26,7 +27,7 @@
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
 #include "utils/tqual.h"
-#include "commands/vacuum.h"
+
 
 PG_FUNCTION_INFO_V1(pgstattuple_approx);
 PG_FUNCTION_INFO_V1(pgstattuple_approx_v1_5);
@@ -156,7 +157,7 @@ statapprox_heap(Relation rel, output_type *stat)
 			 * We count live and dead tuples, but we also need to add up
 			 * others in order to feed vac_estimate_reltuples.
 			 */
-			switch (HeapTupleSatisfiesVacuum(&tuple, OldestXmin, buf))
+			switch (rel->rd_stamroutine->snapshot_satisfiesVacuum(&tuple, OldestXmin, buf))
 			{
 				case HEAPTUPLE_RECENTLY_DEAD:
 					misc_count++;
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index 7ca1bb24d2..e098202f84 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -322,6 +322,7 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 	Buffer		buffer;
 	pgstattuple_type stat = {0};
 	SnapshotData SnapshotDirty;
+	StorageAmRoutine *method = rel->rd_stamroutine;
 
 	/* Disable syncscan because we assume we scan from block zero upwards */
 	scan = heap_beginscan_strat(rel, SnapshotAny, 0, NULL, true, false);
@@ -337,7 +338,7 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 		/* must hold a buffer lock to call HeapTupleSatisfiesVisibility */
 		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 
-		if (HeapTupleSatisfiesVisibility(tuple, &SnapshotDirty, scan->rs_cbuf))
+		if (HeapTupleSatisfiesVisibility(method, tuple, &SnapshotDirty, scan->rs_cbuf))
 		{
 			stat.tuple_len += tuple->t_len;
 			stat.tuple_count++;
diff --git a/src/backend/access/heap/Makefile b/src/backend/access/heap/Makefile
index 816f03a86f..f5c628395b 100644
--- a/src/backend/access/heap/Makefile
+++ b/src/backend/access/heap/Makefile
@@ -12,7 +12,7 @@ subdir = src/backend/access/heap
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = heapam.o hio.o heapam_storage.o pruneheap.o rewriteheap.o \
+OBJS = heapam.o hio.o heapam_storage.o heapam_visibility.o pruneheap.o rewriteheap.o \
 	syncscan.o tuptoaster.o visibilitymap.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 54f1100ffd..404c825089 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -45,6 +45,7 @@
 #include "access/multixact.h"
 #include "access/parallel.h"
 #include "access/relscan.h"
+#include "access/storageamapi.h"
 #include "access/sysattr.h"
 #include "access/transam.h"
 #include "access/tuptoaster.h"
@@ -438,7 +439,7 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 			if (all_visible)
 				valid = true;
 			else
-				valid = HeapTupleSatisfiesVisibility(&loctup, snapshot, buffer);
+				valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine, &loctup, snapshot, buffer);
 
 			CheckForSerializableConflictOut(valid, scan->rs_rd, &loctup,
 											buffer, snapshot);
@@ -653,7 +654,8 @@ heapgettup(HeapScanDesc scan,
 				/*
 				 * if current tuple qualifies, return it.
 				 */
-				valid = HeapTupleSatisfiesVisibility(tuple,
+				valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine,
+													 tuple,
 													 snapshot,
 													 scan->rs_cbuf);
 
@@ -841,6 +843,7 @@ heapgettup_pagemode(HeapScanDesc scan,
 			lineindex = scan->rs_cindex + 1;
 		}
 
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 		dp = BufferGetPage(scan->rs_cbuf);
 		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
 		lines = scan->rs_ntuples;
@@ -885,6 +888,7 @@ heapgettup_pagemode(HeapScanDesc scan,
 			page = scan->rs_cblock; /* current page */
 		}
 
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 		dp = BufferGetPage(scan->rs_cbuf);
 		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
 		lines = scan->rs_ntuples;
@@ -954,23 +958,31 @@ heapgettup_pagemode(HeapScanDesc scan,
 			/*
 			 * if current tuple qualifies, return it.
 			 */
-			if (key != NULL)
+			if (HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine, tuple, scan->rs_snapshot, scan->rs_cbuf))
 			{
-				bool		valid;
+				/*
+				 * if current tuple qualifies, return it.
+				 */
+				if (key != NULL)
+				{
+					bool		valid;
 
-				HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
-							nkeys, key, valid);
-				if (valid)
+					HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
+								nkeys, key, valid);
+					if (valid)
+					{
+						scan->rs_cindex = lineindex;
+						LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+						return;
+					}
+				}
+				else
 				{
 					scan->rs_cindex = lineindex;
+					LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
 					return;
 				}
 			}
-			else
-			{
-				scan->rs_cindex = lineindex;
-				return;
-			}
 
 			/*
 			 * otherwise move to the next item on the page
@@ -982,6 +994,12 @@ heapgettup_pagemode(HeapScanDesc scan,
 				++lineindex;
 		}
 
+		/*
+		 * if we get here, it means we've exhausted the items on this page and
+		 * it's time to move to the next.
+		 */
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+
 		/*
 		 * if we get here, it means we've exhausted the items on this page and
 		 * it's time to move to the next.
@@ -1039,6 +1057,7 @@ heapgettup_pagemode(HeapScanDesc scan,
 
 		heapgetpage(scan, page);
 
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 		dp = BufferGetPage(scan->rs_cbuf);
 		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
 		lines = scan->rs_ntuples;
@@ -1831,7 +1850,7 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 
 	pgstat_count_heap_getnext(scan->rs_rd);
 
-	return &(scan->rs_ctup);
+	return heap_copytuple(&(scan->rs_ctup));
 }
 
 /*
@@ -1950,7 +1969,7 @@ heap_fetch(Relation relation,
 	/*
 	 * check time qualification of tuple, then release lock
 	 */
-	valid = HeapTupleSatisfiesVisibility(tuple, snapshot, buffer);
+	valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, tuple, snapshot, buffer);
 
 	if (valid)
 		PredicateLockTuple(relation, tuple, snapshot);
@@ -2097,7 +2116,7 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
 			ItemPointerSet(&(heapTuple->t_self), BufferGetBlockNumber(buffer), offnum);
 
 			/* If it's visible per the snapshot, we must return it */
-			valid = HeapTupleSatisfiesVisibility(heapTuple, snapshot, buffer);
+			valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, heapTuple, snapshot, buffer);
 			CheckForSerializableConflictOut(valid, relation, heapTuple,
 											buffer, snapshot);
 			/* reset to original, non-redirected, tid */
@@ -2271,7 +2290,7 @@ heap_get_latest_tid(Relation relation,
 		 * Check time qualification of tuple; if visible, set it as the new
 		 * result candidate.
 		 */
-		valid = HeapTupleSatisfiesVisibility(&tp, snapshot, buffer);
+		valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, &tp, snapshot, buffer);
 		CheckForSerializableConflictOut(valid, relation, &tp, buffer, snapshot);
 		if (valid)
 			*tid = ctid;
@@ -3097,7 +3116,7 @@ heap_delete(Relation relation, ItemPointer tid,
 	tp.t_self = *tid;
 
 l1:
-	result = HeapTupleSatisfiesUpdate(&tp, cid, buffer);
+	result = relation->rd_stamroutine->snapshot_satisfiesUpdate(&tp, cid, buffer);
 
 	if (result == HeapTupleInvisible)
 	{
@@ -3208,7 +3227,7 @@ l1:
 	if (crosscheck != InvalidSnapshot && result == HeapTupleMayBeUpdated)
 	{
 		/* Perform additional check for transaction-snapshot mode RI updates */
-		if (!HeapTupleSatisfiesVisibility(&tp, crosscheck, buffer))
+		if (!HeapTupleSatisfiesVisibility(relation->rd_stamroutine, &tp, crosscheck, buffer))
 			result = HeapTupleUpdated;
 	}
 
@@ -3668,7 +3687,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 l2:
 	checked_lockers = false;
 	locker_remains = false;
-	result = HeapTupleSatisfiesUpdate(&oldtup, cid, buffer);
+	result = relation->rd_stamroutine->snapshot_satisfiesUpdate(&oldtup, cid, buffer);
 
 	/* see below about the "no wait" case */
 	Assert(result != HeapTupleBeingUpdated || wait);
@@ -3849,7 +3868,7 @@ l2:
 	if (crosscheck != InvalidSnapshot && result == HeapTupleMayBeUpdated)
 	{
 		/* Perform additional check for transaction-snapshot mode RI updates */
-		if (!HeapTupleSatisfiesVisibility(&oldtup, crosscheck, buffer))
+		if (!HeapTupleSatisfiesVisibility(relation->rd_stamroutine, &oldtup, crosscheck, buffer))
 			result = HeapTupleUpdated;
 	}
 
@@ -4600,7 +4619,7 @@ heap_lock_tuple(Relation relation, HeapTuple tuple,
 	tuple->t_tableOid = RelationGetRelid(relation);
 
 l3:
-	result = HeapTupleSatisfiesUpdate(tuple, cid, *buffer);
+	result = relation->rd_stamroutine->snapshot_satisfiesUpdate(tuple, cid, *buffer);
 
 	if (result == HeapTupleInvisible)
 	{
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index 792e9cb436..a340c46a80 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -20,6 +20,7 @@
  */
 #include "postgres.h"
 
+#include "access/heapam.h"
 #include "access/storageamapi.h"
 #include "utils/builtins.h"
 
@@ -29,5 +30,10 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 {
 	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
 
+	amroutine->snapshot_satisfies = HeapTupleSatisfies;
+
+	amroutine->snapshot_satisfiesUpdate = HeapTupleSatisfiesUpdate;
+	amroutine->snapshot_satisfiesVacuum = HeapTupleSatisfiesVacuum;
+
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/utils/time/tqual.c b/src/backend/access/heap/heapam_visibility.c
similarity index 80%
rename from src/backend/utils/time/tqual.c
rename to src/backend/access/heap/heapam_visibility.c
index 2b218e07e6..2841197960 100644
--- a/src/backend/utils/time/tqual.c
+++ b/src/backend/access/heap/heapam_visibility.c
@@ -1,7 +1,33 @@
 /*-------------------------------------------------------------------------
  *
- * tqual.c
- *	  POSTGRES "time qualification" code, ie, tuple visibility rules.
+ * heapam_visibility.c
+ *	  heapam access method visibility functions
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/heap/heapam_visibility.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/heapam_xlog.h"
+#include "access/hio.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "storage/procarray.h"
+#include "utils/builtins.h"
+#include "utils/rel.h"
+#include "utils/tqual.h"
+
+/*-------------------------------------------------------------------------
+ *
+ * POSTGRES "time qualification" code, ie, tuple visibility rules.
  *
  * NOTE: all the HeapTupleSatisfies routines will update the tuple's
  * "hint" status bits if we see that the inserting or deleting transaction
@@ -45,108 +71,15 @@
  *		  like HeapTupleSatisfiesSelf(), but includes open transactions
  *	 HeapTupleSatisfiesVacuum()
  *		  visible to any running transaction, used by VACUUM
- *	 HeapTupleSatisfiesNonVacuumable()
- *		  Snapshot-style API for HeapTupleSatisfiesVacuum
+ *   HeapTupleSatisfiesNonVacuumable()
+ *        Snapshot-style API for HeapTupleSatisfiesVacuum
  *	 HeapTupleSatisfiesToast()
  *		  visible unless part of interrupted vacuum, used for TOAST
  *	 HeapTupleSatisfiesAny()
  *		  all tuples are visible
  *
- * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * IDENTIFICATION
- *	  src/backend/utils/time/tqual.c
- *
- *-------------------------------------------------------------------------
- */
-
-#include "postgres.h"
-
-#include "access/htup_details.h"
-#include "access/multixact.h"
-#include "access/subtrans.h"
-#include "access/transam.h"
-#include "access/xact.h"
-#include "access/xlog.h"
-#include "storage/bufmgr.h"
-#include "storage/procarray.h"
-#include "utils/builtins.h"
-#include "utils/combocid.h"
-#include "utils/snapmgr.h"
-#include "utils/tqual.h"
-
-
-/* Static variables representing various special snapshot semantics */
-SnapshotData SnapshotSelfData = {HeapTupleSatisfiesSelf};
-SnapshotData SnapshotAnyData = {HeapTupleSatisfiesAny};
-
-
-/*
- * SetHintBits()
- *
- * Set commit/abort hint bits on a tuple, if appropriate at this time.
- *
- * It is only safe to set a transaction-committed hint bit if we know the
- * transaction's commit record is guaranteed to be flushed to disk before the
- * buffer, or if the table is temporary or unlogged and will be obliterated by
- * a crash anyway.  We cannot change the LSN of the page here, because we may
- * hold only a share lock on the buffer, so we can only use the LSN to
- * interlock this if the buffer's LSN already is newer than the commit LSN;
- * otherwise we have to just refrain from setting the hint bit until some
- * future re-examination of the tuple.
- *
- * We can always set hint bits when marking a transaction aborted.  (Some
- * code in heapam.c relies on that!)
- *
- * Also, if we are cleaning up HEAP_MOVED_IN or HEAP_MOVED_OFF entries, then
- * we can always set the hint bits, since pre-9.0 VACUUM FULL always used
- * synchronous commits and didn't move tuples that weren't previously
- * hinted.  (This is not known by this subroutine, but is applied by its
- * callers.)  Note: old-style VACUUM FULL is gone, but we have to keep this
- * module's support for MOVED_OFF/MOVED_IN flag bits for as long as we
- * support in-place update from pre-9.0 databases.
- *
- * Normal commits may be asynchronous, so for those we need to get the LSN
- * of the transaction and then check whether this is flushed.
- *
- * The caller should pass xid as the XID of the transaction to check, or
- * InvalidTransactionId if no check is needed.
- */
-static inline void
-SetHintBits(HeapTupleHeader tuple, Buffer buffer,
-			uint16 infomask, TransactionId xid)
-{
-	if (TransactionIdIsValid(xid))
-	{
-		/* NB: xid must be known committed here! */
-		XLogRecPtr	commitLSN = TransactionIdGetCommitLSN(xid);
-
-		if (BufferIsPermanent(buffer) && XLogNeedsFlush(commitLSN) &&
-			BufferGetLSNAtomic(buffer) < commitLSN)
-		{
-			/* not flushed and no LSN interlock, so don't set hint */
-			return;
-		}
-	}
-
-	tuple->t_infomask |= infomask;
-	MarkBufferDirtyHint(buffer, true);
-}
-
-/*
- * HeapTupleSetHintBits --- exported version of SetHintBits()
- *
- * This must be separate because of C99's brain-dead notions about how to
- * implement inline functions.
+ * -------------------------------------------------------------------------
  */
-void
-HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
-					 uint16 infomask, TransactionId xid)
-{
-	SetHintBits(tuple, buffer, infomask, xid);
-}
-
 
 /*
  * HeapTupleSatisfiesSelf
@@ -172,9 +105,10 @@ HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
  *			(Xmax != my-transaction &&			the row was deleted by another transaction
  *			 Xmax is not committed)))			that has not been committed
  */
-bool
-HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
+static bool
+HeapTupleSatisfiesSelf(StorageTuple stup, Snapshot snapshot, Buffer buffer)
 {
+	HeapTuple	htup = (HeapTuple) stup;
 	HeapTupleHeader tuple = htup->t_data;
 
 	Assert(ItemPointerIsValid(&htup->t_self));
@@ -342,8 +276,8 @@ HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
  * HeapTupleSatisfiesAny
  *		Dummy "satisfies" routine: any tuple satisfies SnapshotAny.
  */
-bool
-HeapTupleSatisfiesAny(HeapTuple htup, Snapshot snapshot, Buffer buffer)
+static bool
+HeapTupleSatisfiesAny(StorageTuple stup, Snapshot snapshot, Buffer buffer)
 {
 	return true;
 }
@@ -362,10 +296,11 @@ HeapTupleSatisfiesAny(HeapTuple htup, Snapshot snapshot, Buffer buffer)
  * Among other things, this means you can't do UPDATEs of rows in a TOAST
  * table.
  */
-bool
-HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot,
+static bool
+HeapTupleSatisfiesToast(StorageTuple stup, Snapshot snapshot,
 						Buffer buffer)
 {
+	HeapTuple	htup = (HeapTuple) stup;
 	HeapTupleHeader tuple = htup->t_data;
 
 	Assert(ItemPointerIsValid(&htup->t_self));
@@ -457,9 +392,10 @@ HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot,
  *	distinguish that case must test for it themselves.)
  */
 HTSU_Result
-HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
+HeapTupleSatisfiesUpdate(StorageTuple stup, CommandId curcid,
 						 Buffer buffer)
 {
+	HeapTuple	htup = (HeapTuple) stup;
 	HeapTupleHeader tuple = htup->t_data;
 
 	Assert(ItemPointerIsValid(&htup->t_self));
@@ -735,10 +671,11 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
  * on the insertion without aborting the whole transaction, the associated
  * token is also returned in snapshot->speculativeToken.
  */
-bool
-HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
+static bool
+HeapTupleSatisfiesDirty(StorageTuple stup, Snapshot snapshot,
 						Buffer buffer)
 {
+	HeapTuple	htup = (HeapTuple) stup;
 	HeapTupleHeader tuple = htup->t_data;
 
 	Assert(ItemPointerIsValid(&htup->t_self));
@@ -959,10 +896,11 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
  * inserting/deleting transaction was still running --- which was more cycles
  * and more contention on the PGXACT array.
  */
-bool
-HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
+static bool
+HeapTupleSatisfiesMVCC(StorageTuple stup, Snapshot snapshot,
 					   Buffer buffer)
 {
+	HeapTuple	htup = (HeapTuple) stup;
 	HeapTupleHeader tuple = htup->t_data;
 
 	Assert(ItemPointerIsValid(&htup->t_self));
@@ -1161,9 +1099,10 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
  * even if we see that the deleting transaction has committed.
  */
 HTSV_Result
-HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
+HeapTupleSatisfiesVacuum(StorageTuple stup, TransactionId OldestXmin,
 						 Buffer buffer)
 {
+	HeapTuple	htup = (HeapTuple) stup;
 	HeapTupleHeader tuple = htup->t_data;
 
 	Assert(ItemPointerIsValid(&htup->t_self));
@@ -1383,258 +1322,24 @@ HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
 	return HEAPTUPLE_DEAD;
 }
 
-
 /*
  * HeapTupleSatisfiesNonVacuumable
  *
- *	True if tuple might be visible to some transaction; false if it's
- *	surely dead to everyone, ie, vacuumable.
+ *     True if tuple might be visible to some transaction; false if it's
+ *     surely dead to everyone, ie, vacuumable.
  *
- *	This is an interface to HeapTupleSatisfiesVacuum that meets the
- *	SnapshotSatisfiesFunc API, so it can be used through a Snapshot.
- *	snapshot->xmin must have been set up with the xmin horizon to use.
+ *     This is an interface to HeapTupleSatisfiesVacuum that meets the
+ *     SnapshotSatisfiesFunc API, so it can be used through a Snapshot.
+ *     snapshot->xmin must have been set up with the xmin horizon to use.
  */
-bool
-HeapTupleSatisfiesNonVacuumable(HeapTuple htup, Snapshot snapshot,
+static bool
+HeapTupleSatisfiesNonVacuumable(StorageTuple htup, Snapshot snapshot,
 								Buffer buffer)
 {
 	return HeapTupleSatisfiesVacuum(htup, snapshot->xmin, buffer)
 		!= HEAPTUPLE_DEAD;
 }
 
-
-/*
- * HeapTupleIsSurelyDead
- *
- *	Cheaply determine whether a tuple is surely dead to all onlookers.
- *	We sometimes use this in lieu of HeapTupleSatisfiesVacuum when the
- *	tuple has just been tested by another visibility routine (usually
- *	HeapTupleSatisfiesMVCC) and, therefore, any hint bits that can be set
- *	should already be set.  We assume that if no hint bits are set, the xmin
- *	or xmax transaction is still running.  This is therefore faster than
- *	HeapTupleSatisfiesVacuum, because we don't consult PGXACT nor CLOG.
- *	It's okay to return false when in doubt, but we must return true only
- *	if the tuple is removable.
- */
-bool
-HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	/*
-	 * If the inserting transaction is marked invalid, then it aborted, and
-	 * the tuple is definitely dead.  If it's marked neither committed nor
-	 * invalid, then we assume it's still alive (since the presumption is that
-	 * all relevant hint bits were just set moments ago).
-	 */
-	if (!HeapTupleHeaderXminCommitted(tuple))
-		return HeapTupleHeaderXminInvalid(tuple) ? true : false;
-
-	/*
-	 * If the inserting transaction committed, but any deleting transaction
-	 * aborted, the tuple is still alive.
-	 */
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)
-		return false;
-
-	/*
-	 * If the XMAX is just a lock, the tuple is still alive.
-	 */
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-		return false;
-
-	/*
-	 * If the Xmax is a MultiXact, it might be dead or alive, but we cannot
-	 * know without checking pg_multixact.
-	 */
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-		return false;
-
-	/* If deleter isn't known to have committed, assume it's still running. */
-	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
-		return false;
-
-	/* Deleter committed, so tuple is dead if the XID is old enough. */
-	return TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin);
-}
-
-/*
- * XidInMVCCSnapshot
- *		Is the given XID still-in-progress according to the snapshot?
- *
- * Note: GetSnapshotData never stores either top xid or subxids of our own
- * backend into a snapshot, so these xids will not be reported as "running"
- * by this function.  This is OK for current uses, because we always check
- * TransactionIdIsCurrentTransactionId first, except when it's known the
- * XID could not be ours anyway.
- */
-bool
-XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot)
-{
-	uint32		i;
-
-	/*
-	 * Make a quick range check to eliminate most XIDs without looking at the
-	 * xip arrays.  Note that this is OK even if we convert a subxact XID to
-	 * its parent below, because a subxact with XID < xmin has surely also got
-	 * a parent with XID < xmin, while one with XID >= xmax must belong to a
-	 * parent that was not yet committed at the time of this snapshot.
-	 */
-
-	/* Any xid < xmin is not in-progress */
-	if (TransactionIdPrecedes(xid, snapshot->xmin))
-		return false;
-	/* Any xid >= xmax is in-progress */
-	if (TransactionIdFollowsOrEquals(xid, snapshot->xmax))
-		return true;
-
-	/*
-	 * Snapshot information is stored slightly differently in snapshots taken
-	 * during recovery.
-	 */
-	if (!snapshot->takenDuringRecovery)
-	{
-		/*
-		 * If the snapshot contains full subxact data, the fastest way to
-		 * check things is just to compare the given XID against both subxact
-		 * XIDs and top-level XIDs.  If the snapshot overflowed, we have to
-		 * use pg_subtrans to convert a subxact XID to its parent XID, but
-		 * then we need only look at top-level XIDs not subxacts.
-		 */
-		if (!snapshot->suboverflowed)
-		{
-			/* we have full data, so search subxip */
-			int32		j;
-
-			for (j = 0; j < snapshot->subxcnt; j++)
-			{
-				if (TransactionIdEquals(xid, snapshot->subxip[j]))
-					return true;
-			}
-
-			/* not there, fall through to search xip[] */
-		}
-		else
-		{
-			/*
-			 * Snapshot overflowed, so convert xid to top-level.  This is safe
-			 * because we eliminated too-old XIDs above.
-			 */
-			xid = SubTransGetTopmostTransaction(xid);
-
-			/*
-			 * If xid was indeed a subxact, we might now have an xid < xmin,
-			 * so recheck to avoid an array scan.  No point in rechecking
-			 * xmax.
-			 */
-			if (TransactionIdPrecedes(xid, snapshot->xmin))
-				return false;
-		}
-
-		for (i = 0; i < snapshot->xcnt; i++)
-		{
-			if (TransactionIdEquals(xid, snapshot->xip[i]))
-				return true;
-		}
-	}
-	else
-	{
-		int32		j;
-
-		/*
-		 * In recovery we store all xids in the subxact array because it is by
-		 * far the bigger array, and we mostly don't know which xids are
-		 * top-level and which are subxacts. The xip array is empty.
-		 *
-		 * We start by searching subtrans, if we overflowed.
-		 */
-		if (snapshot->suboverflowed)
-		{
-			/*
-			 * Snapshot overflowed, so convert xid to top-level.  This is safe
-			 * because we eliminated too-old XIDs above.
-			 */
-			xid = SubTransGetTopmostTransaction(xid);
-
-			/*
-			 * If xid was indeed a subxact, we might now have an xid < xmin,
-			 * so recheck to avoid an array scan.  No point in rechecking
-			 * xmax.
-			 */
-			if (TransactionIdPrecedes(xid, snapshot->xmin))
-				return false;
-		}
-
-		/*
-		 * We now have either a top-level xid higher than xmin or an
-		 * indeterminate xid. We don't know whether it's top level or subxact
-		 * but it doesn't matter. If it's present, the xid is visible.
-		 */
-		for (j = 0; j < snapshot->subxcnt; j++)
-		{
-			if (TransactionIdEquals(xid, snapshot->subxip[j]))
-				return true;
-		}
-	}
-
-	return false;
-}
-
-/*
- * Is the tuple really only locked?  That is, is it not updated?
- *
- * It's easy to check just infomask bits if the locker is not a multi; but
- * otherwise we need to verify that the updating transaction has not aborted.
- *
- * This function is here because it follows the same time qualification rules
- * laid out at the top of this file.
- */
-bool
-HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple)
-{
-	TransactionId xmax;
-
-	/* if there's no valid Xmax, then there's obviously no update either */
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)
-		return true;
-
-	if (tuple->t_infomask & HEAP_XMAX_LOCK_ONLY)
-		return true;
-
-	/* invalid xmax means no update */
-	if (!TransactionIdIsValid(HeapTupleHeaderGetRawXmax(tuple)))
-		return true;
-
-	/*
-	 * if HEAP_XMAX_LOCK_ONLY is not set and not a multi, then this must
-	 * necessarily have been updated
-	 */
-	if (!(tuple->t_infomask & HEAP_XMAX_IS_MULTI))
-		return false;
-
-	/* ... but if it's a multi, then perhaps the updating Xid aborted. */
-	xmax = HeapTupleGetUpdateXid(tuple);
-
-	/* not LOCKED_ONLY, so it has to have an xmax */
-	Assert(TransactionIdIsValid(xmax));
-
-	if (TransactionIdIsCurrentTransactionId(xmax))
-		return false;
-	if (TransactionIdIsInProgress(xmax))
-		return false;
-	if (TransactionIdDidCommit(xmax))
-		return false;
-
-	/*
-	 * not current, not in progress, not committed -- must have aborted or
-	 * crashed
-	 */
-	return true;
-}
-
 /*
  * check whether the transaction id 'xid' is in the pre-sorted array 'xip'.
  */
@@ -1659,10 +1364,11 @@ TransactionIdInArray(TransactionId xid, TransactionId *xip, Size num)
  * dangerous to do so as the semantics of doing so during timetravel are more
  * complicated than when dealing "only" with the present.
  */
-bool
-HeapTupleSatisfiesHistoricMVCC(HeapTuple htup, Snapshot snapshot,
+static bool
+HeapTupleSatisfiesHistoricMVCC(StorageTuple stup, Snapshot snapshot,
 							   Buffer buffer)
 {
+	HeapTuple	htup = (HeapTuple) stup;
 	HeapTupleHeader tuple = htup->t_data;
 	TransactionId xmin = HeapTupleHeaderGetXmin(tuple);
 	TransactionId xmax = HeapTupleHeaderGetRawXmax(tuple);
@@ -1796,3 +1502,35 @@ HeapTupleSatisfiesHistoricMVCC(HeapTuple htup, Snapshot snapshot,
 	else
 		return true;
 }
+
+bool
+HeapTupleSatisfies(StorageTuple stup, Snapshot snapshot, Buffer buffer)
+{
+	switch (snapshot->visibility_type)
+	{
+		case MVCC_VISIBILITY:
+			return HeapTupleSatisfiesMVCC(stup, snapshot, buffer);
+			break;
+		case SELF_VISIBILITY:
+			return HeapTupleSatisfiesSelf(stup, snapshot, buffer);
+			break;
+		case ANY_VISIBILITY:
+			return HeapTupleSatisfiesAny(stup, snapshot, buffer);
+			break;
+		case TOAST_VISIBILITY:
+			return HeapTupleSatisfiesToast(stup, snapshot, buffer);
+			break;
+		case DIRTY_VISIBILITY:
+			return HeapTupleSatisfiesDirty(stup, snapshot, buffer);
+			break;
+		case HISTORIC_MVCC_VISIBILITY:
+			return HeapTupleSatisfiesHistoricMVCC(stup, snapshot, buffer);
+			break;
+		case NON_VACUUMABLE_VISIBILTY:
+			return HeapTupleSatisfiesNonVacuumable(stup, snapshot, buffer);
+			break;
+		default:
+			Assert(0);
+			break;
+	}
+}
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 9f33e0ce07..ed40357976 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -402,7 +402,7 @@ heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum,
 			 * either here or while following a chain below.  Whichever path
 			 * gets there first will mark the tuple unused.
 			 */
-			if (HeapTupleSatisfiesVacuum(&tup, OldestXmin, buffer)
+			if (relation->rd_stamroutine->snapshot_satisfiesVacuum(&tup, OldestXmin, buffer)
 				== HEAPTUPLE_DEAD && !HeapTupleHeaderIsHotUpdated(htup))
 			{
 				heap_prune_record_unused(prstate, rootoffnum);
@@ -486,7 +486,7 @@ heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum,
 		 */
 		tupdead = recent_dead = false;
 
-		switch (HeapTupleSatisfiesVacuum(&tup, OldestXmin, buffer))
+		switch (relation->rd_stamroutine->snapshot_satisfiesVacuum(&tup, OldestXmin, buffer))
 		{
 			case HEAPTUPLE_DEAD:
 				tupdead = true;
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 05d7da001a..01321a2543 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -472,7 +472,7 @@ systable_recheck_tuple(SysScanDesc sysscan, HeapTuple tup)
 		Assert(BufferIsValid(scan->xs_cbuf));
 		/* must hold a buffer lock to call HeapTupleSatisfiesVisibility */
 		LockBuffer(scan->xs_cbuf, BUFFER_LOCK_SHARE);
-		result = HeapTupleSatisfiesVisibility(tup, freshsnap, scan->xs_cbuf);
+		result = HeapTupleSatisfiesVisibility(sysscan->heap_rel->rd_stamroutine, tup, freshsnap, scan->xs_cbuf);
 		LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK);
 	}
 	else
@@ -484,7 +484,7 @@ systable_recheck_tuple(SysScanDesc sysscan, HeapTuple tup)
 		Assert(BufferIsValid(scan->rs_cbuf));
 		/* must hold a buffer lock to call HeapTupleSatisfiesVisibility */
 		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-		result = HeapTupleSatisfiesVisibility(tup, freshsnap, scan->rs_cbuf);
+		result = HeapTupleSatisfiesVisibility(sysscan->heap_rel->rd_stamroutine, tup, freshsnap, scan->rs_cbuf);
 		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
 	}
 	return result;
diff --git a/src/backend/access/storage/Makefile b/src/backend/access/storage/Makefile
index 2a05c7ce66..321676820f 100644
--- a/src/backend/access/storage/Makefile
+++ b/src/backend/access/storage/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/storage
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = storageam.o storageamapi.o
+OBJS = storageam.o storageamapi.o storage_common.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/storage/storage_common.c b/src/backend/access/storage/storage_common.c
new file mode 100644
index 0000000000..3ede680c89
--- /dev/null
+++ b/src/backend/access/storage/storage_common.c
@@ -0,0 +1,277 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_common.c
+ *	  storage access method code that is common across all pluggable
+ *	  storage modules
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/storage/storage_common.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/storage_common.h"
+#include "access/subtrans.h"
+#include "access/transam.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "storage/bufmgr.h"
+#include "storage/procarray.h"
+
+/* Static variables representing various special snapshot semantics */
+SnapshotData SnapshotSelfData = {SELF_VISIBILITY};
+SnapshotData SnapshotAnyData = {ANY_VISIBILITY};
+
+/*
+ * HeapTupleSetHintBits --- exported version of SetHintBits()
+ *
+ * This must be separate because of C99's brain-dead notions about how to
+ * implement inline functions.
+ */
+void
+HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
+					 uint16 infomask, TransactionId xid)
+{
+	SetHintBits(tuple, buffer, infomask, xid);
+}
+
+
+/*
+ * Is the tuple really only locked?  That is, is it not updated?
+ *
+ * It's easy to check just infomask bits if the locker is not a multi; but
+ * otherwise we need to verify that the updating transaction has not aborted.
+ *
+ * This function is here because it follows the same time qualification rules
+ * laid out at the top of this file.
+ */
+bool
+HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple)
+{
+	TransactionId xmax;
+
+	/* if there's no valid Xmax, then there's obviously no update either */
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)
+		return true;
+
+	if (tuple->t_infomask & HEAP_XMAX_LOCK_ONLY)
+		return true;
+
+	/* invalid xmax means no update */
+	if (!TransactionIdIsValid(HeapTupleHeaderGetRawXmax(tuple)))
+		return true;
+
+	/*
+	 * if HEAP_XMAX_LOCK_ONLY is not set and not a multi, then this must
+	 * necessarily have been updated
+	 */
+	if (!(tuple->t_infomask & HEAP_XMAX_IS_MULTI))
+		return false;
+
+	/* ... but if it's a multi, then perhaps the updating Xid aborted. */
+	xmax = HeapTupleGetUpdateXid(tuple);
+
+	/* not LOCKED_ONLY, so it has to have an xmax */
+	Assert(TransactionIdIsValid(xmax));
+
+	if (TransactionIdIsCurrentTransactionId(xmax))
+		return false;
+	if (TransactionIdIsInProgress(xmax))
+		return false;
+	if (TransactionIdDidCommit(xmax))
+		return false;
+
+	/*
+	 * not current, not in progress, not committed -- must have aborted or
+	 * crashed
+	 */
+	return true;
+}
+
+
+/*
+ * HeapTupleIsSurelyDead
+ *
+ *	Cheaply determine whether a tuple is surely dead to all onlookers.
+ *	We sometimes use this in lieu of HeapTupleSatisfiesVacuum when the
+ *	tuple has just been tested by another visibility routine (usually
+ *	HeapTupleSatisfiesMVCC) and, therefore, any hint bits that can be set
+ *	should already be set.  We assume that if no hint bits are set, the xmin
+ *	or xmax transaction is still running.  This is therefore faster than
+ *	HeapTupleSatisfiesVacuum, because we don't consult PGXACT nor CLOG.
+ *	It's okay to return false when in doubt, but we must return TRUE only
+ *	if the tuple is removable.
+ */
+bool
+HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin)
+{
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	/*
+	 * If the inserting transaction is marked invalid, then it aborted, and
+	 * the tuple is definitely dead.  If it's marked neither committed nor
+	 * invalid, then we assume it's still alive (since the presumption is that
+	 * all relevant hint bits were just set moments ago).
+	 */
+	if (!HeapTupleHeaderXminCommitted(tuple))
+		return HeapTupleHeaderXminInvalid(tuple) ? true : false;
+
+	/*
+	 * If the inserting transaction committed, but any deleting transaction
+	 * aborted, the tuple is still alive.
+	 */
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)
+		return false;
+
+	/*
+	 * If the XMAX is just a lock, the tuple is still alive.
+	 */
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+		return false;
+
+	/*
+	 * If the Xmax is a MultiXact, it might be dead or alive, but we cannot
+	 * know without checking pg_multixact.
+	 */
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+		return false;
+
+	/* If deleter isn't known to have committed, assume it's still running. */
+	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
+		return false;
+
+	/* Deleter committed, so tuple is dead if the XID is old enough. */
+	return TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin);
+}
+
+/*
+ * XidInMVCCSnapshot
+ *		Is the given XID still-in-progress according to the snapshot?
+ *
+ * Note: GetSnapshotData never stores either top xid or subxids of our own
+ * backend into a snapshot, so these xids will not be reported as "running"
+ * by this function.  This is OK for current uses, because we always check
+ * TransactionIdIsCurrentTransactionId first, except when it's known the
+ * XID could not be ours anyway.
+ */
+bool
+XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot)
+{
+	uint32		i;
+
+	/*
+	 * Make a quick range check to eliminate most XIDs without looking at the
+	 * xip arrays.  Note that this is OK even if we convert a subxact XID to
+	 * its parent below, because a subxact with XID < xmin has surely also got
+	 * a parent with XID < xmin, while one with XID >= xmax must belong to a
+	 * parent that was not yet committed at the time of this snapshot.
+	 */
+
+	/* Any xid < xmin is not in-progress */
+	if (TransactionIdPrecedes(xid, snapshot->xmin))
+		return false;
+	/* Any xid >= xmax is in-progress */
+	if (TransactionIdFollowsOrEquals(xid, snapshot->xmax))
+		return true;
+
+	/*
+	 * Snapshot information is stored slightly differently in snapshots taken
+	 * during recovery.
+	 */
+	if (!snapshot->takenDuringRecovery)
+	{
+		/*
+		 * If the snapshot contains full subxact data, the fastest way to
+		 * check things is just to compare the given XID against both subxact
+		 * XIDs and top-level XIDs.  If the snapshot overflowed, we have to
+		 * use pg_subtrans to convert a subxact XID to its parent XID, but
+		 * then we need only look at top-level XIDs not subxacts.
+		 */
+		if (!snapshot->suboverflowed)
+		{
+			/* we have full data, so search subxip */
+			int32		j;
+
+			for (j = 0; j < snapshot->subxcnt; j++)
+			{
+				if (TransactionIdEquals(xid, snapshot->subxip[j]))
+					return true;
+			}
+
+			/* not there, fall through to search xip[] */
+		}
+		else
+		{
+			/*
+			 * Snapshot overflowed, so convert xid to top-level.  This is safe
+			 * because we eliminated too-old XIDs above.
+			 */
+			xid = SubTransGetTopmostTransaction(xid);
+
+			/*
+			 * If xid was indeed a subxact, we might now have an xid < xmin,
+			 * so recheck to avoid an array scan.  No point in rechecking
+			 * xmax.
+			 */
+			if (TransactionIdPrecedes(xid, snapshot->xmin))
+				return false;
+		}
+
+		for (i = 0; i < snapshot->xcnt; i++)
+		{
+			if (TransactionIdEquals(xid, snapshot->xip[i]))
+				return true;
+		}
+	}
+	else
+	{
+		int32		j;
+
+		/*
+		 * In recovery we store all xids in the subxact array because it is by
+		 * far the bigger array, and we mostly don't know which xids are
+		 * top-level and which are subxacts. The xip array is empty.
+		 *
+		 * We start by searching subtrans, if we overflowed.
+		 */
+		if (snapshot->suboverflowed)
+		{
+			/*
+			 * Snapshot overflowed, so convert xid to top-level.  This is safe
+			 * because we eliminated too-old XIDs above.
+			 */
+			xid = SubTransGetTopmostTransaction(xid);
+
+			/*
+			 * If xid was indeed a subxact, we might now have an xid < xmin,
+			 * so recheck to avoid an array scan.  No point in rechecking
+			 * xmax.
+			 */
+			if (TransactionIdPrecedes(xid, snapshot->xmin))
+				return false;
+		}
+
+		/*
+		 * We now have either a top-level xid higher than xmin or an
+		 * indeterminate xid. We don't know whether it's top level or subxact
+		 * but it doesn't matter. If it's present, the xid is visible.
+		 */
+		for (j = 0; j < snapshot->subxcnt; j++)
+		{
+			if (TransactionIdEquals(xid, snapshot->subxip[j]))
+				return true;
+		}
+	}
+
+	return false;
+}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 0125c18bc1..ead8d2abdf 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2225,6 +2225,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	TransactionId OldestXmin;
 	BlockNumber root_blkno = InvalidBlockNumber;
 	OffsetNumber root_offsets[MaxHeapTuplesPerPage];
+	StorageAmRoutine *method;
 
 	/*
 	 * sanity checks
@@ -2280,6 +2281,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 		OldestXmin = GetOldestXmin(heapRelation, PROCARRAY_FLAGS_VACUUM);
 	}
 
+	method = heapRelation->rd_stamroutine;
 	scan = heap_beginscan_strat(heapRelation,	/* relation */
 								snapshot,	/* snapshot */
 								0,	/* number of keys */
@@ -2360,8 +2362,8 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 			 */
 			LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 
-			switch (HeapTupleSatisfiesVacuum(heapTuple, OldestXmin,
-											 scan->rs_cbuf))
+			switch (method->snapshot_satisfiesVacuum(heapTuple, OldestXmin,
+													 scan->rs_cbuf))
 			{
 				case HEAPTUPLE_DEAD:
 					/* Definitely dead, we can ignore it */
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f952b3c732..4411c1d4de 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -1119,9 +1119,9 @@ acquire_sample_rows(Relation onerel, int elevel,
 			targtuple.t_data = (HeapTupleHeader) PageGetItem(targpage, itemid);
 			targtuple.t_len = ItemIdGetLength(itemid);
 
-			switch (HeapTupleSatisfiesVacuum(&targtuple,
-											 OldestXmin,
-											 targbuffer))
+			switch (onerel->rd_stamroutine->snapshot_satisfiesVacuum(&targtuple,
+																	 OldestXmin,
+																	 targbuffer))
 			{
 				case HEAPTUPLE_LIVE:
 					sample_it = true;
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 48f1e6e2ad..dbcc5bc172 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -21,6 +21,7 @@
 #include "access/multixact.h"
 #include "access/relscan.h"
 #include "access/rewriteheap.h"
+#include "access/storageamapi.h"
 #include "access/transam.h"
 #include "access/tuptoaster.h"
 #include "access/xact.h"
@@ -967,7 +968,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 
 		LockBuffer(buf, BUFFER_LOCK_SHARE);
 
-		switch (HeapTupleSatisfiesVacuum(tuple, OldestXmin, buf))
+		switch (OldHeap->rd_stamroutine->snapshot_satisfiesVacuum(tuple, OldestXmin, buf))
 		{
 			case HEAPTUPLE_DEAD:
 				/* Definitely dead */
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index f95346acdb..1cd6407946 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -988,7 +988,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
 
 			tupgone = false;
 
-			switch (HeapTupleSatisfiesVacuum(&tuple, OldestXmin, buf))
+			switch (onerel->rd_stamroutine->snapshot_satisfiesVacuum(&tuple, OldestXmin, buf))
 			{
 				case HEAPTUPLE_DEAD:
 
@@ -2162,7 +2162,7 @@ heap_page_is_all_visible(Relation rel, Buffer buf,
 		tuple.t_len = ItemIdGetLength(itemid);
 		tuple.t_tableOid = RelationGetRelid(rel);
 
-		switch (HeapTupleSatisfiesVacuum(&tuple, OldestXmin, buf))
+		switch (rel->rd_stamroutine->snapshot_satisfiesVacuum(&tuple, OldestXmin, buf))
 		{
 			case HEAPTUPLE_LIVE:
 				{
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index eb5bbb57ef..2c5c95d425 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -461,7 +461,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 			loctup.t_len = ItemIdGetLength(lp);
 			loctup.t_tableOid = scan->rs_rd->rd_id;
 			ItemPointerSet(&loctup.t_self, page, offnum);
-			valid = HeapTupleSatisfiesVisibility(&loctup, snapshot, buffer);
+			valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine, &loctup, snapshot, buffer);
 			if (valid)
 			{
 				scan->rs_vistuples[ntup++] = offnum;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index afb83ed3ae..79c34a6e6c 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -191,6 +191,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
  */
 static void
 ExecCheckHeapTupleVisible(EState *estate,
+						  Relation rel,
 						  HeapTuple tuple,
 						  Buffer buffer)
 {
@@ -202,7 +203,7 @@ ExecCheckHeapTupleVisible(EState *estate,
 	 * Caller should be holding pin, but not lock.
 	 */
 	LockBuffer(buffer, BUFFER_LOCK_SHARE);
-	if (!HeapTupleSatisfiesVisibility(tuple, estate->es_snapshot, buffer))
+	if (!HeapTupleSatisfiesVisibility(rel->rd_stamroutine, tuple, estate->es_snapshot, buffer))
 	{
 		/*
 		 * We should not raise a serialization failure if the conflict is
@@ -237,7 +238,7 @@ ExecCheckTIDVisible(EState *estate,
 	tuple.t_self = *tid;
 	if (!heap_fetch(rel, SnapshotAny, &tuple, &buffer, false, NULL))
 		elog(ERROR, "failed to fetch conflicting tuple for ON CONFLICT");
-	ExecCheckHeapTupleVisible(estate, &tuple, buffer);
+	ExecCheckHeapTupleVisible(estate, rel, &tuple, buffer);
 	ReleaseBuffer(buffer);
 }
 
@@ -1313,7 +1314,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	 * snapshot.  This is in line with the way UPDATE deals with newer tuple
 	 * versions.
 	 */
-	ExecCheckHeapTupleVisible(estate, &tuple, buffer);
+	ExecCheckHeapTupleVisible(estate, relation, &tuple, buffer);
 
 	/* Store target's existing tuple in the state's dedicated slot */
 	ExecStoreTuple(&tuple, mtstate->mt_existing, buffer, false);
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index 9c74a836e4..6a118d1883 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -588,7 +588,8 @@ SampleTupleVisible(HeapTuple tuple, OffsetNumber tupoffset, HeapScanDesc scan)
 	else
 	{
 		/* Otherwise, we have to check the tuple individually. */
-		return HeapTupleSatisfiesVisibility(tuple,
+		return HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine,
+											tuple,
 											scan->rs_snapshot,
 											scan->rs_cbuf);
 	}
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index ad65b9831d..86efe3a66b 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -376,7 +376,7 @@ static void
 SnapBuildFreeSnapshot(Snapshot snap)
 {
 	/* make sure we don't get passed an external snapshot */
-	Assert(snap->satisfies == HeapTupleSatisfiesHistoricMVCC);
+	Assert(snap->visibility_type == HISTORIC_MVCC_VISIBILITY);
 
 	/* make sure nobody modified our snapshot */
 	Assert(snap->curcid == FirstCommandId);
@@ -434,7 +434,7 @@ void
 SnapBuildSnapDecRefcount(Snapshot snap)
 {
 	/* make sure we don't get passed an external snapshot */
-	Assert(snap->satisfies == HeapTupleSatisfiesHistoricMVCC);
+	Assert(snap->visibility_type == HISTORIC_MVCC_VISIBILITY);
 
 	/* make sure nobody modified our snapshot */
 	Assert(snap->curcid == FirstCommandId);
@@ -476,7 +476,7 @@ SnapBuildBuildSnapshot(SnapBuild *builder)
 
 	snapshot = MemoryContextAllocZero(builder->context, ssize);
 
-	snapshot->satisfies = HeapTupleSatisfiesHistoricMVCC;
+	snapshot->visibility_type = HISTORIC_MVCC_VISIBILITY;
 
 	/*
 	 * We misuse the original meaning of SnapshotData's xip and subxip fields
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index 251a359bff..4fbad9f0f6 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -3972,7 +3972,7 @@ CheckForSerializableConflictOut(bool visible, Relation relation,
 	 * tuple is visible to us, while HeapTupleSatisfiesVacuum checks what else
 	 * is going on with it.
 	 */
-	htsvResult = HeapTupleSatisfiesVacuum(tuple, TransactionXmin, buffer);
+	htsvResult = relation->rd_stamroutine->snapshot_satisfiesVacuum(tuple, TransactionXmin, buffer);
 	switch (htsvResult)
 	{
 		case HEAPTUPLE_LIVE:
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index b1ae9e5f96..640e9634b3 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -287,7 +287,7 @@ RI_FKey_check(TriggerData *trigdata)
 	 * should be holding pin, but not lock.
 	 */
 	LockBuffer(new_row_buf, BUFFER_LOCK_SHARE);
-	if (!HeapTupleSatisfiesVisibility(new_row, SnapshotSelf, new_row_buf))
+	if (!HeapTupleSatisfiesVisibility(trigdata->tg_relation->rd_stamroutine, new_row, SnapshotSelf, new_row_buf))
 	{
 		LockBuffer(new_row_buf, BUFFER_LOCK_UNLOCK);
 		return PointerGetDatum(NULL);
diff --git a/src/backend/utils/time/Makefile b/src/backend/utils/time/Makefile
index 5a6e6fa4c8..f17b1c5324 100644
--- a/src/backend/utils/time/Makefile
+++ b/src/backend/utils/time/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/utils/time
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = combocid.o tqual.o snapmgr.o
+OBJS = combocid.o snapmgr.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c
index 0b032905a5..b8f195593f 100644
--- a/src/backend/utils/time/snapmgr.c
+++ b/src/backend/utils/time/snapmgr.c
@@ -141,9 +141,9 @@ static volatile OldSnapshotControlData *oldSnapshotControl;
  * These SnapshotData structs are static to simplify memory allocation
  * (see the hack in GetSnapshotData to avoid repeated malloc/free).
  */
-static SnapshotData CurrentSnapshotData = {HeapTupleSatisfiesMVCC};
-static SnapshotData SecondarySnapshotData = {HeapTupleSatisfiesMVCC};
-SnapshotData CatalogSnapshotData = {HeapTupleSatisfiesMVCC};
+static SnapshotData CurrentSnapshotData = {MVCC_VISIBILITY};
+static SnapshotData SecondarySnapshotData = {MVCC_VISIBILITY};
+SnapshotData CatalogSnapshotData = {MVCC_VISIBILITY};
 
 /* Pointers to valid snapshots */
 static Snapshot CurrentSnapshot = NULL;
@@ -2046,7 +2046,7 @@ EstimateSnapshotSpace(Snapshot snap)
 	Size		size;
 
 	Assert(snap != InvalidSnapshot);
-	Assert(snap->satisfies == HeapTupleSatisfiesMVCC);
+	Assert(snap->visibility_type == MVCC_VISIBILITY);
 
 	/* We allocate any XID arrays needed in the same palloc block. */
 	size = add_size(sizeof(SerializedSnapshotData),
@@ -2143,7 +2143,7 @@ RestoreSnapshot(char *start_address)
 
 	/* Copy all required fields */
 	snapshot = (Snapshot) MemoryContextAlloc(TopTransactionContext, size);
-	snapshot->satisfies = HeapTupleSatisfiesMVCC;
+	snapshot->visibility_type = MVCC_VISIBILITY;
 	snapshot->xmin = serialized_snapshot.xmin;
 	snapshot->xmax = serialized_snapshot.xmax;
 	snapshot->xip = NULL;
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index f1366ed958..0ba2ec5894 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -16,6 +16,7 @@
 
 #include "access/sdir.h"
 #include "access/skey.h"
+#include "access/storage_common.h"
 #include "nodes/lockoptions.h"
 #include "nodes/primnodes.h"
 #include "storage/bufpage.h"
@@ -200,4 +201,11 @@ extern BlockNumber ss_get_location(Relation rel, BlockNumber relnblocks);
 extern void SyncScanShmemInit(void);
 extern Size SyncScanShmemSize(void);
 
+/* in heap/heapam_visibility.c */
+extern bool HeapTupleSatisfies(StorageTuple stup, Snapshot snapshot, Buffer buffer);
+extern HTSU_Result HeapTupleSatisfiesUpdate(StorageTuple stup, CommandId curcid,
+						 Buffer buffer);
+extern HTSV_Result HeapTupleSatisfiesVacuum(StorageTuple stup, TransactionId OldestXmin,
+						 Buffer buffer);
+
 #endif							/* HEAPAM_H */
diff --git a/src/include/access/storage_common.h b/src/include/access/storage_common.h
new file mode 100644
index 0000000000..60fb44b6cd
--- /dev/null
+++ b/src/include/access/storage_common.h
@@ -0,0 +1,101 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_common.h
+ *	  POSTGRES storage access method definitions shared across
+ *	  all pluggable storage methods and server.
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/storage_common.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_COMMON_H
+#define STORAGE_COMMON_H
+
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/transam.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "storage/bufpage.h"
+#include "storage/bufmgr.h"
+
+
+/* A physical tuple coming from a storage AM scan */
+typedef void *StorageTuple;
+
+/* Result codes for HeapTupleSatisfiesVacuum */
+typedef enum
+{
+	HEAPTUPLE_DEAD,				/* tuple is dead and deletable */
+	HEAPTUPLE_LIVE,				/* tuple is live (committed, no deleter) */
+	HEAPTUPLE_RECENTLY_DEAD,	/* tuple is dead, but not deletable yet */
+	HEAPTUPLE_INSERT_IN_PROGRESS,	/* inserting xact is still in progress */
+	HEAPTUPLE_DELETE_IN_PROGRESS	/* deleting xact is still in progress */
+} HTSV_Result;
+
+
+/* in storage/storage_common.c */
+extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
+					 uint16 infomask, TransactionId xid);
+extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
+extern bool HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin);
+extern bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
+
+/*
+ * SetHintBits()
+ *
+ * Set commit/abort hint bits on a tuple, if appropriate at this time.
+ *
+ * It is only safe to set a transaction-committed hint bit if we know the
+ * transaction's commit record is guaranteed to be flushed to disk before the
+ * buffer, or if the table is temporary or unlogged and will be obliterated by
+ * a crash anyway.  We cannot change the LSN of the page here, because we may
+ * hold only a share lock on the buffer, so we can only use the LSN to
+ * interlock this if the buffer's LSN already is newer than the commit LSN;
+ * otherwise we have to just refrain from setting the hint bit until some
+ * future re-examination of the tuple.
+ *
+ * We can always set hint bits when marking a transaction aborted.  (Some
+ * code in heapam.c relies on that!)
+ *
+ * Also, if we are cleaning up HEAP_MOVED_IN or HEAP_MOVED_OFF entries, then
+ * we can always set the hint bits, since pre-9.0 VACUUM FULL always used
+ * synchronous commits and didn't move tuples that weren't previously
+ * hinted.  (This is not known by this subroutine, but is applied by its
+ * callers.)  Note: old-style VACUUM FULL is gone, but we have to keep this
+ * module's support for MOVED_OFF/MOVED_IN flag bits for as long as we
+ * support in-place update from pre-9.0 databases.
+ *
+ * Normal commits may be asynchronous, so for those we need to get the LSN
+ * of the transaction and then check whether this is flushed.
+ *
+ * The caller should pass xid as the XID of the transaction to check, or
+ * InvalidTransactionId if no check is needed.
+ */
+static inline void
+SetHintBits(HeapTupleHeader tuple, Buffer buffer,
+			uint16 infomask, TransactionId xid)
+{
+	if (TransactionIdIsValid(xid))
+	{
+		/* NB: xid must be known committed here! */
+		XLogRecPtr	commitLSN = TransactionIdGetCommitLSN(xid);
+
+		if (BufferIsPermanent(buffer) && XLogNeedsFlush(commitLSN) &&
+			BufferGetLSNAtomic(buffer) < commitLSN)
+		{
+			/* not flushed and no LSN interlock, so don't set hint */
+			return;
+		}
+	}
+
+	tuple->t_infomask |= infomask;
+	MarkBufferDirtyHint(buffer, true);
+}
+
+#endif							/* STORAGE_COMMON_H */
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
index 6fae4eea5c..56a791d0c6 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -11,11 +11,19 @@
 #ifndef STORAGEAMAPI_H
 #define STORAGEAMAPI_H
 
+#include "access/storage_common.h"
 #include "nodes/nodes.h"
+#include "utils/snapshot.h"
 #include "fmgr.h"
 
-/* A physical tuple coming from a storage AM scan */
-typedef void *StorageTuple;
+
+/*
+ * Storage routine function hooks
+ */
+typedef bool (*SnapshotSatisfies_function) (StorageTuple htup, Snapshot snapshot, Buffer buffer);
+typedef HTSU_Result (*SnapshotSatisfiesUpdate_function) (StorageTuple htup, CommandId curcid, Buffer buffer);
+typedef HTSV_Result (*SnapshotSatisfiesVacuum_function) (StorageTuple htup, TransactionId OldestXmin, Buffer buffer);
+
 
 /*
  * API struct for a storage AM.  Note this must be stored in a single palloc'd
@@ -30,6 +38,10 @@ typedef struct StorageAmRoutine
 {
 	NodeTag		type;
 
+	SnapshotSatisfies_function snapshot_satisfies;
+	SnapshotSatisfiesUpdate_function snapshot_satisfiesUpdate;	/* HeapTupleSatisfiesUpdate */
+	SnapshotSatisfiesVacuum_function snapshot_satisfiesVacuum;	/* HeapTupleSatisfiesVacuum */
+
 }			StorageAmRoutine;
 
 extern StorageAmRoutine * GetStorageAmRoutine(Oid amhandler);
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 98b63fc5ba..1a403b8e21 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -20,7 +20,6 @@
 #include "storage/relfilenode.h"
 #include "utils/relcache.h"
 #include "utils/snapmgr.h"
-#include "utils/tqual.h"
 
 typedef void *Block;
 
@@ -268,8 +267,8 @@ TestForOldSnapshot(Snapshot snapshot, Relation relation, Page page)
 
 	if (old_snapshot_threshold >= 0
 		&& (snapshot) != NULL
-		&& ((snapshot)->satisfies == HeapTupleSatisfiesMVCC
-			|| (snapshot)->satisfies == HeapTupleSatisfiesToast)
+		&& ((snapshot)->visibility_type == MVCC_VISIBILITY
+			|| (snapshot)->visibility_type == TOAST_VISIBILITY)
 		&& !XLogRecPtrIsInvalid((snapshot)->lsn)
 		&& PageGetLSN(page) > (snapshot)->lsn)
 		TestForOldSnapshot_impl(snapshot, relation);
diff --git a/src/include/utils/snapshot.h b/src/include/utils/snapshot.h
index bf519778df..3896349b39 100644
--- a/src/include/utils/snapshot.h
+++ b/src/include/utils/snapshot.h
@@ -19,6 +19,18 @@
 #include "lib/pairingheap.h"
 #include "storage/buf.h"
 
+typedef enum tuple_visibility_type
+{
+	MVCC_VISIBILITY = 0,		/* HeapTupleSatisfiesMVCC */
+	SELF_VISIBILITY,			/* HeapTupleSatisfiesSelf */
+	ANY_VISIBILITY,				/* HeapTupleSatisfiesAny */
+	TOAST_VISIBILITY,			/* HeapTupleSatisfiesToast */
+	DIRTY_VISIBILITY,			/* HeapTupleSatisfiesDirty */
+	HISTORIC_MVCC_VISIBILITY,	/* HeapTupleSatisfiesHistoricMVCC */
+	NON_VACUUMABLE_VISIBILTY,	/* HeapTupleSatisfiesNonVacuumable */
+
+	END_OF_VISIBILITY
+}			tuple_visibility_type;
 
 typedef struct SnapshotData *Snapshot;
 
@@ -52,7 +64,7 @@ typedef bool (*SnapshotSatisfiesFunc) (HeapTuple htup,
  */
 typedef struct SnapshotData
 {
-	SnapshotSatisfiesFunc satisfies;	/* tuple test function */
+	tuple_visibility_type visibility_type;	/* tuple visibility test type */
 
 	/*
 	 * The remaining fields are used only for MVCC snapshots, and are normally
diff --git a/src/include/utils/tqual.h b/src/include/utils/tqual.h
index 96eaf01ca0..c2ec33e57c 100644
--- a/src/include/utils/tqual.h
+++ b/src/include/utils/tqual.h
@@ -16,6 +16,7 @@
 #define TQUAL_H
 
 #include "utils/snapshot.h"
+#include "access/storageamapi.h"
 #include "access/xlogdefs.h"
 
 
@@ -29,8 +30,8 @@ extern PGDLLIMPORT SnapshotData CatalogSnapshotData;
 
 /* This macro encodes the knowledge of which snapshots are MVCC-safe */
 #define IsMVCCSnapshot(snapshot)  \
-	((snapshot)->satisfies == HeapTupleSatisfiesMVCC || \
-	 (snapshot)->satisfies == HeapTupleSatisfiesHistoricMVCC)
+	((snapshot)->visibility_type == MVCC_VISIBILITY || \
+	 (snapshot)->visibility_type == HISTORIC_MVCC_VISIBILITY)
 
 /*
  * HeapTupleSatisfiesVisibility
@@ -42,47 +43,8 @@ extern PGDLLIMPORT SnapshotData CatalogSnapshotData;
  *	Hint bits in the HeapTuple's t_infomask may be updated as a side effect;
  *	if so, the indicated buffer is marked dirty.
  */
-#define HeapTupleSatisfiesVisibility(tuple, snapshot, buffer) \
-	((*(snapshot)->satisfies) (tuple, snapshot, buffer))
-
-/* Result codes for HeapTupleSatisfiesVacuum */
-typedef enum
-{
-	HEAPTUPLE_DEAD,				/* tuple is dead and deletable */
-	HEAPTUPLE_LIVE,				/* tuple is live (committed, no deleter) */
-	HEAPTUPLE_RECENTLY_DEAD,	/* tuple is dead, but not deletable yet */
-	HEAPTUPLE_INSERT_IN_PROGRESS,	/* inserting xact is still in progress */
-	HEAPTUPLE_DELETE_IN_PROGRESS	/* deleting xact is still in progress */
-} HTSV_Result;
-
-/* These are the "satisfies" test routines for the various snapshot types */
-extern bool HeapTupleSatisfiesMVCC(HeapTuple htup,
-					   Snapshot snapshot, Buffer buffer);
-extern bool HeapTupleSatisfiesSelf(HeapTuple htup,
-					   Snapshot snapshot, Buffer buffer);
-extern bool HeapTupleSatisfiesAny(HeapTuple htup,
-					  Snapshot snapshot, Buffer buffer);
-extern bool HeapTupleSatisfiesToast(HeapTuple htup,
-						Snapshot snapshot, Buffer buffer);
-extern bool HeapTupleSatisfiesDirty(HeapTuple htup,
-						Snapshot snapshot, Buffer buffer);
-extern bool HeapTupleSatisfiesNonVacuumable(HeapTuple htup,
-								Snapshot snapshot, Buffer buffer);
-extern bool HeapTupleSatisfiesHistoricMVCC(HeapTuple htup,
-							   Snapshot snapshot, Buffer buffer);
-
-/* Special "satisfies" routines with different APIs */
-extern HTSU_Result HeapTupleSatisfiesUpdate(HeapTuple htup,
-						 CommandId curcid, Buffer buffer);
-extern HTSV_Result HeapTupleSatisfiesVacuum(HeapTuple htup,
-						 TransactionId OldestXmin, Buffer buffer);
-extern bool HeapTupleIsSurelyDead(HeapTuple htup,
-					  TransactionId OldestXmin);
-extern bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
-
-extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
-					 uint16 infomask, TransactionId xid);
-extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
+#define HeapTupleSatisfiesVisibility(method, tuple, snapshot, buffer) \
+	(((method)->snapshot_satisfies) (tuple, snapshot, buffer))
 
 /*
  * To avoid leaking too much knowledge about reorderbuffer implementation
@@ -101,14 +63,14 @@ extern bool ResolveCminCmaxDuringDecoding(struct HTAB *tuplecid_data,
  * local variable of type SnapshotData, and initialize it with this macro.
  */
 #define InitDirtySnapshot(snapshotdata)  \
-	((snapshotdata).satisfies = HeapTupleSatisfiesDirty)
+	((snapshotdata).visibility_type = DIRTY_VISIBILITY)
 
 /*
  * Similarly, some initialization is required for a NonVacuumable snapshot.
  * The caller must supply the xmin horizon to use (e.g., RecentGlobalXmin).
  */
 #define InitNonVacuumableSnapshot(snapshotdata, xmin_horizon)  \
-	((snapshotdata).satisfies = HeapTupleSatisfiesNonVacuumable, \
+	((snapshotdata).visibility_type = NON_VACUUMABLE_VISIBILTY, \
 	 (snapshotdata).xmin = (xmin_horizon))
 
 /*
@@ -116,7 +78,7 @@ extern bool ResolveCminCmaxDuringDecoding(struct HTAB *tuplecid_data,
  * to set lsn and whenTaken correctly to support snapshot_too_old.
  */
 #define InitToastSnapshot(snapshotdata, l, w)  \
-	((snapshotdata).satisfies = HeapTupleSatisfiesToast, \
+	((snapshotdata).visibility_type = TOAST_VISIBILITY, \
 	 (snapshotdata).lsn = (l),					\
 	 (snapshotdata).whenTaken = (w))
 
-- 
2.15.0.windows.1



  [application/octet-stream] 0005-slot-hooks-are-added-to-storage-AM.patch (62.1K, ../../CAJrrPGc951F-R4Kfa4W47B5vHKeHsB2Y34zewp=b+AWSkF9RVA@mail.gmail.com/9-0005-slot-hooks-are-added-to-storage-AM.patch)
  download | inline diff:
From 7a8e4e7aaa199a31b355f3149385428e8cf8135f Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Wed, 27 Dec 2017 12:41:34 +1100
Subject: [PATCH 05/11] slot hooks are added to storage AM

The tuple is removed as part of the slot and added
an void pointer to store the tuple data that can
understand only by the storage AM routine.

The slot utility functions are reorganized to use
two storageAM routines to satify the current
functionality.

Currently the slot supports minimum tuple also.
---
 src/backend/access/common/heaptuple.c       | 302 +--------------------
 src/backend/access/heap/heapam_storage.c    |   2 +
 src/backend/access/storage/storage_common.c | 404 ++++++++++++++++++++++++++++
 src/backend/commands/copy.c                 |   2 +-
 src/backend/commands/createas.c             |   2 +-
 src/backend/commands/matview.c              |   2 +-
 src/backend/commands/trigger.c              |  15 +-
 src/backend/executor/execExprInterp.c       |  35 +--
 src/backend/executor/execReplication.c      |  90 ++-----
 src/backend/executor/execTuples.c           | 268 +++++++++---------
 src/backend/executor/nodeForeignscan.c      |   2 +-
 src/backend/executor/nodeModifyTable.c      |  24 +-
 src/backend/executor/tqueue.c               |   2 +-
 src/backend/replication/logical/worker.c    |   5 +-
 src/include/access/htup_details.h           |  15 +-
 src/include/access/storage_common.h         |  38 +++
 src/include/access/storageamapi.h           |   2 +
 src/include/executor/tuptable.h             |  54 ++--
 18 files changed, 689 insertions(+), 575 deletions(-)

diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index a1a9d9905b..c29935b91a 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -57,6 +57,7 @@
 
 #include "postgres.h"
 
+#include "access/storageamapi.h"
 #include "access/sysattr.h"
 #include "access/tuptoaster.h"
 #include "executor/tuptable.h"
@@ -1021,111 +1022,6 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
 	}
 }
 
-/*
- * slot_deform_tuple
- *		Given a TupleTableSlot, extract data from the slot's physical tuple
- *		into its Datum/isnull arrays.  Data is extracted up through the
- *		natts'th column (caller must ensure this is a legal column number).
- *
- *		This is essentially an incremental version of heap_deform_tuple:
- *		on each call we extract attributes up to the one needed, without
- *		re-computing information about previously extracted attributes.
- *		slot->tts_nvalid is the number of attributes already extracted.
- */
-static void
-slot_deform_tuple(TupleTableSlot *slot, int natts)
-{
-	HeapTuple	tuple = slot->tts_tuple;
-	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
-	Datum	   *values = slot->tts_values;
-	bool	   *isnull = slot->tts_isnull;
-	HeapTupleHeader tup = tuple->t_data;
-	bool		hasnulls = HeapTupleHasNulls(tuple);
-	int			attnum;
-	char	   *tp;				/* ptr to tuple data */
-	long		off;			/* offset in tuple data */
-	bits8	   *bp = tup->t_bits;	/* ptr to null bitmap in tuple */
-	bool		slow;			/* can we use/set attcacheoff? */
-
-	/*
-	 * Check whether the first call for this tuple, and initialize or restore
-	 * loop state.
-	 */
-	attnum = slot->tts_nvalid;
-	if (attnum == 0)
-	{
-		/* Start from the first attribute */
-		off = 0;
-		slow = false;
-	}
-	else
-	{
-		/* Restore state from previous execution */
-		off = slot->tts_off;
-		slow = slot->tts_slow;
-	}
-
-	tp = (char *) tup + tup->t_hoff;
-
-	for (; attnum < natts; attnum++)
-	{
-		Form_pg_attribute thisatt = TupleDescAttr(tupleDesc, attnum);
-
-		if (hasnulls && att_isnull(attnum, bp))
-		{
-			values[attnum] = (Datum) 0;
-			isnull[attnum] = true;
-			slow = true;		/* can't use attcacheoff anymore */
-			continue;
-		}
-
-		isnull[attnum] = false;
-
-		if (!slow && thisatt->attcacheoff >= 0)
-			off = thisatt->attcacheoff;
-		else if (thisatt->attlen == -1)
-		{
-			/*
-			 * We can only cache the offset for a varlena attribute if the
-			 * offset is already suitably aligned, so that there would be no
-			 * pad bytes in any case: then the offset will be valid for either
-			 * an aligned or unaligned value.
-			 */
-			if (!slow &&
-				off == att_align_nominal(off, thisatt->attalign))
-				thisatt->attcacheoff = off;
-			else
-			{
-				off = att_align_pointer(off, thisatt->attalign, -1,
-										tp + off);
-				slow = true;
-			}
-		}
-		else
-		{
-			/* not varlena, so safe to use att_align_nominal */
-			off = att_align_nominal(off, thisatt->attalign);
-
-			if (!slow)
-				thisatt->attcacheoff = off;
-		}
-
-		values[attnum] = fetchatt(thisatt, tp + off);
-
-		off = att_addlength_pointer(off, thisatt->attlen, tp + off);
-
-		if (thisatt->attlen <= 0)
-			slow = true;		/* can't use attcacheoff anymore */
-	}
-
-	/*
-	 * Save state for next execution
-	 */
-	slot->tts_nvalid = attnum;
-	slot->tts_off = off;
-	slot->tts_slow = slow;
-}
-
 /*
  * slot_getattr
  *		This function fetches an attribute of the slot's current tuple.
@@ -1141,91 +1037,7 @@ slot_deform_tuple(TupleTableSlot *slot, int natts)
 Datum
 slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
 {
-	HeapTuple	tuple = slot->tts_tuple;
-	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
-	HeapTupleHeader tup;
-
-	/*
-	 * system attributes are handled by heap_getsysattr
-	 */
-	if (attnum <= 0)
-	{
-		if (tuple == NULL)		/* internal error */
-			elog(ERROR, "cannot extract system attribute from virtual tuple");
-		if (tuple == &(slot->tts_minhdr))	/* internal error */
-			elog(ERROR, "cannot extract system attribute from minimal tuple");
-		return heap_getsysattr(tuple, attnum, tupleDesc, isnull);
-	}
-
-	/*
-	 * fast path if desired attribute already cached
-	 */
-	if (attnum <= slot->tts_nvalid)
-	{
-		*isnull = slot->tts_isnull[attnum - 1];
-		return slot->tts_values[attnum - 1];
-	}
-
-	/*
-	 * return NULL if attnum is out of range according to the tupdesc
-	 */
-	if (attnum > tupleDesc->natts)
-	{
-		*isnull = true;
-		return (Datum) 0;
-	}
-
-	/*
-	 * otherwise we had better have a physical tuple (tts_nvalid should equal
-	 * natts in all virtual-tuple cases)
-	 */
-	if (tuple == NULL)			/* internal error */
-		elog(ERROR, "cannot extract attribute from empty tuple slot");
-
-	/*
-	 * return NULL if attnum is out of range according to the tuple
-	 *
-	 * (We have to check this separately because of various inheritance and
-	 * table-alteration scenarios: the tuple could be either longer or shorter
-	 * than the tupdesc.)
-	 */
-	tup = tuple->t_data;
-	if (attnum > HeapTupleHeaderGetNatts(tup))
-	{
-		*isnull = true;
-		return (Datum) 0;
-	}
-
-	/*
-	 * check if target attribute is null: no point in groveling through tuple
-	 */
-	if (HeapTupleHasNulls(tuple) && att_isnull(attnum - 1, tup->t_bits))
-	{
-		*isnull = true;
-		return (Datum) 0;
-	}
-
-	/*
-	 * If the attribute's column has been dropped, we force a NULL result.
-	 * This case should not happen in normal use, but it could happen if we
-	 * are executing a plan cached before the column was dropped.
-	 */
-	if (TupleDescAttr(tupleDesc, attnum - 1)->attisdropped)
-	{
-		*isnull = true;
-		return (Datum) 0;
-	}
-
-	/*
-	 * Extract the attribute, along with any preceding attributes.
-	 */
-	slot_deform_tuple(slot, attnum);
-
-	/*
-	 * The result is acquired from tts_values array.
-	 */
-	*isnull = slot->tts_isnull[attnum - 1];
-	return slot->tts_values[attnum - 1];
+	return slot->tts_storageslotam->slot_getattr(slot, attnum, isnull);
 }
 
 /*
@@ -1237,40 +1049,7 @@ slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
 void
 slot_getallattrs(TupleTableSlot *slot)
 {
-	int			tdesc_natts = slot->tts_tupleDescriptor->natts;
-	int			attnum;
-	HeapTuple	tuple;
-
-	/* Quick out if we have 'em all already */
-	if (slot->tts_nvalid == tdesc_natts)
-		return;
-
-	/*
-	 * otherwise we had better have a physical tuple (tts_nvalid should equal
-	 * natts in all virtual-tuple cases)
-	 */
-	tuple = slot->tts_tuple;
-	if (tuple == NULL)			/* internal error */
-		elog(ERROR, "cannot extract attribute from empty tuple slot");
-
-	/*
-	 * load up any slots available from physical tuple
-	 */
-	attnum = HeapTupleHeaderGetNatts(tuple->t_data);
-	attnum = Min(attnum, tdesc_natts);
-
-	slot_deform_tuple(slot, attnum);
-
-	/*
-	 * If tuple doesn't have all the atts indicated by tupleDesc, read the
-	 * rest as null
-	 */
-	for (; attnum < tdesc_natts; attnum++)
-	{
-		slot->tts_values[attnum] = (Datum) 0;
-		slot->tts_isnull[attnum] = true;
-	}
-	slot->tts_nvalid = tdesc_natts;
+	slot->tts_storageslotam->slot_virtualize_tuple(slot, slot->tts_tupleDescriptor->natts);
 }
 
 /*
@@ -1281,43 +1060,7 @@ slot_getallattrs(TupleTableSlot *slot)
 void
 slot_getsomeattrs(TupleTableSlot *slot, int attnum)
 {
-	HeapTuple	tuple;
-	int			attno;
-
-	/* Quick out if we have 'em all already */
-	if (slot->tts_nvalid >= attnum)
-		return;
-
-	/* Check for caller error */
-	if (attnum <= 0 || attnum > slot->tts_tupleDescriptor->natts)
-		elog(ERROR, "invalid attribute number %d", attnum);
-
-	/*
-	 * otherwise we had better have a physical tuple (tts_nvalid should equal
-	 * natts in all virtual-tuple cases)
-	 */
-	tuple = slot->tts_tuple;
-	if (tuple == NULL)			/* internal error */
-		elog(ERROR, "cannot extract attribute from empty tuple slot");
-
-	/*
-	 * load up any slots available from physical tuple
-	 */
-	attno = HeapTupleHeaderGetNatts(tuple->t_data);
-	attno = Min(attno, attnum);
-
-	slot_deform_tuple(slot, attno);
-
-	/*
-	 * If tuple doesn't have all the atts indicated by tupleDesc, read the
-	 * rest as null
-	 */
-	for (; attno < attnum; attno++)
-	{
-		slot->tts_values[attno] = (Datum) 0;
-		slot->tts_isnull[attno] = true;
-	}
-	slot->tts_nvalid = attnum;
+	slot->tts_storageslotam->slot_virtualize_tuple(slot, attnum);
 }
 
 /*
@@ -1328,42 +1071,11 @@ slot_getsomeattrs(TupleTableSlot *slot, int attnum)
 bool
 slot_attisnull(TupleTableSlot *slot, int attnum)
 {
-	HeapTuple	tuple = slot->tts_tuple;
-	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
-
-	/*
-	 * system attributes are handled by heap_attisnull
-	 */
-	if (attnum <= 0)
-	{
-		if (tuple == NULL)		/* internal error */
-			elog(ERROR, "cannot extract system attribute from virtual tuple");
-		if (tuple == &(slot->tts_minhdr))	/* internal error */
-			elog(ERROR, "cannot extract system attribute from minimal tuple");
-		return heap_attisnull(tuple, attnum);
-	}
-
-	/*
-	 * fast path if desired attribute already cached
-	 */
-	if (attnum <= slot->tts_nvalid)
-		return slot->tts_isnull[attnum - 1];
-
-	/*
-	 * return NULL if attnum is out of range according to the tupdesc
-	 */
-	if (attnum > tupleDesc->natts)
-		return true;
+	bool		isnull;
 
-	/*
-	 * otherwise we had better have a physical tuple (tts_nvalid should equal
-	 * natts in all virtual-tuple cases)
-	 */
-	if (tuple == NULL)			/* internal error */
-		elog(ERROR, "cannot extract attribute from empty tuple slot");
+	slot->tts_storageslotam->slot_getattr(slot, attnum, &isnull);
 
-	/* and let the tuple tell it */
-	return heap_attisnull(tuple, attnum);
+	return isnull;
 }
 
 /*
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index a340c46a80..a953a690b3 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -35,5 +35,7 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 	amroutine->snapshot_satisfiesUpdate = HeapTupleSatisfiesUpdate;
 	amroutine->snapshot_satisfiesVacuum = HeapTupleSatisfiesVacuum;
 
+	amroutine->slot_storageam = heapam_storage_slot_handler;
+
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/storage/storage_common.c b/src/backend/access/storage/storage_common.c
index 3ede680c89..b65153bb38 100644
--- a/src/backend/access/storage/storage_common.c
+++ b/src/backend/access/storage/storage_common.c
@@ -95,6 +95,410 @@ HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple)
 	return true;
 }
 
+/*-----------------------
+ *
+ * Slot storage handler API
+ * ----------------------
+ */
+
+static HeapTuple
+heapam_get_tuple(TupleTableSlot *slot, bool palloc_copy)
+{
+	HeapTuple	tup;
+	HeapamTuple *stuple = (HeapamTuple *) slot->tts_storage;
+
+	if (stuple)
+	{
+		if (stuple->hst_mintuple)
+		{
+			tup = heap_tuple_from_minimal_tuple(stuple->hst_mintuple);
+		}
+		else
+		{
+			if (!palloc_copy)
+				tup = stuple->hst_heaptuple;
+			else
+				tup = heap_copytuple(stuple->hst_heaptuple);
+		}
+	}
+	else
+	{
+		tup = heap_form_tuple(slot->tts_tupleDescriptor,
+							  slot->tts_values,
+							  slot->tts_isnull);
+	}
+
+	return tup;
+}
+
+static MinimalTuple
+heapam_get_min_tuple(TupleTableSlot *slot, bool palloc_copy)
+{
+	MinimalTuple tup;
+	HeapamTuple *stuple = (HeapamTuple *) slot->tts_storage;
+
+	if (stuple)
+	{
+		if (stuple->hst_mintuple)
+		{
+			if (!palloc_copy)
+				tup = stuple->hst_mintuple;
+			else
+				tup = heap_copy_minimal_tuple(stuple->hst_mintuple);
+		}
+		else
+		{
+			tup = minimal_tuple_from_heap_tuple(stuple->hst_heaptuple);
+		}
+	}
+	else
+	{
+		tup = heap_form_minimal_tuple(slot->tts_tupleDescriptor,
+									  slot->tts_values,
+									  slot->tts_isnull);
+	}
+
+	return tup;
+}
+
+
+/*
+ * slot_deform_tuple
+ *		Given a TupleTableSlot, extract data from the slot's physical tuple
+ *		into its Datum/isnull arrays.  Data is extracted up through the
+ *		natts'th column (caller must ensure this is a legal column number).
+ *
+ *		This is essentially an incremental version of heap_deform_tuple:
+ *		on each call we extract attributes up to the one needed, without
+ *		re-computing information about previously extracted attributes.
+ *		slot->tts_nvalid is the number of attributes already extracted.
+ */
+static void
+slot_deform_tuple(TupleTableSlot *slot, int natts)
+{
+	HeapamTuple *stuple = (HeapamTuple *) slot->tts_storage;
+	HeapTuple	tuple = stuple ? stuple->hst_heaptuple : NULL;
+	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
+	Datum	   *values = slot->tts_values;
+	bool	   *isnull = slot->tts_isnull;
+	HeapTupleHeader tup = tuple->t_data;
+	bool		hasnulls = HeapTupleHasNulls(tuple);
+	int			attnum;
+	char	   *tp;				/* ptr to tuple data */
+	long		off;			/* offset in tuple data */
+	bits8	   *bp = tup->t_bits;	/* ptr to null bitmap in tuple */
+	bool		slow;			/* can we use/set attcacheoff? */
+
+	/*
+	 * Check whether the first call for this tuple, and initialize or restore
+	 * loop state.
+	 */
+	attnum = slot->tts_nvalid;
+	if (attnum == 0)
+	{
+		/* Start from the first attribute */
+		off = 0;
+		slow = false;
+	}
+	else
+	{
+		/* Restore state from previous execution */
+		off = stuple->hst_off;
+		slow = stuple->hst_slow;
+	}
+
+	tp = (char *) tup + tup->t_hoff;
+
+	for (; attnum < natts; attnum++)
+	{
+		Form_pg_attribute thisatt = TupleDescAttr(tupleDesc, attnum);
+
+		if (hasnulls && att_isnull(attnum, bp))
+		{
+			values[attnum] = (Datum) 0;
+			isnull[attnum] = true;
+			slow = true;		/* can't use attcacheoff anymore */
+			continue;
+		}
+
+		isnull[attnum] = false;
+
+		if (!slow && thisatt->attcacheoff >= 0)
+			off = thisatt->attcacheoff;
+		else if (thisatt->attlen == -1)
+		{
+			/*
+			 * We can only cache the offset for a varlena attribute if the
+			 * offset is already suitably aligned, so that there would be no
+			 * pad bytes in any case: then the offset will be valid for either
+			 * an aligned or unaligned value.
+			 */
+			if (!slow &&
+				off == att_align_nominal(off, thisatt->attalign))
+				thisatt->attcacheoff = off;
+			else
+			{
+				off = att_align_pointer(off, thisatt->attalign, -1,
+										tp + off);
+				slow = true;
+			}
+		}
+		else
+		{
+			/* not varlena, so safe to use att_align_nominal */
+			off = att_align_nominal(off, thisatt->attalign);
+
+			if (!slow)
+				thisatt->attcacheoff = off;
+		}
+
+		values[attnum] = fetchatt(thisatt, tp + off);
+
+		off = att_addlength_pointer(off, thisatt->attlen, tp + off);
+
+		if (thisatt->attlen <= 0)
+			slow = true;		/* can't use attcacheoff anymore */
+	}
+
+	/*
+	 * Save state for next execution
+	 */
+	slot->tts_nvalid = attnum;
+	stuple->hst_off = off;
+	stuple->hst_slow = slow;
+}
+
+static void
+heapam_slot_virtualize_tuple(TupleTableSlot *slot, int16 upto)
+{
+	HeapamTuple *stuple;
+	HeapTuple	tuple;
+	int			attno;
+
+	/* Quick out if we have 'em all already */
+	if (slot->tts_nvalid >= upto)
+		return;
+
+	/* Check for caller error */
+	if (upto <= 0 || upto > slot->tts_tupleDescriptor->natts)
+		elog(ERROR, "invalid attribute number %d", upto);
+
+	/*
+	 * otherwise we had better have a physical tuple (tts_nvalid should equal
+	 * natts in all virtual-tuple cases)
+	 */
+	stuple = slot->tts_storage; /* XXX SlotGetTupleStorage(slot) ??? */
+	tuple = stuple->hst_heaptuple;
+	if (tuple == NULL)			/* internal error */
+		elog(ERROR, "cannot extract attribute from empty tuple slot");
+
+	/*
+	 * load up any slots available from physical tuple
+	 */
+	attno = HeapTupleHeaderGetNatts(tuple->t_data);
+	attno = Min(attno, upto);
+
+	slot_deform_tuple(slot, attno);
+
+	/*
+	 * If tuple doesn't have all the atts indicated by tupleDesc, read the
+	 * rest as null
+	 */
+	for (; attno < upto; attno++)
+	{
+		slot->tts_values[attno] = (Datum) 0;
+		slot->tts_isnull[attno] = true;
+	}
+	slot->tts_nvalid = upto;
+}
+
+static void
+heapam_slot_update_tuple_tableoid(TupleTableSlot *slot, Oid tableoid)
+{
+	HeapTuple	tuple;
+
+	tuple = heapam_get_tuple(slot, false);
+	tuple->t_tableOid = tableoid;
+}
+
+static void
+heapam_slot_store_tuple(TupleTableSlot *slot, StorageTuple tuple, bool shouldFree, bool minimum_tuple)
+{
+	HeapamTuple *stuple;
+	MemoryContext oldcontext;
+
+	oldcontext = MemoryContextSwitchTo(slot->tts_mcxt);
+
+	stuple = (HeapamTuple *) palloc0(sizeof(HeapamTuple));
+
+	if (!minimum_tuple)
+	{
+		stuple->hst_heaptuple = tuple;
+		stuple->hst_slow = false;
+		stuple->hst_off = 0;
+		stuple->hst_mintuple = NULL;
+		slot->tts_shouldFreeMin = false;
+		slot->tts_shouldFree = shouldFree;
+	}
+	else
+	{
+		stuple->hst_mintuple = tuple;
+		stuple->hst_minhdr.t_len = ((MinimalTuple) tuple)->t_len + MINIMAL_TUPLE_OFFSET;
+		stuple->hst_minhdr.t_data = (HeapTupleHeader) ((char *) tuple - MINIMAL_TUPLE_OFFSET);
+		stuple->hst_heaptuple = &stuple->hst_minhdr;
+		slot->tts_shouldFreeMin = shouldFree;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	slot->tts_tid = ((HeapTuple) tuple)->t_self;
+	slot->tts_storage = stuple;
+}
+
+static void
+heapam_slot_clear_tuple(TupleTableSlot *slot)
+{
+	HeapamTuple *stuple;
+
+	/* XXX should this be an Assert() instead? */
+	if (slot->tts_isempty)
+		return;
+
+	stuple = slot->tts_storage;
+	if (stuple == NULL)
+		return;
+
+	if (slot->tts_shouldFree)
+		heap_freetuple(stuple->hst_heaptuple);
+
+	if (slot->tts_shouldFreeMin)
+		heap_free_minimal_tuple(stuple->hst_mintuple);
+
+	slot->tts_shouldFree = false;
+	slot->tts_shouldFreeMin = false;
+
+	pfree(stuple);
+	slot->tts_storage = NULL;
+}
+
+/*
+ * slot_getattr
+ *		This function fetches an attribute of the slot's current tuple.
+ *		It is functionally equivalent to heap_getattr, but fetches of
+ *		multiple attributes of the same tuple will be optimized better,
+ *		because we avoid O(N^2) behavior from multiple calls of
+ *		nocachegetattr(), even when attcacheoff isn't usable.
+ *
+ *		A difference from raw heap_getattr is that attnums beyond the
+ *		slot's tupdesc's last attribute will be considered NULL even
+ *		when the physical tuple is longer than the tupdesc.
+ */
+static Datum
+heapam_slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
+{
+	HeapamTuple *stuple = slot->tts_storage;
+	HeapTuple	tuple = stuple ? stuple->hst_heaptuple : NULL;
+	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
+	HeapTupleHeader tup;
+
+	/*
+	 * system attributes are handled by heap_getsysattr
+	 */
+	if (attnum <= 0)
+	{
+		if (tuple == NULL)		/* internal error */
+			elog(ERROR, "cannot extract system attribute from virtual tuple");
+		if (tuple == &(stuple->hst_minhdr)) /* internal error */
+			elog(ERROR, "cannot extract system attribute from minimal tuple");
+		return heap_getsysattr(tuple, attnum, tupleDesc, isnull);
+	}
+
+	/*
+	 * fast path if desired attribute already cached
+	 */
+	if (attnum <= slot->tts_nvalid)
+	{
+		*isnull = slot->tts_isnull[attnum - 1];
+		return slot->tts_values[attnum - 1];
+	}
+
+	/*
+	 * return NULL if attnum is out of range according to the tupdesc
+	 */
+	if (attnum > tupleDesc->natts)
+	{
+		*isnull = true;
+		return (Datum) 0;
+	}
+
+	/*
+	 * otherwise we had better have a physical tuple (tts_nvalid should equal
+	 * natts in all virtual-tuple cases)
+	 */
+	if (tuple == NULL)			/* internal error */
+		elog(ERROR, "cannot extract attribute from empty tuple slot");
+
+	/*
+	 * return NULL if attnum is out of range according to the tuple
+	 *
+	 * (We have to check this separately because of various inheritance and
+	 * table-alteration scenarios: the tuple could be either longer or shorter
+	 * than the tupdesc.)
+	 */
+	tup = tuple->t_data;
+	if (attnum > HeapTupleHeaderGetNatts(tup))
+	{
+		*isnull = true;
+		return (Datum) 0;
+	}
+
+	/*
+	 * check if target attribute is null: no point in groveling through tuple
+	 */
+	if (HeapTupleHasNulls(tuple) && att_isnull(attnum - 1, tup->t_bits))
+	{
+		*isnull = true;
+		return (Datum) 0;
+	}
+
+	/*
+	 * If the attribute's column has been dropped, we force a NULL result.
+	 * This case should not happen in normal use, but it could happen if we
+	 * are executing a plan cached before the column was dropped.
+	 */
+	if (TupleDescAttr(tupleDesc, (attnum - 1))->attisdropped)
+	{
+		*isnull = true;
+		return (Datum) 0;
+	}
+
+	/*
+	 * Extract the attribute, along with any preceding attributes.
+	 */
+	slot_deform_tuple(slot, attnum);
+
+	/*
+	 * The result is acquired from tts_values array.
+	 */
+	*isnull = slot->tts_isnull[attnum - 1];
+	return slot->tts_values[attnum - 1];
+}
+
+StorageSlotAmRoutine *
+heapam_storage_slot_handler(void)
+{
+	StorageSlotAmRoutine *amroutine = palloc(sizeof(StorageSlotAmRoutine));
+
+	amroutine->slot_store_tuple = heapam_slot_store_tuple;
+	amroutine->slot_virtualize_tuple = heapam_slot_virtualize_tuple;
+	amroutine->slot_clear_tuple = heapam_slot_clear_tuple;
+	amroutine->slot_tuple = heapam_get_tuple;
+	amroutine->slot_min_tuple = heapam_get_min_tuple;
+	amroutine->slot_getattr = heapam_slot_getattr;
+	amroutine->slot_update_tableoid = heapam_slot_update_tuple_tableoid;
+
+	return amroutine;
+}
 
 /*
  * HeapTupleIsSurelyDead
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 254be28ae4..0a0eecf509 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2720,7 +2720,7 @@ CopyFrom(CopyState cstate)
 			if (slot == NULL)	/* "do nothing" */
 				skip_tuple = true;
 			else				/* trigger might have changed tuple */
-				tuple = ExecMaterializeSlot(slot);
+				tuple = ExecHeapifySlot(slot);
 		}
 
 		if (!skip_tuple)
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 4d77411a68..213a8cccbc 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -588,7 +588,7 @@ intorel_receive(TupleTableSlot *slot, DestReceiver *self)
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
 	 */
-	tuple = ExecMaterializeSlot(slot);
+	tuple = ExecHeapifySlot(slot);
 
 	/*
 	 * force assignment of new OID (see comments in ExecInsert)
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index d2e0376511..b440740e28 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -497,7 +497,7 @@ transientrel_receive(TupleTableSlot *slot, DestReceiver *self)
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
 	 */
-	tuple = ExecMaterializeSlot(slot);
+	tuple = ExecHeapifySlot(slot);
 
 	heap_insert(myState->transientrel,
 				tuple,
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 92ae3822d8..96c4fe7d43 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2289,7 +2289,7 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 					 TupleTableSlot *slot)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
-	HeapTuple	slottuple = ExecMaterializeSlot(slot);
+	HeapTuple	slottuple = ExecHeapifySlot(slot);
 	HeapTuple	newtuple = slottuple;
 	HeapTuple	oldtuple;
 	TriggerData LocTriggerData;
@@ -2370,7 +2370,7 @@ ExecIRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 					 TupleTableSlot *slot)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
-	HeapTuple	slottuple = ExecMaterializeSlot(slot);
+	HeapTuple	slottuple = ExecHeapifySlot(slot);
 	HeapTuple	newtuple = slottuple;
 	HeapTuple	oldtuple;
 	TriggerData LocTriggerData;
@@ -2728,7 +2728,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 					 TupleTableSlot *slot)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
-	HeapTuple	slottuple = ExecMaterializeSlot(slot);
+	HeapTuple	slottuple = ExecHeapifySlot(slot);
 	HeapTuple	newtuple = slottuple;
 	TriggerData LocTriggerData;
 	HeapTuple	trigtuple;
@@ -2770,7 +2770,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 	if (newSlot != NULL)
 	{
 		slot = ExecFilterJunk(relinfo->ri_junkFilter, newSlot);
-		slottuple = ExecMaterializeSlot(slot);
+		slottuple = ExecHeapifySlot(slot);
 		newtuple = slottuple;
 	}
 
@@ -2879,7 +2879,7 @@ ExecIRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 					 HeapTuple trigtuple, TupleTableSlot *slot)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
-	HeapTuple	slottuple = ExecMaterializeSlot(slot);
+	HeapTuple	slottuple = ExecHeapifySlot(slot);
 	HeapTuple	newtuple = slottuple;
 	TriggerData LocTriggerData;
 	HeapTuple	oldtuple;
@@ -4006,14 +4006,13 @@ AfterTriggerExecute(AfterTriggerEvent event,
 			 * because we start with a minimal tuple that ExecFetchSlotTuple()
 			 * must materialize anyway.
 			 */
-			LocTriggerData.tg_trigtuple =
-				ExecMaterializeSlot(trig_tuple_slot1);
+			LocTriggerData.tg_trigtuple = ExecHeapifySlot(trig_tuple_slot1);
 			LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
 
 			LocTriggerData.tg_newtuple =
 				((evtshared->ats_event & TRIGGER_EVENT_OPMASK) ==
 				 TRIGGER_EVENT_UPDATE) ?
-				ExecMaterializeSlot(trig_tuple_slot2) : NULL;
+				ExecHeapifySlot(trig_tuple_slot2) : NULL;
 			LocTriggerData.tg_newtuplebuf = InvalidBuffer;
 
 			break;
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 0c3f66803f..9ef6f0edf8 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -509,13 +509,15 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			Datum		d;
 
 			/* these asserts must match defenses in slot_getattr */
-			Assert(innerslot->tts_tuple != NULL);
-			Assert(innerslot->tts_tuple != &(innerslot->tts_minhdr));
+			Assert(innerslot->tts_storage != NULL);
+
+			/*
+			 * hari
+			 * Assert(innerslot->tts_storageslotam->slot_is_physical_tuple(innerslot));
+			 */
 
 			/* heap_getsysattr has sufficient defenses against bad attnums */
-			d = heap_getsysattr(innerslot->tts_tuple, attnum,
-								innerslot->tts_tupleDescriptor,
-								op->resnull);
+			d = slot_getattr(innerslot, attnum, op->resnull);
 			*op->resvalue = d;
 
 			EEO_NEXT();
@@ -527,13 +529,14 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			Datum		d;
 
 			/* these asserts must match defenses in slot_getattr */
-			Assert(outerslot->tts_tuple != NULL);
-			Assert(outerslot->tts_tuple != &(outerslot->tts_minhdr));
+			Assert(outerslot->tts_storage != NULL);
 
+			/*
+			 * hari
+			 * Assert(outerslot->tts_storageslotam->slot_is_physical_tuple(outerslot));
+			 */
 			/* heap_getsysattr has sufficient defenses against bad attnums */
-			d = heap_getsysattr(outerslot->tts_tuple, attnum,
-								outerslot->tts_tupleDescriptor,
-								op->resnull);
+			d = slot_getattr(outerslot, attnum, op->resnull);
 			*op->resvalue = d;
 
 			EEO_NEXT();
@@ -545,13 +548,15 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			Datum		d;
 
 			/* these asserts must match defenses in slot_getattr */
-			Assert(scanslot->tts_tuple != NULL);
-			Assert(scanslot->tts_tuple != &(scanslot->tts_minhdr));
+			Assert(scanslot->tts_storage != NULL);
+
+			/*
+			 * hari
+			 * Assert(scanslot->tts_storageslotam->slot_is_physical_tuple(scanslot));
+			 */
 
 			/* heap_getsysattr has sufficient defenses against bad attnums */
-			d = heap_getsysattr(scanslot->tts_tuple, attnum,
-								scanslot->tts_tupleDescriptor,
-								op->resnull);
+			d = slot_getattr(scanslot, attnum, op->resnull);
 			*op->resvalue = d;
 
 			EEO_NEXT();
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index bd786a1be6..12c15fd6bc 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -171,7 +171,7 @@ retry:
 		HTSU_Result res;
 		HeapTupleData locktup;
 
-		ItemPointerCopy(&outslot->tts_tuple->t_self, &locktup.t_self);
+		ItemPointerCopy(&outslot->tts_tid, &locktup.t_self);
 
 		PushActiveSnapshot(GetLatestSnapshot());
 
@@ -211,59 +211,6 @@ retry:
 	return found;
 }
 
-/*
- * Compare the tuple and slot and check if they have equal values.
- *
- * We use binary datum comparison which might return false negatives but
- * that's the best we can do here as there may be multiple notions of
- * equality for the data types and table columns don't specify which one
- * to use.
- */
-static bool
-tuple_equals_slot(TupleDesc desc, HeapTuple tup, TupleTableSlot *slot)
-{
-	Datum		values[MaxTupleAttributeNumber];
-	bool		isnull[MaxTupleAttributeNumber];
-	int			attrnum;
-
-	heap_deform_tuple(tup, desc, values, isnull);
-
-	/* Check equality of the attributes. */
-	for (attrnum = 0; attrnum < desc->natts; attrnum++)
-	{
-		Form_pg_attribute att;
-		TypeCacheEntry *typentry;
-
-		/*
-		 * If one value is NULL and other is not, then they are certainly not
-		 * equal
-		 */
-		if (isnull[attrnum] != slot->tts_isnull[attrnum])
-			return false;
-
-		/*
-		 * If both are NULL, they can be considered equal.
-		 */
-		if (isnull[attrnum])
-			continue;
-
-		att = TupleDescAttr(desc, attrnum);
-
-		typentry = lookup_type_cache(att->atttypid, TYPECACHE_EQ_OPR_FINFO);
-		if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
-			ereport(ERROR,
-					(errcode(ERRCODE_UNDEFINED_FUNCTION),
-					 errmsg("could not identify an equality operator for type %s",
-							format_type_be(att->atttypid))));
-
-		if (!DatumGetBool(FunctionCall2(&typentry->eq_opr_finfo,
-										values[attrnum],
-										slot->tts_values[attrnum])))
-			return false;
-	}
-
-	return true;
-}
 
 /*
  * Search the relation 'rel' for tuple using the sequential scan.
@@ -279,6 +226,7 @@ bool
 RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 						 TupleTableSlot *searchslot, TupleTableSlot *outslot)
 {
+	TupleTableSlot *scanslot;
 	HeapTuple	scantuple;
 	HeapScanDesc scan;
 	SnapshotData snap;
@@ -292,6 +240,8 @@ RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 	InitDirtySnapshot(snap);
 	scan = heap_beginscan(rel, &snap, 0, NULL);
 
+	scanslot = MakeSingleTupleTableSlot(desc);
+
 retry:
 	found = false;
 
@@ -300,12 +250,12 @@ retry:
 	/* Try to find the tuple */
 	while ((scantuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
 	{
-		if (!tuple_equals_slot(desc, scantuple, searchslot))
+		ExecStoreTuple(scantuple, scanslot, InvalidBuffer, false);
+		if (!ExecSlotCompare(scanslot, searchslot))
 			continue;
 
 		found = true;
-		ExecStoreTuple(scantuple, outslot, InvalidBuffer, false);
-		ExecMaterializeSlot(outslot);
+		ExecCopySlot(outslot, scanslot);
 
 		xwait = TransactionIdIsValid(snap.xmin) ?
 			snap.xmin : snap.xmax;
@@ -329,7 +279,7 @@ retry:
 		HTSU_Result res;
 		HeapTupleData locktup;
 
-		ItemPointerCopy(&outslot->tts_tuple->t_self, &locktup.t_self);
+		ItemPointerCopy(&outslot->tts_tid, &locktup.t_self);
 
 		PushActiveSnapshot(GetLatestSnapshot());
 
@@ -362,6 +312,7 @@ retry:
 	}
 
 	heap_endscan(scan);
+	ExecDropSingleTupleTableSlot(scanslot);
 
 	return found;
 }
@@ -404,7 +355,7 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		/* Store the slot into tuple that we can inspect. */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		/* OK, store the tuple and create index entries for it */
 		simple_heap_insert(rel, tuple);
@@ -442,6 +393,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
+	ItemPointer tid = &(searchslot->tts_tid);
 
 	/* For now we support only tables. */
 	Assert(rel->rd_rel->relkind == RELKIND_RELATION);
@@ -453,7 +405,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 		resultRelInfo->ri_TrigDesc->trig_update_before_row)
 	{
 		slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
-									&searchslot->tts_tuple->t_self,
+									tid,
 									NULL, slot);
 
 		if (slot == NULL)		/* "do nothing" */
@@ -469,21 +421,20 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		/* Store the slot into tuple that we can write. */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		/* OK, update the tuple and index entries for it */
-		simple_heap_update(rel, &searchslot->tts_tuple->t_self,
-						   slot->tts_tuple);
+		simple_heap_update(rel, tid, tuple);
 
 		if (resultRelInfo->ri_NumIndices > 0 &&
-			!HeapTupleIsHeapOnly(slot->tts_tuple))
-			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+			!HeapTupleIsHeapOnly(tuple))
+			recheckIndexes = ExecInsertIndexTuples(slot, tid,
 												   estate, false, NULL,
 												   NIL);
 
 		/* AFTER ROW UPDATE Triggers */
 		ExecARUpdateTriggers(estate, resultRelInfo,
-							 &searchslot->tts_tuple->t_self,
+							 tid,
 							 NULL, tuple, recheckIndexes, NULL);
 
 		list_free(recheckIndexes);
@@ -503,6 +454,7 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate,
 	bool		skip_tuple = false;
 	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
+	ItemPointer tid = &(searchslot->tts_tid);
 
 	/* For now we support only tables. */
 	Assert(rel->rd_rel->relkind == RELKIND_RELATION);
@@ -514,7 +466,7 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate,
 		resultRelInfo->ri_TrigDesc->trig_delete_before_row)
 	{
 		skip_tuple = !ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
-										   &searchslot->tts_tuple->t_self,
+										   tid,
 										   NULL);
 	}
 
@@ -523,11 +475,11 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate,
 		List	   *recheckIndexes = NIL;
 
 		/* OK, delete the tuple */
-		simple_heap_delete(rel, &searchslot->tts_tuple->t_self);
+		simple_heap_delete(rel, tid);
 
 		/* AFTER ROW DELETE Triggers */
 		ExecARDeleteTriggers(estate, resultRelInfo,
-							 &searchslot->tts_tuple->t_self, NULL, NULL);
+							 tid, NULL, NULL);
 
 		list_free(recheckIndexes);
 	}
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 51d2c5d166..01ca94f9e5 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -82,6 +82,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/storage_common.h"
 #include "access/tuptoaster.h"
 #include "funcapi.h"
 #include "catalog/pg_type.h"
@@ -113,16 +114,15 @@ MakeTupleTableSlot(void)
 	TupleTableSlot *slot = makeNode(TupleTableSlot);
 
 	slot->tts_isempty = true;
-	slot->tts_shouldFree = false;
-	slot->tts_shouldFreeMin = false;
-	slot->tts_tuple = NULL;
 	slot->tts_tupleDescriptor = NULL;
 	slot->tts_mcxt = CurrentMemoryContext;
-	slot->tts_buffer = InvalidBuffer;
 	slot->tts_nvalid = 0;
 	slot->tts_values = NULL;
 	slot->tts_isnull = NULL;
-	slot->tts_mintuple = NULL;
+	slot->tts_tupleOid = InvalidOid;
+	slot->tts_tableOid = InvalidOid;
+	slot->tts_storageslotam = heapam_storage_slot_handler();
+	slot->tts_storage = NULL;
 
 	return slot;
 }
@@ -205,6 +205,54 @@ MakeSingleTupleTableSlot(TupleDesc tupdesc)
 	return slot;
 }
 
+/* --------------------------------
+ *		ExecSlotCompare
+ *
+ *		This is a slot comparision function to find out
+ *		whether both the slots are same or not?
+ * --------------------------------
+ */
+bool
+ExecSlotCompare(TupleTableSlot *slot1, TupleTableSlot *slot2)
+{
+	int			attrnum;
+
+	Assert(slot1->tts_tupleDescriptor->natts == slot2->tts_tupleDescriptor->natts);
+
+	slot_getallattrs(slot1);
+	slot_getallattrs(slot2);
+
+	/* Check equality of the attributes. */
+	for (attrnum = 0; attrnum < slot1->tts_tupleDescriptor->natts; attrnum++)
+	{
+		Form_pg_attribute att;
+		TypeCacheEntry *typentry;
+
+		/*
+		 * If one value is NULL and other is not, then they are certainly not
+		 * equal
+		 */
+		if (slot1->tts_isnull[attrnum] != slot2->tts_isnull[attrnum])
+			return false;
+
+		att = TupleDescAttr(slot1->tts_tupleDescriptor, attrnum);
+
+		typentry = lookup_type_cache(att->atttypid, TYPECACHE_EQ_OPR_FINFO);
+		if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_FUNCTION),
+					 errmsg("could not identify an equality operator for type %s",
+							format_type_be(att->atttypid))));
+
+		if (!DatumGetBool(FunctionCall2(&typentry->eq_opr_finfo,
+										slot1->tts_values[attrnum],
+										slot2->tts_values[attrnum])))
+			return false;
+	}
+
+	return true;
+}
+
 /* --------------------------------
  *		ExecDropSingleTupleTableSlot
  *
@@ -317,7 +365,7 @@ ExecSetSlotDescriptor(TupleTableSlot *slot, /* slot to change */
  * --------------------------------
  */
 TupleTableSlot *
-ExecStoreTuple(HeapTuple tuple,
+ExecStoreTuple(void *tuple,
 			   TupleTableSlot *slot,
 			   Buffer buffer,
 			   bool shouldFree)
@@ -328,47 +376,27 @@ ExecStoreTuple(HeapTuple tuple,
 	Assert(tuple != NULL);
 	Assert(slot != NULL);
 	Assert(slot->tts_tupleDescriptor != NULL);
+	Assert(slot->tts_storageslotam != NULL);
 	/* passing shouldFree=true for a tuple on a disk page is not sane */
 	Assert(BufferIsValid(buffer) ? (!shouldFree) : true);
 
 	/*
 	 * Free any old physical tuple belonging to the slot.
 	 */
-	if (slot->tts_shouldFree)
-		heap_freetuple(slot->tts_tuple);
-	if (slot->tts_shouldFreeMin)
-		heap_free_minimal_tuple(slot->tts_mintuple);
+	slot->tts_storageslotam->slot_clear_tuple(slot);	/* XXX ?? */
 
 	/*
-	 * Store the new tuple into the specified slot.
+	 * Store the new tuple into the specified slot, and mark the slot as no
+	 * longer empty.  This clears any previously stored physical tuple.
 	 */
+	/* XXX should we pass the buffer down to the storageAM perhaps? */
+	slot->tts_storageslotam->slot_store_tuple(slot, tuple, shouldFree, false);
+
 	slot->tts_isempty = false;
-	slot->tts_shouldFree = shouldFree;
-	slot->tts_shouldFreeMin = false;
-	slot->tts_tuple = tuple;
-	slot->tts_mintuple = NULL;
 
 	/* Mark extracted state invalid */
 	slot->tts_nvalid = 0;
 
-	/*
-	 * If tuple is on a disk page, keep the page pinned as long as we hold a
-	 * pointer into it.  We assume the caller already has such a pin.
-	 *
-	 * This is coded to optimize the case where the slot previously held a
-	 * tuple on the same disk page: in that case releasing and re-acquiring
-	 * the pin is a waste of cycles.  This is a common situation during
-	 * seqscans, so it's worth troubling over.
-	 */
-	if (slot->tts_buffer != buffer)
-	{
-		if (BufferIsValid(slot->tts_buffer))
-			ReleaseBuffer(slot->tts_buffer);
-		slot->tts_buffer = buffer;
-		if (BufferIsValid(buffer))
-			IncrBufferRefCount(buffer);
-	}
-
 	return slot;
 }
 
@@ -395,31 +423,19 @@ ExecStoreMinimalTuple(MinimalTuple mtup,
 	/*
 	 * Free any old physical tuple belonging to the slot.
 	 */
-	if (slot->tts_shouldFree)
-		heap_freetuple(slot->tts_tuple);
-	if (slot->tts_shouldFreeMin)
-		heap_free_minimal_tuple(slot->tts_mintuple);
-
-	/*
-	 * Drop the pin on the referenced buffer, if there is one.
-	 */
-	if (BufferIsValid(slot->tts_buffer))
-		ReleaseBuffer(slot->tts_buffer);
-
-	slot->tts_buffer = InvalidBuffer;
+	slot->tts_storageslotam->slot_clear_tuple(slot);	/* XXX ?? */
 
 	/*
 	 * Store the new tuple into the specified slot.
 	 */
 	slot->tts_isempty = false;
-	slot->tts_shouldFree = false;
-	slot->tts_shouldFreeMin = shouldFree;
-	slot->tts_tuple = &slot->tts_minhdr;
-	slot->tts_mintuple = mtup;
 
-	slot->tts_minhdr.t_len = mtup->t_len + MINIMAL_TUPLE_OFFSET;
-	slot->tts_minhdr.t_data = (HeapTupleHeader) ((char *) mtup - MINIMAL_TUPLE_OFFSET);
-	/* no need to set t_self or t_tableOid since we won't allow access */
+	/*
+	 * Store the new tuple into the specified slot, and mark the slot as no
+	 * longer empty.  This clears any previously stored physical tuple.
+	 */
+	/* XXX should we pass the buffer down to the storageAM perhaps? */
+	slot->tts_storageslotam->slot_store_tuple(slot, mtup, false, true);
 
 	/* Mark extracted state invalid */
 	slot->tts_nvalid = 0;
@@ -444,25 +460,9 @@ ExecClearTuple(TupleTableSlot *slot)	/* slot in which to store tuple */
 	Assert(slot != NULL);
 
 	/*
-	 * Free the old physical tuple if necessary.
+	 * Tell the storage AM to release any resource associated with the slot.
 	 */
-	if (slot->tts_shouldFree)
-		heap_freetuple(slot->tts_tuple);
-	if (slot->tts_shouldFreeMin)
-		heap_free_minimal_tuple(slot->tts_mintuple);
-
-	slot->tts_tuple = NULL;
-	slot->tts_mintuple = NULL;
-	slot->tts_shouldFree = false;
-	slot->tts_shouldFreeMin = false;
-
-	/*
-	 * Drop the pin on the referenced buffer, if there is one.
-	 */
-	if (BufferIsValid(slot->tts_buffer))
-		ReleaseBuffer(slot->tts_buffer);
-
-	slot->tts_buffer = InvalidBuffer;
+	slot->tts_storageslotam->slot_clear_tuple(slot);
 
 	/*
 	 * Mark it empty.
@@ -541,7 +541,7 @@ ExecStoreAllNullTuple(TupleTableSlot *slot)
  *		however the "system columns" of the result will not be meaningful.
  * --------------------------------
  */
-HeapTuple
+StorageTuple
 ExecCopySlotTuple(TupleTableSlot *slot)
 {
 	/*
@@ -550,20 +550,7 @@ ExecCopySlotTuple(TupleTableSlot *slot)
 	Assert(slot != NULL);
 	Assert(!slot->tts_isempty);
 
-	/*
-	 * If we have a physical tuple (either format) then just copy it.
-	 */
-	if (TTS_HAS_PHYSICAL_TUPLE(slot))
-		return heap_copytuple(slot->tts_tuple);
-	if (slot->tts_mintuple)
-		return heap_tuple_from_minimal_tuple(slot->tts_mintuple);
-
-	/*
-	 * Otherwise we need to build a tuple from the Datum array.
-	 */
-	return heap_form_tuple(slot->tts_tupleDescriptor,
-						   slot->tts_values,
-						   slot->tts_isnull);
+	return slot->tts_storageslotam->slot_tuple(slot, true);
 }
 
 /* --------------------------------
@@ -582,21 +569,19 @@ ExecCopySlotMinimalTuple(TupleTableSlot *slot)
 	Assert(slot != NULL);
 	Assert(!slot->tts_isempty);
 
-	/*
-	 * If we have a physical tuple then just copy it.  Prefer to copy
-	 * tts_mintuple since that's a tad cheaper.
-	 */
-	if (slot->tts_mintuple)
-		return heap_copy_minimal_tuple(slot->tts_mintuple);
-	if (slot->tts_tuple)
-		return minimal_tuple_from_heap_tuple(slot->tts_tuple);
+	return slot->tts_storageslotam->slot_min_tuple(slot, true);
+}
 
+void
+ExecSlotUpdateTupleTableoid(TupleTableSlot *slot, Oid tableoid)
+{
 	/*
-	 * Otherwise we need to build a tuple from the Datum array.
+	 * sanity checks
 	 */
-	return heap_form_minimal_tuple(slot->tts_tupleDescriptor,
-								   slot->tts_values,
-								   slot->tts_isnull);
+	Assert(slot != NULL);
+	Assert(!slot->tts_isempty);
+
+	slot->tts_storageslotam->slot_update_tableoid(slot, tableoid);
 }
 
 /* --------------------------------
@@ -614,25 +599,34 @@ ExecCopySlotMinimalTuple(TupleTableSlot *slot)
  * Hence, the result must be treated as read-only.
  * --------------------------------
  */
-HeapTuple
+StorageTuple
 ExecFetchSlotTuple(TupleTableSlot *slot)
 {
+	MemoryContext oldContext;
+	StorageTuple tup;
+
 	/*
 	 * sanity checks
 	 */
 	Assert(slot != NULL);
 	Assert(!slot->tts_isempty);
 
-	/*
-	 * If we have a regular physical tuple then just return it.
-	 */
-	if (TTS_HAS_PHYSICAL_TUPLE(slot))
-		return slot->tts_tuple;
+	if (slot->tts_shouldFree)
+		return slot->tts_storageslotam->slot_tuple(slot, false);
 
 	/*
-	 * Otherwise materialize the slot...
+	 * Otherwise, copy or build a tuple, and store it into the slot.
+	 *
+	 * We may be called in a context that is shorter-lived than the tuple
+	 * slot, but we have to ensure that the materialized tuple will survive
+	 * anyway.
 	 */
-	return ExecMaterializeSlot(slot);
+	oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
+	tup = ExecCopySlotTuple(slot);
+	ExecStoreTuple(tup, slot, InvalidBuffer, true);
+	MemoryContextSwitchTo(oldContext);
+
+	return tup;
 }
 
 /* --------------------------------
@@ -652,6 +646,7 @@ MinimalTuple
 ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 {
 	MemoryContext oldContext;
+	MinimalTuple tup;
 
 	/*
 	 * sanity checks
@@ -659,11 +654,8 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 	Assert(slot != NULL);
 	Assert(!slot->tts_isempty);
 
-	/*
-	 * If we have a minimal physical tuple (local or not) then just return it.
-	 */
-	if (slot->tts_mintuple)
-		return slot->tts_mintuple;
+	if (slot->tts_shouldFreeMin)
+		return slot->tts_storageslotam->slot_min_tuple(slot, false);
 
 	/*
 	 * Otherwise, copy or build a minimal tuple, and store it into the slot.
@@ -673,18 +665,11 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 	 * anyway.
 	 */
 	oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
-	slot->tts_mintuple = ExecCopySlotMinimalTuple(slot);
-	slot->tts_shouldFreeMin = true;
+	tup = ExecCopySlotMinimalTuple(slot);
+	ExecStoreMinimalTuple(tup, slot, true);
 	MemoryContextSwitchTo(oldContext);
 
-	/*
-	 * Note: we may now have a situation where we have a local minimal tuple
-	 * attached to a virtual or non-local physical tuple.  There seems no harm
-	 * in that at the moment, but if any materializes, we should change this
-	 * function to force the slot into minimal-tuple-only state.
-	 */
-
-	return slot->tts_mintuple;
+	return tup;
 }
 
 /* --------------------------------
@@ -713,18 +698,19 @@ ExecFetchSlotTupleDatum(TupleTableSlot *slot)
  *			Force a slot into the "materialized" state.
  *
  *		This causes the slot's tuple to be a local copy not dependent on
- *		any external storage.  A pointer to the contained tuple is returned.
+ *		any external storage.
  *
  *		A typical use for this operation is to prepare a computed tuple
  *		for being stored on disk.  The original data may or may not be
  *		virtual, but in any case we need a private copy for heap_insert
- *		to scribble on.
+ *		to scribble on.  XXX is this comment good?
  * --------------------------------
  */
-HeapTuple
+void
 ExecMaterializeSlot(TupleTableSlot *slot)
 {
 	MemoryContext oldContext;
+	HeapTuple	tup;
 
 	/*
 	 * sanity checks
@@ -732,12 +718,8 @@ ExecMaterializeSlot(TupleTableSlot *slot)
 	Assert(slot != NULL);
 	Assert(!slot->tts_isempty);
 
-	/*
-	 * If we have a regular physical tuple, and it's locally palloc'd, we have
-	 * nothing to do.
-	 */
-	if (slot->tts_tuple && slot->tts_shouldFree)
-		return slot->tts_tuple;
+	if (slot->tts_shouldFree)
+		return;
 
 	/*
 	 * Otherwise, copy or build a physical tuple, and store it into the slot.
@@ -747,18 +729,10 @@ ExecMaterializeSlot(TupleTableSlot *slot)
 	 * anyway.
 	 */
 	oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
-	slot->tts_tuple = ExecCopySlotTuple(slot);
-	slot->tts_shouldFree = true;
+	tup = ExecCopySlotTuple(slot);
+	ExecStoreTuple(tup, slot, InvalidBuffer, true);
 	MemoryContextSwitchTo(oldContext);
 
-	/*
-	 * Drop the pin on the referenced buffer, if there is one.
-	 */
-	if (BufferIsValid(slot->tts_buffer))
-		ReleaseBuffer(slot->tts_buffer);
-
-	slot->tts_buffer = InvalidBuffer;
-
 	/*
 	 * Mark extracted state invalid.  This is important because the slot is
 	 * not supposed to depend any more on the previous external data; we
@@ -768,17 +742,15 @@ ExecMaterializeSlot(TupleTableSlot *slot)
 	 * that we have not pfree'd tts_mintuple, if there is one.)
 	 */
 	slot->tts_nvalid = 0;
+}
 
-	/*
-	 * On the same principle of not depending on previous remote storage,
-	 * forget the mintuple if it's not local storage.  (If it is local
-	 * storage, we must not pfree it now, since callers might have already
-	 * fetched datum pointers referencing it.)
-	 */
-	if (!slot->tts_shouldFreeMin)
-		slot->tts_mintuple = NULL;
+StorageTuple
+ExecHeapifySlot(TupleTableSlot *slot)
+{
+	ExecMaterializeSlot(slot);
+	Assert(slot->tts_storage != NULL);
 
-	return slot->tts_tuple;
+	return slot->tts_storageslotam->slot_tuple(slot, false);
 }
 
 /* --------------------------------
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index dc6cfcfa66..690308845c 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -62,7 +62,7 @@ ForeignNext(ForeignScanState *node)
 	 */
 	if (plan->fsSystemCol && !TupIsNull(slot))
 	{
-		HeapTuple	tup = ExecMaterializeSlot(slot);
+		HeapTuple	tup = ExecHeapifySlot(slot);
 
 		tup->t_tableOid = RelationGetRelid(node->ss.ss_currentRelation);
 	}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 79c34a6e6c..f0307ba50e 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -172,7 +172,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
 		 * initialize t_tableOid before evaluating them.
 		 */
 		Assert(!TupIsNull(econtext->ecxt_scantuple));
-		tuple = ExecMaterializeSlot(econtext->ecxt_scantuple);
+		tuple = ExecHeapifySlot(econtext->ecxt_scantuple);
 		tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
 	}
 	econtext->ecxt_outertuple = planSlot;
@@ -272,7 +272,7 @@ ExecInsert(ModifyTableState *mtstate,
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
 	 */
-	tuple = ExecMaterializeSlot(slot);
+	tuple = ExecHeapifySlot(slot);
 
 	/*
 	 * get information on the (current) result relation
@@ -407,7 +407,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW INSERT Triggers */
@@ -420,7 +420,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		newId = InvalidOid;
 	}
@@ -438,7 +438,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* FDW might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		/*
 		 * AFTER ROW Triggers or RETURNING expressions might reference the
@@ -747,7 +747,7 @@ ExecDelete(ModifyTableState *mtstate,
 		 */
 		if (slot->tts_isempty)
 			ExecStoreAllNullTuple(slot);
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
 	}
 	else
@@ -898,7 +898,7 @@ ldelete:;
 		 * Before releasing the target tuple again, make sure rslot has a
 		 * local copy of any pass-by-reference values.
 		 */
-		ExecMaterializeSlot(rslot);
+		ExecHeapifySlot(rslot);
 
 		ExecClearTuple(slot);
 		if (BufferIsValid(delbuffer))
@@ -959,7 +959,7 @@ ExecUpdate(ModifyTableState *mtstate,
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
 	 */
-	tuple = ExecMaterializeSlot(slot);
+	tuple = ExecHeapifySlot(slot);
 
 	/*
 	 * get information on the (current) result relation
@@ -978,7 +978,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW UPDATE Triggers */
@@ -992,7 +992,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
@@ -1008,7 +1008,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* FDW might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		/*
 		 * AFTER ROW Triggers or RETURNING expressions might reference the
@@ -1124,7 +1124,7 @@ lreplace:;
 					{
 						*tupleid = hufd.ctid;
 						slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
-						tuple = ExecMaterializeSlot(slot);
+						tuple = ExecHeapifySlot(slot);
 						goto lreplace;
 					}
 				}
diff --git a/src/backend/executor/tqueue.c b/src/backend/executor/tqueue.c
index 0dcb911c3c..ef681e2ec2 100644
--- a/src/backend/executor/tqueue.c
+++ b/src/backend/executor/tqueue.c
@@ -58,7 +58,7 @@ tqueueReceiveSlot(TupleTableSlot *slot, DestReceiver *self)
 	shm_mq_result result;
 
 	/* Send the tuple itself. */
-	tuple = ExecMaterializeSlot(slot);
+	tuple = ExecHeapifySlot(slot);
 	result = shm_mq_send(tqueue->queue, tuple->t_len, tuple->t_data, false);
 
 	/* Check for failure. */
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index fa5d9bb120..ad0aceb61e 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -729,9 +729,12 @@ apply_handle_update(StringInfo s)
 	 */
 	if (found)
 	{
+		HeapTuple	tuple;
+
 		/* Process and store remote tuple in the slot */
 		oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
-		ExecStoreTuple(localslot->tts_tuple, remoteslot, InvalidBuffer, false);
+		tuple = ExecHeapifySlot(localslot);
+		ExecStoreTuple(tuple, remoteslot, InvalidBuffer, false);
 		slot_modify_cstrings(remoteslot, rel, newtup.values, newtup.changed);
 		MemoryContextSwitchTo(oldctx);
 
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index b0d4c54121..168edb058d 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -20,6 +20,19 @@
 #include "access/transam.h"
 #include "storage/bufpage.h"
 
+/*
+ * Opaque tuple representation for executor's TupleTableSlot tts_storage
+ * (XXX This should probably live in a separate header)
+ */
+typedef struct HeapamTuple
+{
+	HeapTuple	hst_heaptuple;
+	bool		hst_slow;
+	long		hst_off;
+	MinimalTuple hst_mintuple;	/* minimal tuple, or NULL if none */
+	HeapTupleData hst_minhdr;	/* workspace for minimal-tuple-only case */
+}			HeapamTuple;
+
 /*
  * MaxTupleAttributeNumber limits the number of (user) columns in a tuple.
  * The key limit on this value is that the size of the fixed overhead for
@@ -658,7 +671,7 @@ struct MinimalTupleData
 /*
  * GETSTRUCT - given a HeapTuple pointer, return address of the user data
  */
-#define GETSTRUCT(TUP) ((char *) ((TUP)->t_data) + (TUP)->t_data->t_hoff)
+#define GETSTRUCT(TUP) ((char *) (((HeapTuple)(TUP))->t_data) + ((HeapTuple)(TUP))->t_data->t_hoff)
 
 /*
  * Accessor macros to be used with HeapTuple pointers.
diff --git a/src/include/access/storage_common.h b/src/include/access/storage_common.h
index 60fb44b6cd..e38555cd0b 100644
--- a/src/include/access/storage_common.h
+++ b/src/include/access/storage_common.h
@@ -21,9 +21,46 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "access/xlog.h"
+#include "executor/tuptable.h"
 #include "storage/bufpage.h"
 #include "storage/bufmgr.h"
 
+/* A physical tuple coming from a storage AM scan */
+typedef void *StorageTuple;
+
+/*
+ * slot storage routine functions
+ */
+typedef void (*SlotStoreTuple_function) (TupleTableSlot *slot,
+										 StorageTuple tuple,
+										 bool shouldFree,
+										 bool minumumtuple);
+typedef void (*SlotClearTuple_function) (TupleTableSlot *slot);
+typedef Datum (*SlotGetattr_function) (TupleTableSlot *slot,
+									   int attnum, bool *isnull);
+typedef void (*SlotVirtualizeTuple_function) (TupleTableSlot *slot, int16 upto);
+
+typedef HeapTuple (*SlotGetTuple_function) (TupleTableSlot *slot, bool palloc_copy);
+typedef MinimalTuple (*SlotGetMinTuple_function) (TupleTableSlot *slot, bool palloc_copy);
+
+typedef void (*SlotUpdateTableoid_function) (TupleTableSlot *slot, Oid tableoid);
+
+typedef void (*SpeculativeAbort_function) (Relation rel,
+										   TupleTableSlot *slot);
+
+typedef struct StorageSlotAmRoutine
+{
+	/* Operations on TupleTableSlot */
+	SlotStoreTuple_function slot_store_tuple;
+	SlotVirtualizeTuple_function slot_virtualize_tuple;
+	SlotClearTuple_function slot_clear_tuple;
+	SlotGetattr_function slot_getattr;
+	SlotGetTuple_function slot_tuple;
+	SlotGetMinTuple_function slot_min_tuple;
+	SlotUpdateTableoid_function slot_update_tableoid;
+}			StorageSlotAmRoutine;
+
+typedef StorageSlotAmRoutine * (*slot_storageam_hook) (void);
 
 /* A physical tuple coming from a storage AM scan */
 typedef void *StorageTuple;
@@ -45,6 +82,7 @@ extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
 extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
 extern bool HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin);
 extern bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
+extern StorageSlotAmRoutine * heapam_storage_slot_handler(void);
 
 /*
  * SetHintBits()
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
index 56a791d0c6..7df51c4167 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -42,6 +42,8 @@ typedef struct StorageAmRoutine
 	SnapshotSatisfiesUpdate_function snapshot_satisfiesUpdate;	/* HeapTupleSatisfiesUpdate */
 	SnapshotSatisfiesVacuum_function snapshot_satisfiesVacuum;	/* HeapTupleSatisfiesVacuum */
 
+	slot_storageam_hook slot_storageam;
+
 }			StorageAmRoutine;
 
 extern StorageAmRoutine * GetStorageAmRoutine(Oid amhandler);
diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h
index db2a42af5e..e6ff66f14b 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -18,9 +18,25 @@
 #include "access/tupdesc.h"
 #include "storage/buf.h"
 
+/*
+ * Forward declare StorageAmRoutine to avoid including storageamapi.h here
+ */
+struct StorageSlotAmRoutine;
+
+/*
+ * Forward declare StorageTuple to avoid including storageamapi.h here
+ */
+typedef void *StorageTuple;
+
 /*----------
  * The executor stores tuples in a "tuple table" which is a List of
- * independent TupleTableSlots.  There are several cases we need to handle:
+ * independent TupleTableSlots.
+ *
+ * XXX The "html-commented out" text below no longer reflects reality, as
+ * physical tuples are now responsibility of storage AMs.  But we have kept
+ * "minimal tuples".  Adjust this comment!
+ *
+ * <!-- There are several cases we need to handle:
  *		1. physical tuple in a disk buffer page
  *		2. physical tuple constructed in palloc'ed memory
  *		3. "minimal" physical tuple constructed in palloc'ed memory
@@ -56,6 +72,7 @@
  * had the fatal defect of invalidating any pass-by-reference Datums pointing
  * into the existing slot contents.)  Both copies must contain identical data
  * payloads when this is the case.
+ * -->
  *
  * The Datum/isnull arrays of a TupleTableSlot serve double duty.  When the
  * slot contains a virtual tuple, they are the authoritative data.  When the
@@ -82,11 +99,6 @@
  * When tts_shouldFree is true, the physical tuple is "owned" by the slot
  * and should be freed when the slot's reference to the tuple is dropped.
  *
- * If tts_buffer is not InvalidBuffer, then the slot is holding a pin
- * on the indicated buffer page; drop the pin when we release the
- * slot's reference to that buffer.  (tts_shouldFree should always be
- * false in such a case, since presumably tts_tuple is pointing at the
- * buffer page.)
  *
  * tts_nvalid indicates the number of valid columns in the tts_values/isnull
  * arrays.  When the slot is holding a "virtual" tuple this must be equal
@@ -114,24 +126,21 @@ typedef struct TupleTableSlot
 {
 	NodeTag		type;
 	bool		tts_isempty;	/* true = slot is empty */
-	bool		tts_shouldFree; /* should pfree tts_tuple? */
-	bool		tts_shouldFreeMin;	/* should pfree tts_mintuple? */
-	bool		tts_slow;		/* saved state for slot_deform_tuple */
-	HeapTuple	tts_tuple;		/* physical tuple, or NULL if virtual */
+	ItemPointerData tts_tid;	/* XXX describe */
 	TupleDesc	tts_tupleDescriptor;	/* slot's tuple descriptor */
 	MemoryContext tts_mcxt;		/* slot itself is in this context */
-	Buffer		tts_buffer;		/* tuple's buffer, or InvalidBuffer */
+	Oid			tts_tableOid;	/* XXX describe */
+	Oid			tts_tupleOid;	/* XXX describe */
 	int			tts_nvalid;		/* # of valid values in tts_values */
+	uint32		tts_speculativeToken;	/* XXX describe */
+	bool		tts_shouldFree;
+	bool		tts_shouldFreeMin;
 	Datum	   *tts_values;		/* current per-attribute values */
 	bool	   *tts_isnull;		/* current per-attribute isnull flags */
-	MinimalTuple tts_mintuple;	/* minimal tuple, or NULL if none */
-	HeapTupleData tts_minhdr;	/* workspace for minimal-tuple-only case */
-	long		tts_off;		/* saved state for slot_deform_tuple */
+	struct StorageSlotAmRoutine *tts_storageslotam; /* storage AM */
+	void	   *tts_storage;	/* storage AM's opaque space */
 } TupleTableSlot;
 
-#define TTS_HAS_PHYSICAL_TUPLE(slot)  \
-	((slot)->tts_tuple != NULL && (slot)->tts_tuple != &((slot)->tts_minhdr))
-
 /*
  * TupIsNull -- is a TupleTableSlot empty?
  */
@@ -143,9 +152,10 @@ extern TupleTableSlot *MakeTupleTableSlot(void);
 extern TupleTableSlot *ExecAllocTableSlot(List **tupleTable);
 extern void ExecResetTupleTable(List *tupleTable, bool shouldFree);
 extern TupleTableSlot *MakeSingleTupleTableSlot(TupleDesc tupdesc);
+extern bool ExecSlotCompare(TupleTableSlot *slot1, TupleTableSlot *slot2);
 extern void ExecDropSingleTupleTableSlot(TupleTableSlot *slot);
 extern void ExecSetSlotDescriptor(TupleTableSlot *slot, TupleDesc tupdesc);
-extern TupleTableSlot *ExecStoreTuple(HeapTuple tuple,
+extern TupleTableSlot *ExecStoreTuple(void *tuple,
 			   TupleTableSlot *slot,
 			   Buffer buffer,
 			   bool shouldFree);
@@ -155,12 +165,14 @@ extern TupleTableSlot *ExecStoreMinimalTuple(MinimalTuple mtup,
 extern TupleTableSlot *ExecClearTuple(TupleTableSlot *slot);
 extern TupleTableSlot *ExecStoreVirtualTuple(TupleTableSlot *slot);
 extern TupleTableSlot *ExecStoreAllNullTuple(TupleTableSlot *slot);
-extern HeapTuple ExecCopySlotTuple(TupleTableSlot *slot);
+extern StorageTuple ExecCopySlotTuple(TupleTableSlot *slot);
 extern MinimalTuple ExecCopySlotMinimalTuple(TupleTableSlot *slot);
-extern HeapTuple ExecFetchSlotTuple(TupleTableSlot *slot);
+extern void ExecSlotUpdateTupleTableoid(TupleTableSlot *slot, Oid tableoid);
+extern StorageTuple ExecFetchSlotTuple(TupleTableSlot *slot);
 extern MinimalTuple ExecFetchSlotMinimalTuple(TupleTableSlot *slot);
 extern Datum ExecFetchSlotTupleDatum(TupleTableSlot *slot);
-extern HeapTuple ExecMaterializeSlot(TupleTableSlot *slot);
+extern void ExecMaterializeSlot(TupleTableSlot *slot);
+extern StorageTuple ExecHeapifySlot(TupleTableSlot *slot);
 extern TupleTableSlot *ExecCopySlot(TupleTableSlot *dstslot,
 			 TupleTableSlot *srcslot);
 
-- 
2.15.0.windows.1



  [application/octet-stream] 0006-Tuple-Insert-API-is-added-to-Storage-AM.patch (100.8K, ../../CAJrrPGc951F-R4Kfa4W47B5vHKeHsB2Y34zewp=b+AWSkF9RVA@mail.gmail.com/10-0006-Tuple-Insert-API-is-added-to-Storage-AM.patch)
  download | inline diff:
From 7ad4a052339ff64ee459105f53293a4541283897 Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Wed, 27 Dec 2017 12:52:12 +1100
Subject: [PATCH 06/11] Tuple Insert API is added to Storage AM

heap_insert, heap_delete, heap_fetch, heap_update,
heap_get_latest_oid, heap_lock_tuple and heap_multi_insert
functions are added to storage AM. Move the index insertion
logic into storage AM, The Index insert still outside for
the case of multi_insert (Yet to change).

Replaced the usage of HeapTuple with storageTuple in
some places, increased the use of slot.
---
 src/backend/access/common/heaptuple.c       |  24 +++
 src/backend/access/heap/heapam.c            | 136 +++++++++-------
 src/backend/access/heap/heapam_storage.c    | 230 ++++++++++++++++++++++++++
 src/backend/access/heap/rewriteheap.c       |   5 +-
 src/backend/access/heap/tuptoaster.c        |   9 +-
 src/backend/access/storage/storage_common.c |  16 +-
 src/backend/access/storage/storageam.c      | 117 +++++++++++++
 src/backend/commands/copy.c                 |  39 ++---
 src/backend/commands/createas.c             |  24 +--
 src/backend/commands/matview.c              |  22 ++-
 src/backend/commands/tablecmds.c            |   6 +-
 src/backend/commands/trigger.c              |  50 +++---
 src/backend/executor/execIndexing.c         |   2 +-
 src/backend/executor/execMain.c             | 131 ++++++++-------
 src/backend/executor/execReplication.c      |  72 ++++----
 src/backend/executor/nodeLockRows.c         |  47 +++---
 src/backend/executor/nodeModifyTable.c      | 244 +++++++++++++---------------
 src/backend/executor/nodeTidscan.c          |  23 ++-
 src/backend/utils/adt/tid.c                 |   5 +-
 src/include/access/heapam.h                 |  14 +-
 src/include/access/htup_details.h           |   1 +
 src/include/access/storage_common.h         |   5 -
 src/include/access/storageam.h              |  61 +++++++
 src/include/access/storageamapi.h           |  90 ++++++++++
 src/include/commands/trigger.h              |   2 +-
 src/include/executor/executor.h             |  15 +-
 src/include/executor/tuptable.h             |   1 +
 src/include/nodes/execnodes.h               |   8 +-
 28 files changed, 960 insertions(+), 439 deletions(-)
 create mode 100644 src/include/access/storageam.h

diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index c29935b91a..a003df1c28 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -685,6 +685,30 @@ heap_copy_tuple_as_datum(HeapTuple tuple, TupleDesc tupleDesc)
 	return PointerGetDatum(td);
 }
 
+/*
+ * heap_form_tuple_by_datum
+ *		construct a tuple from the given dataum
+ *
+ * The result is allocated in the current memory context.
+ */
+HeapTuple
+heap_form_tuple_by_datum(Datum data, Oid tableoid)
+{
+	HeapTuple	newTuple;
+	HeapTupleHeader td;
+
+	td = DatumGetHeapTupleHeader(data);
+
+	newTuple = (HeapTuple) palloc(HEAPTUPLESIZE + HeapTupleHeaderGetDatumLength(td));
+	newTuple->t_len = HeapTupleHeaderGetDatumLength(td);
+	newTuple->t_self = td->t_ctid;
+	newTuple->t_tableOid = tableoid;
+	newTuple->t_data = (HeapTupleHeader) ((char *) newTuple + HEAPTUPLESIZE);
+	memcpy((char *) newTuple->t_data, (char *) td, newTuple->t_len);
+
+	return newTuple;
+}
+
 /*
  * heap_form_tuple
  *		construct a tuple from the given values[] and isnull[] arrays,
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 404c825089..8da5720e57 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -1893,13 +1893,13 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
  */
 bool
 heap_fetch(Relation relation,
+		   ItemPointer tid,
 		   Snapshot snapshot,
 		   HeapTuple tuple,
 		   Buffer *userbuf,
 		   bool keep_buf,
 		   Relation stats_relation)
 {
-	ItemPointer tid = &(tuple->t_self);
 	ItemId		lp;
 	Buffer		buffer;
 	Page		page;
@@ -1933,7 +1933,6 @@ heap_fetch(Relation relation,
 			ReleaseBuffer(buffer);
 			*userbuf = InvalidBuffer;
 		}
-		tuple->t_data = NULL;
 		return false;
 	}
 
@@ -1955,13 +1954,13 @@ heap_fetch(Relation relation,
 			ReleaseBuffer(buffer);
 			*userbuf = InvalidBuffer;
 		}
-		tuple->t_data = NULL;
 		return false;
 	}
 
 	/*
-	 * fill in *tuple fields
+	 * fill in tuple fields and place it in stuple
 	 */
+	ItemPointerCopy(tid, &(tuple->t_self));
 	tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
 	tuple->t_len = ItemIdGetLength(lp);
 	tuple->t_tableOid = RelationGetRelid(relation);
@@ -2312,6 +2311,18 @@ heap_get_latest_tid(Relation relation,
 	}							/* end of loop */
 }
 
+/*
+ * HeapTupleSetHintBits --- exported version of SetHintBits()
+ *
+ * This must be separate because of C99's brain-dead notions about how to
+ * implement inline functions.
+ */
+static void
+HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
+					 uint16 infomask, TransactionId xid)
+{
+	SetHintBits(tuple, buffer, infomask, xid);
+}
 
 /*
  * UpdateXmaxHintBits - update tuple hint bits after xmax transaction ends
@@ -4576,13 +4587,12 @@ get_mxact_status_for_lock(LockTupleMode mode, bool is_update)
  * See README.tuplock for a thorough explanation of this mechanism.
  */
 HTSU_Result
-heap_lock_tuple(Relation relation, HeapTuple tuple,
+heap_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * stuple,
 				CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 				bool follow_updates,
 				Buffer *buffer, HeapUpdateFailureData *hufd)
 {
 	HTSU_Result result;
-	ItemPointer tid = &(tuple->t_self);
 	ItemId		lp;
 	Page		page;
 	Buffer		vmbuffer = InvalidBuffer;
@@ -4595,6 +4605,9 @@ heap_lock_tuple(Relation relation, HeapTuple tuple,
 	bool		first_time = true;
 	bool		have_tuple_lock = false;
 	bool		cleared_all_frozen = false;
+	HeapTupleData tuple;
+
+	Assert(stuple != NULL);
 
 	*buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
 	block = ItemPointerGetBlockNumber(tid);
@@ -4614,12 +4627,13 @@ heap_lock_tuple(Relation relation, HeapTuple tuple,
 	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid));
 	Assert(ItemIdIsNormal(lp));
 
-	tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	tuple->t_len = ItemIdGetLength(lp);
-	tuple->t_tableOid = RelationGetRelid(relation);
+	tuple.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+	tuple.t_len = ItemIdGetLength(lp);
+	tuple.t_tableOid = RelationGetRelid(relation);
+	ItemPointerCopy(tid, &tuple.t_self);
 
 l3:
-	result = relation->rd_stamroutine->snapshot_satisfiesUpdate(tuple, cid, *buffer);
+	result = HeapTupleSatisfiesUpdate(&tuple, cid, *buffer);
 
 	if (result == HeapTupleInvisible)
 	{
@@ -4641,10 +4655,10 @@ l3:
 		ItemPointerData t_ctid;
 
 		/* must copy state data before unlocking buffer */
-		xwait = HeapTupleHeaderGetRawXmax(tuple->t_data);
-		infomask = tuple->t_data->t_infomask;
-		infomask2 = tuple->t_data->t_infomask2;
-		ItemPointerCopy(&tuple->t_data->t_ctid, &t_ctid);
+		xwait = HeapTupleHeaderGetRawXmax(tuple.t_data);
+		infomask = tuple.t_data->t_infomask;
+		infomask2 = tuple.t_data->t_infomask2;
+		ItemPointerCopy(&tuple.t_data->t_ctid, &t_ctid);
 
 		LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
 
@@ -4776,7 +4790,7 @@ l3:
 				{
 					HTSU_Result res;
 
-					res = heap_lock_updated_tuple(relation, tuple, &t_ctid,
+					res = heap_lock_updated_tuple(relation, &tuple, &t_ctid,
 												  GetCurrentTransactionId(),
 												  mode);
 					if (res != HeapTupleMayBeUpdated)
@@ -4797,8 +4811,8 @@ l3:
 				 * now need to follow the update chain to lock the new
 				 * versions.
 				 */
-				if (!HeapTupleHeaderIsOnlyLocked(tuple->t_data) &&
-					((tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED) ||
+				if (!HeapTupleHeaderIsOnlyLocked(tuple.t_data) &&
+					((tuple.t_data->t_infomask2 & HEAP_KEYS_UPDATED) ||
 					 !updated))
 					goto l3;
 
@@ -4829,8 +4843,8 @@ l3:
 				 * Make sure it's still an appropriate lock, else start over.
 				 * See above about allowing xmax to change.
 				 */
-				if (!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask) ||
-					HEAP_XMAX_IS_EXCL_LOCKED(tuple->t_data->t_infomask))
+				if (!HEAP_XMAX_IS_LOCKED_ONLY(tuple.t_data->t_infomask) ||
+					HEAP_XMAX_IS_EXCL_LOCKED(tuple.t_data->t_infomask))
 					goto l3;
 				require_sleep = false;
 			}
@@ -4852,8 +4866,8 @@ l3:
 					 * meantime, start over.
 					 */
 					LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-					if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
-						!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple->t_data),
+					if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
+						!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple.t_data),
 											 xwait))
 						goto l3;
 
@@ -4866,9 +4880,9 @@ l3:
 				LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
 
 				/* if the xmax changed in the meantime, start over */
-				if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
+				if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
 					!TransactionIdEquals(
-										 HeapTupleHeaderGetRawXmax(tuple->t_data),
+										 HeapTupleHeaderGetRawXmax(tuple.t_data),
 										 xwait))
 					goto l3;
 				/* otherwise, we're good */
@@ -4893,11 +4907,11 @@ l3:
 		{
 			/* ... but if the xmax changed in the meantime, start over */
 			LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-			if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
-				!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple->t_data),
+			if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
+				!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple.t_data),
 									 xwait))
 				goto l3;
-			Assert(HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask));
+			Assert(HEAP_XMAX_IS_LOCKED_ONLY(tuple.t_data->t_infomask));
 			require_sleep = false;
 		}
 
@@ -4954,7 +4968,7 @@ l3:
 				{
 					case LockWaitBlock:
 						MultiXactIdWait((MultiXactId) xwait, status, infomask,
-										relation, &tuple->t_self, XLTW_Lock, NULL);
+										relation, &tuple.t_self, XLTW_Lock, NULL);
 						break;
 					case LockWaitSkip:
 						if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
@@ -4995,7 +5009,7 @@ l3:
 				switch (wait_policy)
 				{
 					case LockWaitBlock:
-						XactLockTableWait(xwait, relation, &tuple->t_self,
+						XactLockTableWait(xwait, relation, &tuple.t_self,
 										  XLTW_Lock);
 						break;
 					case LockWaitSkip:
@@ -5022,7 +5036,7 @@ l3:
 			{
 				HTSU_Result res;
 
-				res = heap_lock_updated_tuple(relation, tuple, &t_ctid,
+				res = heap_lock_updated_tuple(relation, &tuple, &t_ctid,
 											  GetCurrentTransactionId(),
 											  mode);
 				if (res != HeapTupleMayBeUpdated)
@@ -5041,8 +5055,8 @@ l3:
 			 * other xact could update this tuple before we get to this point.
 			 * Check for xmax change, and start over if so.
 			 */
-			if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
-				!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple->t_data),
+			if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
+				!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple.t_data),
 									 xwait))
 				goto l3;
 
@@ -5056,7 +5070,7 @@ l3:
 				 * don't check for this in the multixact case, because some
 				 * locker transactions might still be running.
 				 */
-				UpdateXmaxHintBits(tuple->t_data, *buffer, xwait);
+				UpdateXmaxHintBits(tuple.t_data, *buffer, xwait);
 			}
 		}
 
@@ -5068,9 +5082,9 @@ l3:
 		 * at all for whatever reason.
 		 */
 		if (!require_sleep ||
-			(tuple->t_data->t_infomask & HEAP_XMAX_INVALID) ||
-			HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask) ||
-			HeapTupleHeaderIsOnlyLocked(tuple->t_data))
+			(tuple.t_data->t_infomask & HEAP_XMAX_INVALID) ||
+			HEAP_XMAX_IS_LOCKED_ONLY(tuple.t_data->t_infomask) ||
+			HeapTupleHeaderIsOnlyLocked(tuple.t_data))
 			result = HeapTupleMayBeUpdated;
 		else
 			result = HeapTupleUpdated;
@@ -5081,11 +5095,11 @@ failed:
 	{
 		Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated ||
 			   result == HeapTupleWouldBlock);
-		Assert(!(tuple->t_data->t_infomask & HEAP_XMAX_INVALID));
-		hufd->ctid = tuple->t_data->t_ctid;
-		hufd->xmax = HeapTupleHeaderGetUpdateXid(tuple->t_data);
+		Assert(!(tuple.t_data->t_infomask & HEAP_XMAX_INVALID));
+		hufd->ctid = tuple.t_data->t_ctid;
+		hufd->xmax = HeapTupleHeaderGetUpdateXid(tuple.t_data);
 		if (result == HeapTupleSelfUpdated)
-			hufd->cmax = HeapTupleHeaderGetCmax(tuple->t_data);
+			hufd->cmax = HeapTupleHeaderGetCmax(tuple.t_data);
 		else
 			hufd->cmax = InvalidCommandId;
 		goto out_locked;
@@ -5108,8 +5122,8 @@ failed:
 		goto l3;
 	}
 
-	xmax = HeapTupleHeaderGetRawXmax(tuple->t_data);
-	old_infomask = tuple->t_data->t_infomask;
+	xmax = HeapTupleHeaderGetRawXmax(tuple.t_data);
+	old_infomask = tuple.t_data->t_infomask;
 
 	/*
 	 * If this is the first possibly-multixact-able operation in the current
@@ -5126,7 +5140,7 @@ failed:
 	 * not modify the tuple just yet, because that would leave it in the wrong
 	 * state if multixact.c elogs.
 	 */
-	compute_new_xmax_infomask(xmax, old_infomask, tuple->t_data->t_infomask2,
+	compute_new_xmax_infomask(xmax, old_infomask, tuple.t_data->t_infomask2,
 							  GetCurrentTransactionId(), mode, false,
 							  &xid, &new_infomask, &new_infomask2);
 
@@ -5142,13 +5156,13 @@ failed:
 	 * Also reset the HOT UPDATE bit, but only if there's no update; otherwise
 	 * we would break the HOT chain.
 	 */
-	tuple->t_data->t_infomask &= ~HEAP_XMAX_BITS;
-	tuple->t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
-	tuple->t_data->t_infomask |= new_infomask;
-	tuple->t_data->t_infomask2 |= new_infomask2;
+	tuple.t_data->t_infomask &= ~HEAP_XMAX_BITS;
+	tuple.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+	tuple.t_data->t_infomask |= new_infomask;
+	tuple.t_data->t_infomask2 |= new_infomask2;
 	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
-		HeapTupleHeaderClearHotUpdated(tuple->t_data);
-	HeapTupleHeaderSetXmax(tuple->t_data, xid);
+		HeapTupleHeaderClearHotUpdated(tuple.t_data);
+	HeapTupleHeaderSetXmax(tuple.t_data, xid);
 
 	/*
 	 * Make sure there is no forward chain link in t_ctid.  Note that in the
@@ -5158,7 +5172,7 @@ failed:
 	 * the tuple as well.
 	 */
 	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
-		tuple->t_data->t_ctid = *tid;
+		tuple.t_data->t_ctid = *tid;
 
 	/* Clear only the all-frozen bit on visibility map if needed */
 	if (PageIsAllVisible(page) &&
@@ -5189,10 +5203,10 @@ failed:
 		XLogBeginInsert();
 		XLogRegisterBuffer(0, *buffer, REGBUF_STANDARD);
 
-		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self);
+		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple.t_self);
 		xlrec.locking_xid = xid;
 		xlrec.infobits_set = compute_infobits(new_infomask,
-											  tuple->t_data->t_infomask2);
+											  tuple.t_data->t_infomask2);
 		xlrec.flags = cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
 		XLogRegisterData((char *) &xlrec, SizeOfHeapLock);
 
@@ -5226,6 +5240,7 @@ out_unlocked:
 	if (have_tuple_lock)
 		UnlockTupleTuplock(relation, tid, mode);
 
+	*stuple = heap_copytuple(&tuple);
 	return result;
 }
 
@@ -5683,9 +5698,8 @@ heap_lock_updated_tuple_rec(Relation rel, ItemPointer tid, TransactionId xid,
 		new_infomask = 0;
 		new_xmax = InvalidTransactionId;
 		block = ItemPointerGetBlockNumber(&tupid);
-		ItemPointerCopy(&tupid, &(mytup.t_self));
 
-		if (!heap_fetch(rel, SnapshotAny, &mytup, &buf, false, NULL))
+		if (!heap_fetch(rel, &tupid, SnapshotAny, &mytup, &buf, false, NULL))
 		{
 			/*
 			 * if we fail to find the updated version of the tuple, it's
@@ -6032,14 +6046,18 @@ heap_lock_updated_tuple(Relation rel, HeapTuple tuple, ItemPointer ctid,
  * An explicit confirmation WAL record also makes logical decoding simpler.
  */
 void
-heap_finish_speculative(Relation relation, HeapTuple tuple)
+heap_finish_speculative(Relation relation, TupleTableSlot *slot)
 {
+	HeapamTuple *stuple = (HeapamTuple *) slot->tts_storage;
+	HeapTuple	tuple = stuple->hst_heaptuple;
 	Buffer		buffer;
 	Page		page;
 	OffsetNumber offnum;
 	ItemId		lp = NULL;
 	HeapTupleHeader htup;
 
+	Assert(slot->tts_speculativeToken != 0);
+
 	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&(tuple->t_self)));
 	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 	page = (Page) BufferGetPage(buffer);
@@ -6094,6 +6112,7 @@ heap_finish_speculative(Relation relation, HeapTuple tuple)
 	END_CRIT_SECTION();
 
 	UnlockReleaseBuffer(buffer);
+	slot->tts_speculativeToken = 0;
 }
 
 /*
@@ -6123,8 +6142,10 @@ heap_finish_speculative(Relation relation, HeapTuple tuple)
  * confirmation records.
  */
 void
-heap_abort_speculative(Relation relation, HeapTuple tuple)
+heap_abort_speculative(Relation relation, TupleTableSlot *slot)
 {
+	HeapamTuple *stuple = (HeapamTuple *) slot->tts_storage;
+	HeapTuple	tuple = stuple->hst_heaptuple;
 	TransactionId xid = GetCurrentTransactionId();
 	ItemPointer tid = &(tuple->t_self);
 	ItemId		lp;
@@ -6133,6 +6154,10 @@ heap_abort_speculative(Relation relation, HeapTuple tuple)
 	BlockNumber block;
 	Buffer		buffer;
 
+	/*
+	 * Assert(slot->tts_speculativeToken != 0); This needs some update in
+	 * toast
+	 */
 	Assert(ItemPointerIsValid(tid));
 
 	block = ItemPointerGetBlockNumber(tid);
@@ -6246,6 +6271,7 @@ heap_abort_speculative(Relation relation, HeapTuple tuple)
 
 	/* count deletion, as we counted the insertion too */
 	pgstat_count_heap_delete(relation);
+	slot->tts_speculativeToken = 0;
 }
 
 /*
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index a953a690b3..d85ebed274 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -23,8 +23,226 @@
 #include "access/heapam.h"
 #include "access/storageamapi.h"
 #include "utils/builtins.h"
+#include "utils/rel.h"
 
 
+/* ----------------------------------------------------------------
+ *				storage AM support routines for heapam
+ * ----------------------------------------------------------------
+ */
+
+static bool
+heapam_fetch(Relation relation,
+			 ItemPointer tid,
+			 Snapshot snapshot,
+			 StorageTuple * stuple,
+			 Buffer *userbuf,
+			 bool keep_buf,
+			 Relation stats_relation)
+{
+	HeapTupleData tuple;
+
+	*stuple = NULL;
+	if (heap_fetch(relation, tid, snapshot, &tuple, userbuf, keep_buf, stats_relation))
+	{
+		*stuple = heap_copytuple(&tuple);
+		return true;
+	}
+
+	return false;
+}
+
+/*
+ * Insert a heap tuple from a slot, which may contain an OID and speculative
+ * insertion token.
+ */
+static Oid
+heapam_heap_insert(Relation relation, TupleTableSlot *slot, CommandId cid,
+				   int options, BulkInsertState bistate, InsertIndexTuples IndexFunc,
+				   EState *estate, List *arbiterIndexes, List **recheckIndexes)
+{
+	Oid			oid;
+	HeapTuple	tuple = NULL;
+
+	if (slot->tts_storage)
+	{
+		HeapamTuple *htuple = slot->tts_storage;
+
+		tuple = htuple->hst_heaptuple;
+
+		if (relation->rd_rel->relhasoids)
+			HeapTupleSetOid(tuple, InvalidOid);
+	}
+	else
+	{
+		/*
+		 * Obtain the physical tuple to insert, building from the slot values.
+		 * XXX: maybe the slot already contains a physical tuple in the right
+		 * format?  In fact, if the slot isn't fully deformed, this is
+		 * completely bogus ...
+		 */
+		tuple = heap_form_tuple(slot->tts_tupleDescriptor,
+								slot->tts_values,
+								slot->tts_isnull);
+	}
+
+	/* Set the OID, if the slot has one */
+	if (slot->tts_tupleOid != InvalidOid)
+		HeapTupleHeaderSetOid(tuple->t_data, slot->tts_tupleOid);
+
+	/* Update the tuple with table oid */
+	if (slot->tts_tableOid != InvalidOid)
+		tuple->t_tableOid = slot->tts_tableOid;
+
+	/* Set the speculative insertion token, if the slot has one */
+	if ((options & HEAP_INSERT_SPECULATIVE) && slot->tts_speculativeToken)
+		HeapTupleHeaderSetSpeculativeToken(tuple->t_data, slot->tts_speculativeToken);
+
+	/* Perform the insertion, and copy the resulting ItemPointer */
+	oid = heap_insert(relation, tuple, cid, options, bistate);
+	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
+
+	if (slot->tts_storage == NULL)
+		ExecStoreTuple(tuple, slot, InvalidBuffer, true);
+
+	if ((estate != NULL) && (estate->es_result_relation_info->ri_NumIndices > 0))
+	{
+		Assert(IndexFunc != NULL);
+
+		if (options & HEAP_INSERT_SPECULATIVE)
+		{
+			bool		specConflict = false;
+
+			*recheckIndexes = (IndexFunc) (slot, estate, true,
+										   &specConflict,
+										   arbiterIndexes);
+
+			/* adjust the tuple's state accordingly */
+			if (!specConflict)
+				heap_finish_speculative(relation, slot);
+			else
+			{
+				heap_abort_speculative(relation, slot);
+				slot->tts_specConflict = true;
+			}
+		}
+		else
+		{
+			*recheckIndexes = (IndexFunc) (slot, estate, false,
+										   NULL, arbiterIndexes);
+		}
+	}
+
+	return oid;
+}
+
+static HTSU_Result
+heapam_heap_delete(Relation relation, ItemPointer tid, CommandId cid,
+				   Snapshot crosscheck, bool wait,
+				   HeapUpdateFailureData *hufd)
+{
+	return heap_delete(relation, tid, cid, crosscheck, wait, hufd);
+}
+
+
+
+static HTSU_Result
+heapam_heap_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
+				   EState *estate, CommandId cid, Snapshot crosscheck,
+				   bool wait, HeapUpdateFailureData *hufd, LockTupleMode *lockmode,
+				   InsertIndexTuples IndexFunc, List **recheckIndexes)
+{
+	HeapTuple	tuple;
+	HTSU_Result result;
+
+	if (slot->tts_storage)
+	{
+		HeapamTuple *htuple = slot->tts_storage;
+
+		tuple = htuple->hst_heaptuple;
+	}
+	else
+	{
+		tuple = heap_form_tuple(slot->tts_tupleDescriptor,
+								slot->tts_values,
+								slot->tts_isnull);
+	}
+
+	/* Set the OID, if the slot has one */
+	if (slot->tts_tupleOid != InvalidOid)
+		HeapTupleHeaderSetOid(tuple->t_data, slot->tts_tupleOid);
+
+	/* Update the tuple with table oid */
+	if (slot->tts_tableOid != InvalidOid)
+		tuple->t_tableOid = slot->tts_tableOid;
+
+	result = heap_update(relation, otid, tuple, cid, crosscheck, wait,
+						 hufd, lockmode);
+	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
+
+	if (slot->tts_storage == NULL)
+		ExecStoreTuple(tuple, slot, InvalidBuffer, true);
+
+	/*
+	 * Note: instead of having to update the old index tuples associated with
+	 * the heap tuple, all we do is form and insert new index tuples. This is
+	 * because UPDATEs are actually DELETEs and INSERTs, and index tuple
+	 * deletion is done later by VACUUM (see notes in ExecDelete). All we do
+	 * here is insert new index tuples.  -cim 9/27/89
+	 */
+
+	/*
+	 * insert index entries for tuple
+	 *
+	 * Note: heap_update returns the tid (location) of the new tuple in the
+	 * t_self field.
+	 *
+	 * If it's a HOT update, we mustn't insert new index entries.
+	 */
+	if ((result == HeapTupleMayBeUpdated) &&
+		((estate != NULL) && (estate->es_result_relation_info->ri_NumIndices > 0)) &&
+		(!HeapTupleIsHeapOnly(tuple)))
+		*recheckIndexes = (IndexFunc) (slot, estate, false, NULL, NIL);
+
+	return result;
+}
+
+static tuple_data
+heapam_get_tuple_data(StorageTuple tuple, tuple_data_flags flags)
+{
+	tuple_data	result;
+
+	switch (flags)
+	{
+		case XMIN:
+			result.xid = HeapTupleHeaderGetXmin(((HeapTuple) tuple)->t_data);
+			break;
+		case UPDATED_XID:
+			result.xid = HeapTupleHeaderGetUpdateXid(((HeapTuple) tuple)->t_data);
+			break;
+		case CMIN:
+			result.cid = HeapTupleHeaderGetCmin(((HeapTuple) tuple)->t_data);
+			break;
+		case TID:
+			result.tid = ((HeapTuple) tuple)->t_self;
+			break;
+		case CTID:
+			result.tid = ((HeapTuple) tuple)->t_data->t_ctid;
+			break;
+		default:
+			Assert(0);
+			break;
+	}
+
+	return result;
+}
+
+static StorageTuple
+heapam_form_tuple_by_datum(Datum data, Oid tableoid)
+{
+	return heap_form_tuple_by_datum(data, tableoid);
+}
+
 Datum
 heapam_storage_handler(PG_FUNCTION_ARGS)
 {
@@ -37,5 +255,17 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 
 	amroutine->slot_storageam = heapam_storage_slot_handler;
 
+	amroutine->tuple_fetch = heapam_fetch;
+	amroutine->tuple_insert = heapam_heap_insert;
+	amroutine->tuple_delete = heapam_heap_delete;
+	amroutine->tuple_update = heapam_heap_update;
+	amroutine->tuple_lock = heap_lock_tuple;
+	amroutine->multi_insert = heap_multi_insert;
+
+	amroutine->get_tuple_data = heapam_get_tuple_data;
+	amroutine->tuple_from_datum = heapam_form_tuple_by_datum;
+	amroutine->tuple_get_latest_tid = heap_get_latest_tid;
+	amroutine->relation_sync = heap_sync;
+
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 7d163c9137..05e82a36e0 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -110,6 +110,7 @@
 #include "access/heapam.h"
 #include "access/heapam_xlog.h"
 #include "access/rewriteheap.h"
+#include "access/storageam.h"
 #include "access/transam.h"
 #include "access/tuptoaster.h"
 #include "access/xact.h"
@@ -126,13 +127,13 @@
 
 #include "storage/bufmgr.h"
 #include "storage/fd.h"
+#include "storage/procarray.h"
 #include "storage/smgr.h"
 
 #include "utils/memutils.h"
 #include "utils/rel.h"
 #include "utils/tqual.h"
 
-#include "storage/procarray.h"
 
 /*
  * State associated with a rewrite operation. This is opaque to the user
@@ -357,7 +358,7 @@ end_heap_rewrite(RewriteState state)
 	 * wrote before the checkpoint.
 	 */
 	if (RelationNeedsWAL(state->rs_new_rel))
-		heap_sync(state->rs_new_rel);
+		storage_sync(state->rs_new_rel);
 
 	logical_end_heap_rewrite(state);
 
diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c
index c74945a52a..747da720b0 100644
--- a/src/backend/access/heap/tuptoaster.c
+++ b/src/backend/access/heap/tuptoaster.c
@@ -32,6 +32,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/storageam.h"
 #include "access/tuptoaster.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
@@ -1777,7 +1778,13 @@ toast_delete_datum(Relation rel, Datum value, bool is_speculative)
 		 * Have a chunk, delete it
 		 */
 		if (is_speculative)
-			heap_abort_speculative(toastrel, toasttup);
+		{
+			TupleTableSlot *slot = MakeSingleTupleTableSlot(RelationGetDescr(toastrel));
+
+			ExecStoreTuple(toasttup, slot, InvalidBuffer, false);
+			heap_abort_speculative(toastrel, slot);
+			ExecDropSingleTupleTableSlot(slot);
+		}
 		else
 			simple_heap_delete(toastrel, &toasttup->t_self);
 	}
diff --git a/src/backend/access/storage/storage_common.c b/src/backend/access/storage/storage_common.c
index b65153bb38..30976f6286 100644
--- a/src/backend/access/storage/storage_common.c
+++ b/src/backend/access/storage/storage_common.c
@@ -29,20 +29,6 @@
 SnapshotData SnapshotSelfData = {SELF_VISIBILITY};
 SnapshotData SnapshotAnyData = {ANY_VISIBILITY};
 
-/*
- * HeapTupleSetHintBits --- exported version of SetHintBits()
- *
- * This must be separate because of C99's brain-dead notions about how to
- * implement inline functions.
- */
-void
-HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
-					 uint16 infomask, TransactionId xid)
-{
-	SetHintBits(tuple, buffer, infomask, xid);
-}
-
-
 /*
  * Is the tuple really only locked?  That is, is it not updated?
  *
@@ -352,6 +338,8 @@ heapam_slot_store_tuple(TupleTableSlot *slot, StorageTuple tuple, bool shouldFre
 	MemoryContextSwitchTo(oldcontext);
 
 	slot->tts_tid = ((HeapTuple) tuple)->t_self;
+	if (slot->tts_tupleDescriptor->tdhasoid)
+		slot->tts_tupleOid = HeapTupleGetOid((HeapTuple) tuple);
 	slot->tts_storage = stuple;
 }
 
diff --git a/src/backend/access/storage/storageam.c b/src/backend/access/storage/storageam.c
index 8541c75782..9b2215effa 100644
--- a/src/backend/access/storage/storageam.c
+++ b/src/backend/access/storage/storageam.c
@@ -13,3 +13,120 @@
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
+
+#include "access/storageam.h"
+#include "access/storageamapi.h"
+#include "utils/rel.h"
+
+/*
+ *	storage_fetch		- retrieve tuple with given tid
+ */
+bool
+storage_fetch(Relation relation,
+			  ItemPointer tid,
+			  Snapshot snapshot,
+			  StorageTuple * stuple,
+			  Buffer *userbuf,
+			  bool keep_buf,
+			  Relation stats_relation)
+{
+	return relation->rd_stamroutine->tuple_fetch(relation, tid, snapshot, stuple,
+												 userbuf, keep_buf, stats_relation);
+}
+
+
+/*
+ *	storage_lock_tuple - lock a tuple in shared or exclusive mode
+ */
+HTSU_Result
+storage_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * stuple,
+				   CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
+				   bool follow_updates, Buffer *buffer, HeapUpdateFailureData *hufd)
+{
+	return relation->rd_stamroutine->tuple_lock(relation, tid, stuple,
+												cid, mode, wait_policy,
+												follow_updates, buffer, hufd);
+}
+
+/*
+ * Insert a tuple from a slot into storage AM routine
+ */
+Oid
+storage_insert(Relation relation, TupleTableSlot *slot, CommandId cid,
+			   int options, BulkInsertState bistate, InsertIndexTuples IndexFunc,
+			   EState *estate, List *arbiterIndexes, List **recheckIndexes)
+{
+	return relation->rd_stamroutine->tuple_insert(relation, slot, cid, options,
+												  bistate, IndexFunc, estate,
+												  arbiterIndexes, recheckIndexes);
+}
+
+/*
+ * Delete a tuple from tid using storage AM routine
+ */
+HTSU_Result
+storage_delete(Relation relation, ItemPointer tid, CommandId cid,
+			   Snapshot crosscheck, bool wait,
+			   HeapUpdateFailureData *hufd)
+{
+	return relation->rd_stamroutine->tuple_delete(relation, tid, cid,
+												  crosscheck, wait, hufd);
+}
+
+/*
+ * update a tuple from tid using storage AM routine
+ */
+HTSU_Result
+storage_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
+			   EState *estate, CommandId cid, Snapshot crosscheck, bool wait,
+			   HeapUpdateFailureData *hufd, LockTupleMode *lockmode,
+			   InsertIndexTuples IndexFunc, List **recheckIndexes)
+{
+	return relation->rd_stamroutine->tuple_update(relation, otid, slot, estate,
+												  cid, crosscheck, wait, hufd,
+												  lockmode, IndexFunc, recheckIndexes);
+}
+
+
+/*
+ *	storage_multi_insert	- insert multiple tuple into a storage
+ */
+void
+storage_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
+					 CommandId cid, int options, BulkInsertState bistate)
+{
+	relation->rd_stamroutine->multi_insert(relation, tuples, ntuples,
+										   cid, options, bistate);
+}
+
+tuple_data
+storage_tuple_get_data(Relation relation, StorageTuple tuple, tuple_data_flags flags)
+{
+	return relation->rd_stamroutine->get_tuple_data(tuple, flags);
+}
+
+StorageTuple
+storage_tuple_by_datum(Relation relation, Datum data, Oid tableoid)
+{
+	if (relation)
+		return relation->rd_stamroutine->tuple_from_datum(data, tableoid);
+	else
+		return heap_form_tuple_by_datum(data, tableoid);
+}
+
+void
+storage_get_latest_tid(Relation relation,
+					   Snapshot snapshot,
+					   ItemPointer tid)
+{
+	relation->rd_stamroutine->tuple_get_latest_tid(relation, snapshot, tid);
+}
+
+/*
+ *	storage_sync		- sync a heap, for use when no WAL has been written
+ */
+void
+storage_sync(Relation rel)
+{
+	rel->rd_stamroutine->relation_sync(rel);
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 0a0eecf509..62d1ac98d5 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -20,6 +20,7 @@
 
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "access/xlog.h"
@@ -2719,8 +2720,6 @@ CopyFrom(CopyState cstate)
 
 			if (slot == NULL)	/* "do nothing" */
 				skip_tuple = true;
-			else				/* trigger might have changed tuple */
-				tuple = ExecHeapifySlot(slot);
 		}
 
 		if (!skip_tuple)
@@ -2783,19 +2782,11 @@ CopyFrom(CopyState cstate)
 					List	   *recheckIndexes = NIL;
 
 					/* OK, store the tuple and create index entries for it */
-					heap_insert(resultRelInfo->ri_RelationDesc, tuple, mycid,
-								hi_options, bistate);
-
-					if (resultRelInfo->ri_NumIndices > 0)
-						recheckIndexes = ExecInsertIndexTuples(slot,
-															   &(tuple->t_self),
-															   estate,
-															   false,
-															   NULL,
-															   NIL);
+					storage_insert(resultRelInfo->ri_RelationDesc, slot, mycid, hi_options,
+								   bistate, ExecInsertIndexTuples, estate, NIL, &recheckIndexes);
 
 					/* AFTER ROW INSERT Triggers */
-					ExecARInsertTriggers(estate, resultRelInfo, tuple,
+					ExecARInsertTriggers(estate, resultRelInfo, slot,
 										 recheckIndexes, cstate->transition_capture);
 
 					list_free(recheckIndexes);
@@ -2891,7 +2882,7 @@ CopyFrom(CopyState cstate)
 	 * indexes since those use WAL anyway)
 	 */
 	if (hi_options & HEAP_INSERT_SKIP_WAL)
-		heap_sync(cstate->rel);
+		storage_sync(cstate->rel);
 
 	return processed;
 }
@@ -2924,12 +2915,12 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
 	 * before calling it.
 	 */
 	oldcontext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
-	heap_multi_insert(cstate->rel,
-					  bufferedTuples,
-					  nBufferedTuples,
-					  mycid,
-					  hi_options,
-					  bistate);
+	storage_multi_insert(cstate->rel,
+						 bufferedTuples,
+						 nBufferedTuples,
+						 mycid,
+						 hi_options,
+						 bistate);
 	MemoryContextSwitchTo(oldcontext);
 
 	/*
@@ -2945,10 +2936,9 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
 			cstate->cur_lineno = firstBufferedLineNo + i;
 			ExecStoreTuple(bufferedTuples[i], myslot, InvalidBuffer, false);
 			recheckIndexes =
-				ExecInsertIndexTuples(myslot, &(bufferedTuples[i]->t_self),
-									  estate, false, NULL, NIL);
+				ExecInsertIndexTuples(myslot, estate, false, NULL, NIL);
 			ExecARInsertTriggers(estate, resultRelInfo,
-								 bufferedTuples[i],
+								 myslot,
 								 recheckIndexes, cstate->transition_capture);
 			list_free(recheckIndexes);
 		}
@@ -2965,8 +2955,9 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
 		for (i = 0; i < nBufferedTuples; i++)
 		{
 			cstate->cur_lineno = firstBufferedLineNo + i;
+			ExecStoreTuple(bufferedTuples[i], myslot, InvalidBuffer, false);
 			ExecARInsertTriggers(estate, resultRelInfo,
-								 bufferedTuples[i],
+								 myslot,
 								 NIL, cstate->transition_capture);
 		}
 	}
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 213a8cccbc..9e6fb8740b 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -26,6 +26,7 @@
 
 #include "access/reloptions.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "access/xlog.h"
@@ -582,25 +583,28 @@ static bool
 intorel_receive(TupleTableSlot *slot, DestReceiver *self)
 {
 	DR_intorel *myState = (DR_intorel *) self;
-	HeapTuple	tuple;
 
 	/*
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
 	 */
-	tuple = ExecHeapifySlot(slot);
+	ExecMaterializeSlot(slot);
 
 	/*
 	 * force assignment of new OID (see comments in ExecInsert)
 	 */
 	if (myState->rel->rd_rel->relhasoids)
-		HeapTupleSetOid(tuple, InvalidOid);
-
-	heap_insert(myState->rel,
-				tuple,
-				myState->output_cid,
-				myState->hi_options,
-				myState->bistate);
+		slot->tts_tupleOid = InvalidOid;
+
+	storage_insert(myState->rel,
+				   slot,
+				   myState->output_cid,
+				   myState->hi_options,
+				   myState->bistate,
+				   NULL,
+				   NULL,
+				   NIL,
+				   NULL);
 
 	/* We know this is a newly created relation, so there are no indexes */
 
@@ -619,7 +623,7 @@ intorel_shutdown(DestReceiver *self)
 
 	/* If we skipped using WAL, must heap_sync before commit */
 	if (myState->hi_options & HEAP_INSERT_SKIP_WAL)
-		heap_sync(myState->rel);
+		storage_sync(myState->rel);
 
 	/* close rel, but keep lock until commit */
 	heap_close(myState->rel, NoLock);
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index b440740e28..936ea9b9e5 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -16,6 +16,7 @@
 
 #include "access/htup_details.h"
 #include "access/multixact.h"
+#include "access/storageam.h"
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/catalog.h"
@@ -491,19 +492,22 @@ static bool
 transientrel_receive(TupleTableSlot *slot, DestReceiver *self)
 {
 	DR_transientrel *myState = (DR_transientrel *) self;
-	HeapTuple	tuple;
 
 	/*
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
 	 */
-	tuple = ExecHeapifySlot(slot);
-
-	heap_insert(myState->transientrel,
-				tuple,
-				myState->output_cid,
-				myState->hi_options,
-				myState->bistate);
+	ExecMaterializeSlot(slot);
+
+	storage_insert(myState->transientrel,
+				   slot,
+				   myState->output_cid,
+				   myState->hi_options,
+				   myState->bistate,
+				   NULL,
+				   NULL,
+				   NIL,
+				   NULL);
 
 	/* We know this is a newly created relation, so there are no indexes */
 
@@ -522,7 +526,7 @@ transientrel_shutdown(DestReceiver *self)
 
 	/* If we skipped using WAL, must heap_sync before commit */
 	if (myState->hi_options & HEAP_INSERT_SKIP_WAL)
-		heap_sync(myState->transientrel);
+		storage_sync(myState->transientrel);
 
 	/* close transientrel, but keep lock until commit */
 	heap_close(myState->transientrel, NoLock);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d979ce266d..73b68826f2 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -19,6 +19,7 @@
 #include "access/multixact.h"
 #include "access/reloptions.h"
 #include "access/relscan.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/tupconvert.h"
 #include "access/xact.h"
@@ -4663,7 +4664,8 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 
 			/* Write the tuple out to the new relation */
 			if (newrel)
-				heap_insert(newrel, tuple, mycid, hi_options, bistate);
+				storage_insert(newrel, newslot, mycid, hi_options, bistate,
+							   NULL, NULL, NIL, NULL);
 
 			ResetExprContext(econtext);
 
@@ -4687,7 +4689,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 
 		/* If we skipped writing WAL, then we need to sync the heap. */
 		if (hi_options & HEAP_INSERT_SKIP_WAL)
-			heap_sync(newrel);
+			storage_sync(newrel);
 
 		heap_close(newrel, NoLock);
 	}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 96c4fe7d43..0a58ea16b6 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -15,6 +15,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/htup_details.h"
 #include "access/xact.h"
@@ -2352,17 +2353,21 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 
 void
 ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
-					 HeapTuple trigtuple, List *recheckIndexes,
+					 TupleTableSlot *slot, List *recheckIndexes,
 					 TransitionCaptureState *transition_capture)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 
 	if ((trigdesc && trigdesc->trig_insert_after_row) ||
 		(transition_capture && transition_capture->tcs_insert_new_table))
+	{
+		HeapTuple	trigtuple = ExecHeapifySlot(slot);
+
 		AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT,
 							  true, NULL, trigtuple,
 							  recheckIndexes, NULL,
 							  transition_capture);
+	}
 }
 
 TupleTableSlot *
@@ -3012,9 +3017,10 @@ GetTupleForTrigger(EState *estate,
 				   TupleTableSlot **newSlot)
 {
 	Relation	relation = relinfo->ri_RelationDesc;
-	HeapTupleData tuple;
+	StorageTuple tuple;
 	HeapTuple	result;
 	Buffer		buffer;
+	tuple_data	t_data;
 
 	if (newSlot != NULL)
 	{
@@ -3030,11 +3036,11 @@ GetTupleForTrigger(EState *estate,
 		 * lock tuple for update
 		 */
 ltrmark:;
-		tuple.t_self = *tid;
-		test = heap_lock_tuple(relation, &tuple,
-							   estate->es_output_cid,
-							   lockmode, LockWaitBlock,
-							   false, &buffer, &hufd);
+		test = storage_lock_tuple(relation, tid, &tuple,
+								  estate->es_output_cid,
+								  lockmode, LockWaitBlock,
+								  false, &buffer, &hufd);
+		result = tuple;
 		switch (test)
 		{
 			case HeapTupleSelfUpdated:
@@ -3066,7 +3072,8 @@ ltrmark:;
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("could not serialize access due to concurrent update")));
-				if (!ItemPointerEquals(&hufd.ctid, &tuple.t_self))
+				t_data = relation->rd_stamroutine->get_tuple_data(tuple, TID);
+				if (!ItemPointerEquals(&hufd.ctid, &(t_data.tid)))
 				{
 					/* it was updated, so look at the updated version */
 					TupleTableSlot *epqslot;
@@ -3112,6 +3119,7 @@ ltrmark:;
 	{
 		Page		page;
 		ItemId		lp;
+		HeapTupleData tupledata;
 
 		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
 
@@ -3130,17 +3138,17 @@ ltrmark:;
 
 		Assert(ItemIdIsNormal(lp));
 
-		tuple.t_data = (HeapTupleHeader) PageGetItem(page, lp);
-		tuple.t_len = ItemIdGetLength(lp);
-		tuple.t_self = *tid;
-		tuple.t_tableOid = RelationGetRelid(relation);
+		tupledata.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+		tupledata.t_len = ItemIdGetLength(lp);
+		tupledata.t_self = *tid;
+		tupledata.t_tableOid = RelationGetRelid(relation);
 
 		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+
+		result = heap_copytuple(&tupledata);
 	}
 
-	result = heap_copytuple(&tuple);
 	ReleaseBuffer(buffer);
-
 	return result;
 }
 
@@ -3946,8 +3954,8 @@ AfterTriggerExecute(AfterTriggerEvent event,
 	AfterTriggerShared evtshared = GetTriggerSharedData(event);
 	Oid			tgoid = evtshared->ats_tgoid;
 	TriggerData LocTriggerData;
-	HeapTupleData tuple1;
-	HeapTupleData tuple2;
+	StorageTuple tuple1;
+	StorageTuple tuple2;
 	HeapTuple	rettuple;
 	Buffer		buffer1 = InvalidBuffer;
 	Buffer		buffer2 = InvalidBuffer;
@@ -4020,10 +4028,9 @@ AfterTriggerExecute(AfterTriggerEvent event,
 		default:
 			if (ItemPointerIsValid(&(event->ate_ctid1)))
 			{
-				ItemPointerCopy(&(event->ate_ctid1), &(tuple1.t_self));
-				if (!heap_fetch(rel, SnapshotAny, &tuple1, &buffer1, false, NULL))
+				if (!storage_fetch(rel, &(event->ate_ctid1), SnapshotAny, &tuple1, &buffer1, false, NULL))
 					elog(ERROR, "failed to fetch tuple1 for AFTER trigger");
-				LocTriggerData.tg_trigtuple = &tuple1;
+				LocTriggerData.tg_trigtuple = tuple1;
 				LocTriggerData.tg_trigtuplebuf = buffer1;
 			}
 			else
@@ -4037,10 +4044,9 @@ AfterTriggerExecute(AfterTriggerEvent event,
 				AFTER_TRIGGER_2CTID &&
 				ItemPointerIsValid(&(event->ate_ctid2)))
 			{
-				ItemPointerCopy(&(event->ate_ctid2), &(tuple2.t_self));
-				if (!heap_fetch(rel, SnapshotAny, &tuple2, &buffer2, false, NULL))
+				if (!storage_fetch(rel, &(event->ate_ctid2), SnapshotAny, &tuple2, &buffer2, false, NULL))
 					elog(ERROR, "failed to fetch tuple2 for AFTER trigger");
-				LocTriggerData.tg_newtuple = &tuple2;
+				LocTriggerData.tg_newtuple = tuple2;
 				LocTriggerData.tg_newtuplebuf = buffer2;
 			}
 			else
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 89e189fa71..ab533cf9c7 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -269,12 +269,12 @@ ExecCloseIndices(ResultRelInfo *resultRelInfo)
  */
 List *
 ExecInsertIndexTuples(TupleTableSlot *slot,
-					  ItemPointer tupleid,
 					  EState *estate,
 					  bool noDupErr,
 					  bool *specConflict,
 					  List *arbiterIndexes)
 {
+	ItemPointer tupleid = &slot->tts_tid;
 	List	   *result = NIL;
 	ResultRelInfo *resultRelInfo;
 	int			i;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index dbaa47f2d3..22f225ac8a 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -38,6 +38,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -1894,7 +1895,7 @@ ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
 		/* See the comment above. */
 		if (resultRelInfo->ri_PartitionRoot)
 		{
-			HeapTuple	tuple = ExecFetchSlotTuple(slot);
+			StorageTuple tuple = ExecFetchSlotTuple(slot);
 			TupleDesc	old_tupdesc = RelationGetDescr(rel);
 			TupleConversionMap *map;
 
@@ -1974,7 +1975,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				 */
 				if (resultRelInfo->ri_PartitionRoot)
 				{
-					HeapTuple	tuple = ExecFetchSlotTuple(slot);
+					StorageTuple tuple = ExecFetchSlotTuple(slot);
 					TupleConversionMap *map;
 
 					rel = resultRelInfo->ri_PartitionRoot;
@@ -2021,7 +2022,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 			/* See the comment above. */
 			if (resultRelInfo->ri_PartitionRoot)
 			{
-				HeapTuple	tuple = ExecFetchSlotTuple(slot);
+				StorageTuple tuple = ExecFetchSlotTuple(slot);
 				TupleDesc	old_tupdesc = RelationGetDescr(rel);
 				TupleConversionMap *map;
 
@@ -2480,7 +2481,8 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
 			 ItemPointer tid, TransactionId priorXmax)
 {
 	TupleTableSlot *slot;
-	HeapTuple	copyTuple;
+	StorageTuple copyTuple;
+	tuple_data	t_data;
 
 	Assert(rti > 0);
 
@@ -2497,7 +2499,9 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
 	 * For UPDATE/DELETE we have to return tid of actual row we're executing
 	 * PQ for.
 	 */
-	*tid = copyTuple->t_self;
+
+	t_data = storage_tuple_get_data(relation, copyTuple, TID);
+	*tid = t_data.tid;
 
 	/*
 	 * Need to run a recheck subquery.  Initialize or reinitialize EPQ state.
@@ -2528,7 +2532,7 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
 	 * is to guard against early re-use of the EPQ query.
 	 */
 	if (!TupIsNull(slot))
-		(void) ExecMaterializeSlot(slot);
+		ExecMaterializeSlot(slot);
 
 	/*
 	 * Clear out the test tuple.  This is needed in case the EPQ query is
@@ -2561,14 +2565,14 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
  * Note: properly, lockmode should be declared as enum LockTupleMode,
  * but we use "int" to avoid having to include heapam.h in executor.h.
  */
-HeapTuple
+StorageTuple
 EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 				  LockWaitPolicy wait_policy,
 				  ItemPointer tid, TransactionId priorXmax)
 {
-	HeapTuple	copyTuple = NULL;
-	HeapTupleData tuple;
+	StorageTuple tuple = NULL;
 	SnapshotData SnapshotDirty;
+	tuple_data	t_data;
 
 	/*
 	 * fetch target tuple
@@ -2576,12 +2580,12 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 	 * Loop here to deal with updated or busy tuples
 	 */
 	InitDirtySnapshot(SnapshotDirty);
-	tuple.t_self = *tid;
 	for (;;)
 	{
 		Buffer		buffer;
+		ItemPointerData ctid;
 
-		if (heap_fetch(relation, &SnapshotDirty, &tuple, &buffer, true, NULL))
+		if (storage_fetch(relation, tid, &SnapshotDirty, &tuple, &buffer, true, NULL))
 		{
 			HTSU_Result test;
 			HeapUpdateFailureData hufd;
@@ -2595,7 +2599,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 			 * 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),
+			if (!TransactionIdEquals(HeapTupleHeaderGetXmin(((HeapTuple) tuple)->t_data),
 									 priorXmax))
 			{
 				ReleaseBuffer(buffer);
@@ -2617,7 +2621,8 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 				{
 					case LockWaitBlock:
 						XactLockTableWait(SnapshotDirty.xmax,
-										  relation, &tuple.t_self,
+										  relation,
+										  tid,
 										  XLTW_FetchUpdated);
 						break;
 					case LockWaitSkip:
@@ -2646,20 +2651,23 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 			 * that priorXmax == xmin, so we can test that variable instead of
 			 * doing HeapTupleHeaderGetXmin again.
 			 */
-			if (TransactionIdIsCurrentTransactionId(priorXmax) &&
-				HeapTupleHeaderGetCmin(tuple.t_data) >= estate->es_output_cid)
+			if (TransactionIdIsCurrentTransactionId(priorXmax))
 			{
-				ReleaseBuffer(buffer);
-				return NULL;
+				t_data = storage_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 = heap_lock_tuple(relation, &tuple,
-								   estate->es_output_cid,
-								   lockmode, wait_policy,
-								   false, &buffer, &hufd);
+			test = storage_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);
 
@@ -2695,12 +2703,15 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 								(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 								 errmsg("could not serialize access due to concurrent update")));
 
+#if 0 //hari
 					/* Should not encounter speculative tuple on recheck */
 					Assert(!HeapTupleHeaderIsSpeculative(tuple.t_data));
-					if (!ItemPointerEquals(&hufd.ctid, &tuple.t_self))
+#endif
+					t_data = storage_tuple_get_data(relation, tuple, TID);
+					if (!ItemPointerEquals(&hufd.ctid, &t_data.tid))
 					{
 						/* it was updated, so look at the updated version */
-						tuple.t_self = hufd.ctid;
+						*tid = hufd.ctid;
 						/* updated row should have xmin matching this xmax */
 						priorXmax = hufd.xmax;
 						continue;
@@ -2722,10 +2733,6 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 					return NULL;	/* keep compiler quiet */
 			}
 
-			/*
-			 * We got tuple - now copy it for use by recheck query.
-			 */
-			copyTuple = heap_copytuple(&tuple);
 			ReleaseBuffer(buffer);
 			break;
 		}
@@ -2734,7 +2741,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 		 * 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)
+		if (tuple == NULL)
 		{
 			ReleaseBuffer(buffer);
 			return NULL;
@@ -2743,7 +2750,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 		/*
 		 * As above, if xmin isn't what we're expecting, do nothing.
 		 */
-		if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple.t_data),
+		if (!TransactionIdEquals(HeapTupleHeaderGetXmin(((HeapTuple) tuple)->t_data),
 								 priorXmax))
 		{
 			ReleaseBuffer(buffer);
@@ -2762,7 +2769,9 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 		 * 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))
+		t_data = storage_tuple_get_data(relation, tuple, CTID);
+		ctid = t_data.tid;
+		if (ItemPointerEquals(tid, &ctid))
 		{
 			/* deleted, so forget about it */
 			ReleaseBuffer(buffer);
@@ -2770,17 +2779,19 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 		}
 
 		/* updated, so look at the updated row */
-		tuple.t_self = tuple.t_data->t_ctid;
+		*tid = ctid;
+
 		/* updated row should have xmin matching this xmax */
-		priorXmax = HeapTupleHeaderGetUpdateXid(tuple.t_data);
+		t_data = storage_tuple_get_data(relation, tuple, UPDATED_XID);
+		priorXmax = t_data.xid;
 		ReleaseBuffer(buffer);
 		/* loop back to fetch next in chain */
 	}
 
 	/*
-	 * Return the copied tuple
+	 * Return the tuple
 	 */
-	return copyTuple;
+	return tuple;
 }
 
 /*
@@ -2826,7 +2837,7 @@ EvalPlanQualSetPlan(EPQState *epqstate, Plan *subplan, List *auxrowmarks)
  * NB: passed tuple must be palloc'd; it may get freed later
  */
 void
-EvalPlanQualSetTuple(EPQState *epqstate, Index rti, HeapTuple tuple)
+EvalPlanQualSetTuple(EPQState *epqstate, Index rti, StorageTuple tuple)
 {
 	EState	   *estate = epqstate->estate;
 
@@ -2845,7 +2856,7 @@ EvalPlanQualSetTuple(EPQState *epqstate, Index rti, HeapTuple tuple)
 /*
  * Fetch back the current test tuple (if any) for the specified RTI
  */
-HeapTuple
+StorageTuple
 EvalPlanQualGetTuple(EPQState *epqstate, Index rti)
 {
 	EState	   *estate = epqstate->estate;
@@ -2873,7 +2884,7 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 		ExecRowMark *erm = aerm->rowmark;
 		Datum		datum;
 		bool		isNull;
-		HeapTupleData tuple;
+		StorageTuple tuple;
 
 		if (RowMarkRequiresRowShareLock(erm->markType))
 			elog(ERROR, "EvalPlanQual doesn't support locking rowmarks");
@@ -2904,8 +2915,6 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 
 		if (erm->markType == ROW_MARK_REFERENCE)
 		{
-			HeapTuple	copyTuple;
-
 			Assert(erm->relation != NULL);
 
 			/* fetch the tuple's ctid */
@@ -2929,11 +2938,11 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 							 errmsg("cannot lock rows in foreign table \"%s\"",
 									RelationGetRelationName(erm->relation))));
-				copyTuple = fdwroutine->RefetchForeignRow(epqstate->estate,
-														  erm,
-														  datum,
-														  &updated);
-				if (copyTuple == NULL)
+				tuple = fdwroutine->RefetchForeignRow(epqstate->estate,
+													  erm,
+													  datum,
+													  &updated);
+				if (tuple == NULL)
 					elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck");
 
 				/*
@@ -2947,23 +2956,18 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 				/* ordinary table, fetch the tuple */
 				Buffer		buffer;
 
-				tuple.t_self = *((ItemPointer) DatumGetPointer(datum));
-				if (!heap_fetch(erm->relation, SnapshotAny, &tuple, &buffer,
-								false, NULL))
+				if (!storage_fetch(erm->relation, (ItemPointer) DatumGetPointer(datum), SnapshotAny, &tuple, &buffer,
+								   false, NULL))
 					elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck");
 
-				/* successful, copy tuple */
-				copyTuple = heap_copytuple(&tuple);
 				ReleaseBuffer(buffer);
 			}
 
 			/* store tuple */
-			EvalPlanQualSetTuple(epqstate, erm->rti, copyTuple);
+			EvalPlanQualSetTuple(epqstate, erm->rti, tuple);
 		}
 		else
 		{
-			HeapTupleHeader td;
-
 			Assert(erm->markType == ROW_MARK_COPY);
 
 			/* fetch the whole-row Var for the relation */
@@ -2973,19 +2977,12 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 			/* non-locked rels could be on the inside of outer joins */
 			if (isNull)
 				continue;
-			td = DatumGetHeapTupleHeader(datum);
-
-			/* build a temporary HeapTuple control structure */
-			tuple.t_len = HeapTupleHeaderGetDatumLength(td);
-			tuple.t_data = td;
-			/* relation might be a foreign table, if so provide tableoid */
-			tuple.t_tableOid = erm->relid;
-			/* also copy t_ctid in case there's valid data there */
-			tuple.t_self = td->t_ctid;
-
-			/* copy and store tuple */
-			EvalPlanQualSetTuple(epqstate, erm->rti,
-								 heap_copytuple(&tuple));
+
+			tuple = storage_tuple_by_datum(erm->relation, datum, erm->relid);
+
+			/* store tuple */
+			EvalPlanQualSetTuple(epqstate, erm->rti, tuple);
+
 		}
 	}
 }
@@ -3154,8 +3151,8 @@ EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
 	}
 	else
 	{
-		estate->es_epqTuple = (HeapTuple *)
-			palloc0(rtsize * sizeof(HeapTuple));
+		estate->es_epqTuple = (StorageTuple *)
+			palloc0(rtsize * sizeof(StorageTuple));
 		estate->es_epqTupleSet = (bool *)
 			palloc0(rtsize * sizeof(bool));
 	}
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 12c15fd6bc..fcb58a0421 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -15,6 +15,7 @@
 #include "postgres.h"
 
 #include "access/relscan.h"
+#include "access/storageam.h"
 #include "access/transam.h"
 #include "access/xact.h"
 #include "commands/trigger.h"
@@ -169,19 +170,19 @@ retry:
 		Buffer		buf;
 		HeapUpdateFailureData hufd;
 		HTSU_Result res;
-		HeapTupleData locktup;
-
-		ItemPointerCopy(&outslot->tts_tid, &locktup.t_self);
+		StorageTuple locktup;
 
 		PushActiveSnapshot(GetLatestSnapshot());
 
-		res = heap_lock_tuple(rel, &locktup, GetCurrentCommandId(false),
-							  lockmode,
-							  LockWaitBlock,
-							  false /* don't follow updates */ ,
-							  &buf, &hufd);
+		res = storage_lock_tuple(rel, &(outslot->tts_tid), &locktup, GetCurrentCommandId(false),
+								 lockmode,
+								 LockWaitBlock,
+								 false /* don't follow updates */ ,
+								 &buf, &hufd);
 		/* the tuple slot already has the buffer pinned */
-		ReleaseBuffer(buf);
+		if (BufferIsValid(buf))
+			ReleaseBuffer(buf);
+		pfree(locktup);
 
 		PopActiveSnapshot();
 
@@ -277,19 +278,20 @@ retry:
 		Buffer		buf;
 		HeapUpdateFailureData hufd;
 		HTSU_Result res;
-		HeapTupleData locktup;
-
-		ItemPointerCopy(&outslot->tts_tid, &locktup.t_self);
+		StorageTuple locktup;
 
 		PushActiveSnapshot(GetLatestSnapshot());
 
-		res = heap_lock_tuple(rel, &locktup, GetCurrentCommandId(false),
-							  lockmode,
-							  LockWaitBlock,
-							  false /* don't follow updates */ ,
-							  &buf, &hufd);
+		res = storage_lock_tuple(rel, &(outslot->tts_tid), &locktup, GetCurrentCommandId(false),
+								 lockmode,
+								 LockWaitBlock,
+								 false /* don't follow updates */ ,
+								 &buf, &hufd);
 		/* the tuple slot already has the buffer pinned */
-		ReleaseBuffer(buf);
+		if (BufferIsValid(buf))
+			ReleaseBuffer(buf);
+
+		pfree(locktup);
 
 		PopActiveSnapshot();
 
@@ -327,7 +329,6 @@ void
 ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 {
 	bool		skip_tuple = false;
-	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 
@@ -354,19 +355,12 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 		if (rel->rd_att->constr)
 			ExecConstraints(resultRelInfo, slot, estate);
 
-		/* Store the slot into tuple that we can inspect. */
-		tuple = ExecHeapifySlot(slot);
-
-		/* OK, store the tuple and create index entries for it */
-		simple_heap_insert(rel, tuple);
-
-		if (resultRelInfo->ri_NumIndices > 0)
-			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
-												   estate, false, NULL,
-												   NIL);
+		storage_insert(resultRelInfo->ri_RelationDesc, slot,
+					   GetCurrentCommandId(true), 0, NULL,
+					   ExecInsertIndexTuples, estate, NIL, &recheckIndexes);
 
 		/* AFTER ROW INSERT Triggers */
-		ExecARInsertTriggers(estate, resultRelInfo, tuple,
+		ExecARInsertTriggers(estate, resultRelInfo, slot,
 							 recheckIndexes, NULL);
 
 		/*
@@ -390,7 +384,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 						 TupleTableSlot *searchslot, TupleTableSlot *slot)
 {
 	bool		skip_tuple = false;
-	HeapTuple	tuple;
+	StorageTuple tuple;
 	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	ItemPointer tid = &(searchslot->tts_tid);
@@ -415,22 +409,18 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 	if (!skip_tuple)
 	{
 		List	   *recheckIndexes = NIL;
+		HeapUpdateFailureData hufd;
+		LockTupleMode lockmode;
+		InsertIndexTuples IndexFunc = ExecInsertIndexTuples;
 
 		/* Check the constraints of the tuple */
 		if (rel->rd_att->constr)
 			ExecConstraints(resultRelInfo, slot, estate);
 
-		/* Store the slot into tuple that we can write. */
-		tuple = ExecHeapifySlot(slot);
+		storage_update(rel, tid, slot, estate, GetCurrentCommandId(true), InvalidSnapshot,
+					   true, &hufd, &lockmode, IndexFunc, &recheckIndexes);
 
-		/* OK, update the tuple and index entries for it */
-		simple_heap_update(rel, tid, tuple);
-
-		if (resultRelInfo->ri_NumIndices > 0 &&
-			!HeapTupleIsHeapOnly(tuple))
-			recheckIndexes = ExecInsertIndexTuples(slot, tid,
-												   estate, false, NULL,
-												   NIL);
+		tuple = ExecHeapifySlot(slot);
 
 		/* AFTER ROW UPDATE Triggers */
 		ExecARUpdateTriggers(estate, resultRelInfo,
diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c
index 93895600a5..2da5240d24 100644
--- a/src/backend/executor/nodeLockRows.c
+++ b/src/backend/executor/nodeLockRows.c
@@ -22,6 +22,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/xact.h"
 #include "executor/executor.h"
 #include "executor/nodeLockRows.h"
@@ -74,18 +75,20 @@ lnext:
 	{
 		ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc);
 		ExecRowMark *erm = aerm->rowmark;
-		HeapTuple  *testTuple;
+		StorageTuple *testTuple;
 		Datum		datum;
 		bool		isNull;
-		HeapTupleData tuple;
+		StorageTuple tuple;
 		Buffer		buffer;
 		HeapUpdateFailureData hufd;
 		LockTupleMode lockmode;
 		HTSU_Result test;
-		HeapTuple	copyTuple;
+		StorageTuple copyTuple;
+		ItemPointerData tid;
+		tuple_data	t_data;
 
 		/* clear any leftover test tuple for this rel */
-		testTuple = &(node->lr_curtuples[erm->rti - 1]);
+		testTuple = (StorageTuple) (&(node->lr_curtuples[erm->rti - 1]));
 		if (*testTuple != NULL)
 			heap_freetuple(*testTuple);
 		*testTuple = NULL;
@@ -159,7 +162,7 @@ lnext:
 		}
 
 		/* okay, try to lock the tuple */
-		tuple.t_self = *((ItemPointer) DatumGetPointer(datum));
+		tid = *((ItemPointer) DatumGetPointer(datum));
 		switch (erm->markType)
 		{
 			case ROW_MARK_EXCLUSIVE:
@@ -180,11 +183,13 @@ lnext:
 				break;
 		}
 
-		test = heap_lock_tuple(erm->relation, &tuple,
-							   estate->es_output_cid,
-							   lockmode, erm->waitPolicy, true,
-							   &buffer, &hufd);
-		ReleaseBuffer(buffer);
+		test = storage_lock_tuple(erm->relation, &tid, &tuple,
+								  estate->es_output_cid,
+								  lockmode, erm->waitPolicy, true,
+								  &buffer, &hufd);
+		if (BufferIsValid(buffer))
+			ReleaseBuffer(buffer);
+
 		switch (test)
 		{
 			case HeapTupleWouldBlock:
@@ -218,7 +223,8 @@ lnext:
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("could not serialize access due to concurrent update")));
-				if (ItemPointerEquals(&hufd.ctid, &tuple.t_self))
+				t_data = erm->relation->rd_stamroutine->get_tuple_data(tuple, TID);
+				if (ItemPointerEquals(&hufd.ctid, &(t_data.tid)))
 				{
 					/* Tuple was deleted, so don't return it */
 					goto lnext;
@@ -238,7 +244,8 @@ lnext:
 					goto lnext;
 				}
 				/* remember the actually locked tuple's TID */
-				tuple.t_self = copyTuple->t_self;
+				t_data = erm->relation->rd_stamroutine->get_tuple_data(copyTuple, TID);
+				tid = t_data.tid;
 
 				/* Save locked tuple for EvalPlanQual testing below */
 				*testTuple = copyTuple;
@@ -258,7 +265,7 @@ lnext:
 		}
 
 		/* Remember locked tuple's TID for EPQ testing and WHERE CURRENT OF */
-		erm->curCtid = tuple.t_self;
+		erm->curCtid = tid;
 	}
 
 	/*
@@ -280,7 +287,7 @@ lnext:
 		{
 			ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc);
 			ExecRowMark *erm = aerm->rowmark;
-			HeapTupleData tuple;
+			StorageTuple tuple;
 			Buffer		buffer;
 
 			/* skip non-active child tables, but clear their test tuples */
@@ -308,14 +315,12 @@ lnext:
 			Assert(ItemPointerIsValid(&(erm->curCtid)));
 
 			/* okay, fetch the tuple */
-			tuple.t_self = erm->curCtid;
-			if (!heap_fetch(erm->relation, SnapshotAny, &tuple, &buffer,
-							false, NULL))
+			if (!storage_fetch(erm->relation, &erm->curCtid, SnapshotAny, &tuple, &buffer,
+							   false, NULL))
 				elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck");
 
 			/* successful, copy and store tuple */
-			EvalPlanQualSetTuple(&node->lr_epqstate, erm->rti,
-								 heap_copytuple(&tuple));
+			EvalPlanQualSetTuple(&node->lr_epqstate, erm->rti, tuple);
 			ReleaseBuffer(buffer);
 		}
 
@@ -394,8 +399,8 @@ ExecInitLockRows(LockRows *node, EState *estate, int eflags)
 	 * Create workspace in which we can remember per-RTE locked tuples
 	 */
 	lrstate->lr_ntables = list_length(estate->es_range_table);
-	lrstate->lr_curtuples = (HeapTuple *)
-		palloc0(lrstate->lr_ntables * sizeof(HeapTuple));
+	lrstate->lr_curtuples = (StorageTuple *)
+		palloc0(lrstate->lr_ntables * sizeof(StorageTuple));
 
 	/*
 	 * Locate the ExecRowMark(s) that this node is responsible for, and
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index f0307ba50e..86bf30bd06 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -38,7 +38,10 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/storageam.h"
+#include "access/storageam.h"
 #include "access/xact.h"
+#include "catalog/pg_am.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -165,15 +168,13 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
 		econtext->ecxt_scantuple = tupleSlot;
 	else
 	{
-		HeapTuple	tuple;
-
 		/*
 		 * RETURNING expressions might reference the tableoid column, so
 		 * initialize t_tableOid before evaluating them.
 		 */
 		Assert(!TupIsNull(econtext->ecxt_scantuple));
-		tuple = ExecHeapifySlot(econtext->ecxt_scantuple);
-		tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+		ExecSlotUpdateTupleTableoid(econtext->ecxt_scantuple,
+									RelationGetRelid(resultRelInfo->ri_RelationDesc));
 	}
 	econtext->ecxt_outertuple = planSlot;
 
@@ -192,7 +193,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
 static void
 ExecCheckHeapTupleVisible(EState *estate,
 						  Relation rel,
-						  HeapTuple tuple,
+						  StorageTuple tuple,
 						  Buffer buffer)
 {
 	if (!IsolationUsesXactSnapshot())
@@ -205,13 +206,15 @@ ExecCheckHeapTupleVisible(EState *estate,
 	LockBuffer(buffer, BUFFER_LOCK_SHARE);
 	if (!HeapTupleSatisfiesVisibility(rel->rd_stamroutine, tuple, estate->es_snapshot, buffer))
 	{
+		tuple_data	t_data = storage_tuple_get_data(rel, tuple, XMIN);
+
 		/*
 		 * We should not raise a serialization failure if the conflict is
 		 * against a tuple inserted by our own transaction, even if it's not
 		 * visible to our snapshot.  (This would happen, for example, if
 		 * conflicting keys are proposed for insertion in a single command.)
 		 */
-		if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple->t_data)))
+		if (!TransactionIdIsCurrentTransactionId(t_data.xid))
 			ereport(ERROR,
 					(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 					 errmsg("could not serialize access due to concurrent update")));
@@ -227,19 +230,20 @@ ExecCheckTIDVisible(EState *estate,
 					ResultRelInfo *relinfo,
 					ItemPointer tid)
 {
-	Relation	rel = relinfo->ri_RelationDesc;
 	Buffer		buffer;
-	HeapTupleData tuple;
+	Relation	rel = relinfo->ri_RelationDesc;
+	StorageTuple tuple;
 
 	/* Redundantly check isolation level */
 	if (!IsolationUsesXactSnapshot())
 		return;
 
-	tuple.t_self = *tid;
-	if (!heap_fetch(rel, SnapshotAny, &tuple, &buffer, false, NULL))
+	if (!storage_fetch(rel, tid, SnapshotAny, &tuple, &buffer, false, NULL))
 		elog(ERROR, "failed to fetch conflicting tuple for ON CONFLICT");
-	ExecCheckHeapTupleVisible(estate, rel, &tuple, buffer);
-	ReleaseBuffer(buffer);
+	ExecCheckHeapTupleVisible(estate, rel, tuple, buffer);
+	if (BufferIsValid(buffer))
+		ReleaseBuffer(buffer);
+	pfree(tuple);
 }
 
 /* ----------------------------------------------------------------
@@ -260,7 +264,7 @@ ExecInsert(ModifyTableState *mtstate,
 		   EState *estate,
 		   bool canSetTag)
 {
-	HeapTuple	tuple;
+	StorageTuple tuple;
 	ResultRelInfo *resultRelInfo;
 	ResultRelInfo *saved_resultRelInfo = NULL;
 	Relation	resultRelationDesc;
@@ -268,12 +272,6 @@ ExecInsert(ModifyTableState *mtstate,
 	List	   *recheckIndexes = NIL;
 	TupleTableSlot *result = NULL;
 
-	/*
-	 * get the heap tuple out of the tuple table slot, making sure we have a
-	 * writable copy
-	 */
-	tuple = ExecHeapifySlot(slot);
-
 	/*
 	 * get information on the (current) result relation
 	 */
@@ -285,6 +283,8 @@ ExecInsert(ModifyTableState *mtstate,
 		int			leaf_part_index;
 		TupleConversionMap *map;
 
+		tuple = ExecHeapifySlot(slot);
+
 		/*
 		 * Away we go ... If we end up not finding a partition after all,
 		 * ExecFindPartition() does not return and errors out instead.
@@ -375,19 +375,31 @@ ExecInsert(ModifyTableState *mtstate,
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/*
-	 * If the result relation has OIDs, force the tuple's OID to zero so that
-	 * heap_insert will assign a fresh OID.  Usually the OID already will be
-	 * zero at this point, but there are corner cases where the plan tree can
-	 * return a tuple extracted literally from some table with the same
-	 * rowtype.
+	 * get the heap tuple out of the tuple table slot, making sure we have a
+	 * writable copy  <-- obsolete comment XXX explain what we really do here
+	 *
+	 * Do we really need to do this here?
+	 */
+	ExecMaterializeSlot(slot);
+
+
+	/*
+	 * If the result relation uses heapam and has OIDs, force the tuple's OID
+	 * to zero so that heap_insert will assign a fresh OID.  Usually the OID
+	 * already will be zero at this point, but there are corner cases where
+	 * the plan tree can return a tuple extracted literally from some table
+	 * with the same rowtype.
 	 *
 	 * XXX if we ever wanted to allow users to assign their own OIDs to new
 	 * rows, this'd be the place to do it.  For the moment, we make a point of
 	 * doing this before calling triggers, so that a user-supplied trigger
 	 * could hack the OID if desired.
 	 */
-	if (resultRelationDesc->rd_rel->relhasoids)
-		HeapTupleSetOid(tuple, InvalidOid);
+	if (resultRelationDesc->rd_rel->relam == HEAPAM_STORAGE_AM_OID &&
+		resultRelationDesc->rd_rel->relhasoids)
+	{
+		slot->tts_tupleOid = InvalidOid;
+	}
 
 	/*
 	 * BEFORE ROW INSERT Triggers.
@@ -405,9 +417,6 @@ ExecInsert(ModifyTableState *mtstate,
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
-
-		/* trigger might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW INSERT Triggers */
@@ -419,9 +428,6 @@ ExecInsert(ModifyTableState *mtstate,
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
 
-		/* trigger might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
-
 		newId = InvalidOid;
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
@@ -437,14 +443,12 @@ ExecInsert(ModifyTableState *mtstate,
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
 
-		/* FDW might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
-
 		/*
 		 * AFTER ROW Triggers or RETURNING expressions might reference the
 		 * tableoid column, so initialize t_tableOid before evaluating them.
 		 */
-		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
+		slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
+		ExecSlotUpdateTupleTableoid(slot, slot->tts_tableOid);
 
 		newId = InvalidOid;
 	}
@@ -464,7 +468,8 @@ ExecInsert(ModifyTableState *mtstate,
 		 * Constraints might reference the tableoid column, so initialize
 		 * t_tableOid before evaluating them.
 		 */
-		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
+		slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
+		ExecSlotUpdateTupleTableoid(slot, slot->tts_tableOid);
 
 		/*
 		 * Check any RLS INSERT WITH CHECK policies
@@ -494,7 +499,6 @@ ExecInsert(ModifyTableState *mtstate,
 			/* Perform a speculative insertion. */
 			uint32		specToken;
 			ItemPointerData conflictTid;
-			bool		specConflict;
 
 			/*
 			 * Do a non-conclusive check for conflicts first.
@@ -509,7 +513,7 @@ ExecInsert(ModifyTableState *mtstate,
 			 * speculatively.
 			 */
 	vlock:
-			specConflict = false;
+			slot->tts_specConflict = false;
 			if (!ExecCheckIndexConstraints(slot, estate, &conflictTid,
 										   arbiterIndexes))
 			{
@@ -555,24 +559,17 @@ ExecInsert(ModifyTableState *mtstate,
 			 * waiting for the whole transaction to complete.
 			 */
 			specToken = SpeculativeInsertionLockAcquire(GetCurrentTransactionId());
-			HeapTupleHeaderSetSpeculativeToken(tuple->t_data, specToken);
+			slot->tts_speculativeToken = specToken;
 
 			/* insert the tuple, with the speculative token */
-			newId = heap_insert(resultRelationDesc, tuple,
-								estate->es_output_cid,
-								HEAP_INSERT_SPECULATIVE,
-								NULL);
-
-			/* insert index entries for tuple */
-			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
-												   estate, true, &specConflict,
-												   arbiterIndexes);
-
-			/* adjust the tuple's state accordingly */
-			if (!specConflict)
-				heap_finish_speculative(resultRelationDesc, tuple);
-			else
-				heap_abort_speculative(resultRelationDesc, tuple);
+			newId = storage_insert(resultRelationDesc, slot,
+								   estate->es_output_cid,
+								   HEAP_INSERT_SPECULATIVE,
+								   NULL,
+								   ExecInsertIndexTuples,
+								   estate,
+								   arbiterIndexes,
+								   &recheckIndexes);
 
 			/*
 			 * Wake up anyone waiting for our decision.  They will re-check
@@ -588,7 +585,7 @@ ExecInsert(ModifyTableState *mtstate,
 			 * the pre-check again, which will now find the conflicting tuple
 			 * (unless it aborts before we get there).
 			 */
-			if (specConflict)
+			if (slot->tts_specConflict)
 			{
 				list_free(recheckIndexes);
 				goto vlock;
@@ -600,19 +597,14 @@ ExecInsert(ModifyTableState *mtstate,
 		{
 			/*
 			 * insert the tuple normally.
-			 *
-			 * Note: heap_insert returns the tid (location) of the new tuple
-			 * in the t_self field.
 			 */
-			newId = heap_insert(resultRelationDesc, tuple,
-								estate->es_output_cid,
-								0, NULL);
-
-			/* insert index entries for tuple */
-			if (resultRelInfo->ri_NumIndices > 0)
-				recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
-													   estate, false, NULL,
-													   arbiterIndexes);
+			newId = storage_insert(resultRelationDesc, slot,
+								   estate->es_output_cid,
+								   0, NULL,
+								   ExecInsertIndexTuples,
+								   estate,
+								   arbiterIndexes,
+								   &recheckIndexes);
 		}
 	}
 
@@ -620,11 +612,11 @@ ExecInsert(ModifyTableState *mtstate,
 	{
 		(estate->es_processed)++;
 		estate->es_lastoid = newId;
-		setLastTid(&(tuple->t_self));
+		setLastTid(&(slot->tts_tid));
 	}
 
 	/* AFTER ROW INSERT Triggers */
-	ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes,
+	ExecARInsertTriggers(estate, resultRelInfo, slot, recheckIndexes,
 						 mtstate->mt_transition_capture);
 
 	list_free(recheckIndexes);
@@ -675,7 +667,7 @@ ExecInsert(ModifyTableState *mtstate,
 static TupleTableSlot *
 ExecDelete(ModifyTableState *mtstate,
 		   ItemPointer tupleid,
-		   HeapTuple oldtuple,
+		   StorageTuple oldtuple,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
 		   EState *estate,
@@ -720,8 +712,6 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
-		HeapTuple	tuple;
-
 		/*
 		 * delete from foreign table: let the FDW do it
 		 *
@@ -747,8 +737,10 @@ ExecDelete(ModifyTableState *mtstate,
 		 */
 		if (slot->tts_isempty)
 			ExecStoreAllNullTuple(slot);
-		tuple = ExecHeapifySlot(slot);
-		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
+
+		ExecMaterializeSlot(slot);
+
+		slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
 	}
 	else
 	{
@@ -762,11 +754,11 @@ ExecDelete(ModifyTableState *mtstate,
 		 * mode transactions.
 		 */
 ldelete:;
-		result = heap_delete(resultRelationDesc, tupleid,
-							 estate->es_output_cid,
-							 estate->es_crosscheck_snapshot,
-							 true /* wait for commit */ ,
-							 &hufd);
+		result = storage_delete(resultRelationDesc, tupleid,
+								estate->es_output_cid,
+								estate->es_crosscheck_snapshot,
+								true /* wait for commit */ ,
+								&hufd);
 		switch (result)
 		{
 			case HeapTupleSelfUpdated:
@@ -862,7 +854,7 @@ ldelete:;
 		 * gotta fetch it.  We can use the trigger tuple slot.
 		 */
 		TupleTableSlot *rslot;
-		HeapTupleData deltuple;
+		StorageTuple deltuple = NULL;
 		Buffer		delbuffer;
 
 		if (resultRelInfo->ri_FdwRoutine)
@@ -876,20 +868,19 @@ ldelete:;
 			slot = estate->es_trig_tuple_slot;
 			if (oldtuple != NULL)
 			{
-				deltuple = *oldtuple;
+				deltuple = heap_copytuple(oldtuple);
 				delbuffer = InvalidBuffer;
 			}
 			else
 			{
-				deltuple.t_self = *tupleid;
-				if (!heap_fetch(resultRelationDesc, SnapshotAny,
-								&deltuple, &delbuffer, false, NULL))
+				if (!storage_fetch(resultRelationDesc, tupleid, SnapshotAny,
+								   &deltuple, &delbuffer, false, NULL))
 					elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
 			}
 
 			if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
 				ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
-			ExecStoreTuple(&deltuple, slot, InvalidBuffer, false);
+			ExecStoreTuple(deltuple, slot, InvalidBuffer, false);
 		}
 
 		rslot = ExecProcessReturning(resultRelInfo, slot, planSlot);
@@ -898,7 +889,7 @@ ldelete:;
 		 * Before releasing the target tuple again, make sure rslot has a
 		 * local copy of any pass-by-reference values.
 		 */
-		ExecHeapifySlot(rslot);
+		ExecMaterializeSlot(rslot);
 
 		ExecClearTuple(slot);
 		if (BufferIsValid(delbuffer))
@@ -935,14 +926,14 @@ ldelete:;
 static TupleTableSlot *
 ExecUpdate(ModifyTableState *mtstate,
 		   ItemPointer tupleid,
-		   HeapTuple oldtuple,
+		   StorageTuple oldtuple,
 		   TupleTableSlot *slot,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
 		   EState *estate,
 		   bool canSetTag)
 {
-	HeapTuple	tuple;
+	StorageTuple tuple;
 	ResultRelInfo *resultRelInfo;
 	Relation	resultRelationDesc;
 	HTSU_Result result;
@@ -1007,14 +998,14 @@ ExecUpdate(ModifyTableState *mtstate,
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
 
-		/* FDW might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
-
 		/*
 		 * AFTER ROW Triggers or RETURNING expressions might reference the
 		 * tableoid column, so initialize t_tableOid before evaluating them.
 		 */
-		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
+		ExecSlotUpdateTupleTableoid(slot, RelationGetRelid(resultRelationDesc));
+
+		/* FDW might have changed tuple */
+		tuple = ExecHeapifySlot(slot);
 	}
 	else
 	{
@@ -1024,7 +1015,7 @@ ExecUpdate(ModifyTableState *mtstate,
 		 * Constraints might reference the tableoid column, so initialize
 		 * t_tableOid before evaluating them.
 		 */
-		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
+		slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
 
 		/*
 		 * Check any RLS UPDATE WITH CHECK policies
@@ -1060,11 +1051,14 @@ lreplace:;
 		 * needed for referential integrity updates in transaction-snapshot
 		 * mode transactions.
 		 */
-		result = heap_update(resultRelationDesc, tupleid, tuple,
-							 estate->es_output_cid,
-							 estate->es_crosscheck_snapshot,
-							 true /* wait for commit */ ,
-							 &hufd, &lockmode);
+		result = storage_update(resultRelationDesc, tupleid, slot,
+								estate,
+								estate->es_output_cid,
+								estate->es_crosscheck_snapshot,
+								true /* wait for commit */ ,
+								&hufd, &lockmode,
+								ExecInsertIndexTuples,
+								&recheckIndexes);
 		switch (result)
 		{
 			case HeapTupleSelfUpdated:
@@ -1135,26 +1129,6 @@ lreplace:;
 				elog(ERROR, "unrecognized heap_update status: %u", result);
 				return NULL;
 		}
-
-		/*
-		 * Note: instead of having to update the old index tuples associated
-		 * with the heap tuple, all we do is form and insert new index tuples.
-		 * This is because UPDATEs are actually DELETEs and INSERTs, and index
-		 * tuple deletion is done later by VACUUM (see notes in ExecDelete).
-		 * All we do here is insert new index tuples.  -cim 9/27/89
-		 */
-
-		/*
-		 * insert index entries for tuple
-		 *
-		 * Note: heap_update returns the tid (location) of the new tuple in
-		 * the t_self field.
-		 *
-		 * If it's a HOT update, we mustn't insert new index entries.
-		 */
-		if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple))
-			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
-												   estate, false, NULL, NIL);
 	}
 
 	if (canSetTag)
@@ -1212,11 +1186,12 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	ExprContext *econtext = mtstate->ps.ps_ExprContext;
 	Relation	relation = resultRelInfo->ri_RelationDesc;
 	ExprState  *onConflictSetWhere = resultRelInfo->ri_onConflictSetWhere;
-	HeapTupleData tuple;
+	StorageTuple tuple = NULL;
 	HeapUpdateFailureData hufd;
 	LockTupleMode lockmode;
 	HTSU_Result test;
 	Buffer		buffer;
+	tuple_data	t_data;
 
 	/* Determine lock mode to use */
 	lockmode = ExecUpdateLockMode(estate, resultRelInfo);
@@ -1227,10 +1202,8 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	 * previous conclusion that the tuple is conclusively committed is not
 	 * true anymore.
 	 */
-	tuple.t_self = *conflictTid;
-	test = heap_lock_tuple(relation, &tuple, estate->es_output_cid,
-						   lockmode, LockWaitBlock, false, &buffer,
-						   &hufd);
+	test = storage_lock_tuple(relation, conflictTid, &tuple, estate->es_output_cid,
+							  lockmode, LockWaitBlock, false, &buffer, &hufd);
 	switch (test)
 	{
 		case HeapTupleMayBeUpdated:
@@ -1255,7 +1228,8 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 			 * that for SQL MERGE, an exception must be raised in the event of
 			 * an attempt to update the same row twice.
 			 */
-			if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple.t_data)))
+			t_data = storage_tuple_get_data(relation, tuple, XMIN);
+			if (TransactionIdIsCurrentTransactionId(t_data.xid))
 				ereport(ERROR,
 						(errcode(ERRCODE_CARDINALITY_VIOLATION),
 						 errmsg("ON CONFLICT DO UPDATE command cannot affect row a second time"),
@@ -1286,7 +1260,9 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 			 * loop here, as the new version of the row might not conflict
 			 * anymore, or the conflicting tuple has actually been deleted.
 			 */
-			ReleaseBuffer(buffer);
+			if (BufferIsValid(buffer))
+				ReleaseBuffer(buffer);
+			pfree(tuple);
 			return false;
 
 		default:
@@ -1314,10 +1290,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, buffer);
 
 	/* Store target's existing tuple in the state's dedicated slot */
-	ExecStoreTuple(&tuple, mtstate->mt_existing, buffer, false);
+	ExecStoreTuple(tuple, mtstate->mt_existing, buffer, false);
 
 	/*
 	 * Make tuple and any needed join variables available to ExecQual and
@@ -1332,7 +1308,9 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 
 	if (!ExecQual(onConflictSetWhere, econtext))
 	{
-		ReleaseBuffer(buffer);
+		if (BufferIsValid(buffer))
+			ReleaseBuffer(buffer);
+		pfree(tuple);
 		InstrCountFiltered1(&mtstate->ps, 1);
 		return true;			/* done with the tuple */
 	}
@@ -1372,12 +1350,14 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	 */
 
 	/* Execute UPDATE with projection */
-	*returning = ExecUpdate(mtstate, &tuple.t_self, NULL,
+	*returning = ExecUpdate(mtstate, conflictTid, NULL,
 							mtstate->mt_conflproj, planSlot,
 							&mtstate->mt_epqstate, mtstate->ps.state,
 							canSetTag);
 
-	ReleaseBuffer(buffer);
+	if (BufferIsValid(buffer))
+		ReleaseBuffer(buffer);
+	pfree(tuple);
 	return true;
 }
 
@@ -1580,7 +1560,7 @@ ExecModifyTable(PlanState *pstate)
 	ItemPointer tupleid;
 	ItemPointerData tuple_ctid;
 	HeapTupleData oldtupdata;
-	HeapTuple	oldtuple;
+	StorageTuple oldtuple;
 
 	CHECK_FOR_INTERRUPTS();
 
diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c
index 0ee76e7d25..47d8b7b12f 100644
--- a/src/backend/executor/nodeTidscan.c
+++ b/src/backend/executor/nodeTidscan.c
@@ -22,6 +22,7 @@
  */
 #include "postgres.h"
 
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "catalog/pg_type.h"
 #include "executor/execdebug.h"
@@ -306,7 +307,7 @@ TidNext(TidScanState *node)
 	ScanDirection direction;
 	Snapshot	snapshot;
 	Relation	heapRelation;
-	HeapTuple	tuple;
+	StorageTuple tuple;
 	TupleTableSlot *slot;
 	Buffer		buffer = InvalidBuffer;
 	ItemPointerData *tidList;
@@ -331,12 +332,6 @@ TidNext(TidScanState *node)
 	tidList = node->tss_TidList;
 	numTids = node->tss_NumTids;
 
-	/*
-	 * We use node->tss_htup as the tuple pointer; note this can't just be a
-	 * local variable here, as the scan tuple slot will keep a pointer to it.
-	 */
-	tuple = &(node->tss_htup);
-
 	/*
 	 * Initialize or advance scan position, depending on direction.
 	 */
@@ -364,7 +359,7 @@ TidNext(TidScanState *node)
 
 	while (node->tss_TidPtr >= 0 && node->tss_TidPtr < numTids)
 	{
-		tuple->t_self = tidList[node->tss_TidPtr];
+		ItemPointerData tid = tidList[node->tss_TidPtr];
 
 		/*
 		 * For WHERE CURRENT OF, the tuple retrieved from the cursor might
@@ -372,9 +367,9 @@ TidNext(TidScanState *node)
 		 * current according to our snapshot.
 		 */
 		if (node->tss_isCurrentOf)
-			heap_get_latest_tid(heapRelation, snapshot, &tuple->t_self);
+			storage_get_latest_tid(heapRelation, snapshot, &tid);
 
-		if (heap_fetch(heapRelation, snapshot, tuple, &buffer, false, NULL))
+		if (storage_fetch(heapRelation, &tid, snapshot, &tuple, &buffer, false, NULL))
 		{
 			/*
 			 * store the scanned tuple in the scan tuple slot of the scan
@@ -385,14 +380,16 @@ TidNext(TidScanState *node)
 			 */
 			ExecStoreTuple(tuple,	/* tuple to store */
 						   slot,	/* slot to store in */
-						   buffer,	/* buffer associated with tuple  */
-						   false);	/* don't pfree */
+						   InvalidBuffer,	/* buffer associated with tuple  */
+						   true);	/* don't pfree */
 
 			/*
 			 * At this point we have an extra pin on the buffer, because
 			 * ExecStoreTuple incremented the pin count. Drop our local pin.
 			 */
-			ReleaseBuffer(buffer);
+			/* hari */
+			if (BufferIsValid(buffer))
+				ReleaseBuffer(buffer);
 
 			return slot;
 		}
diff --git a/src/backend/utils/adt/tid.c b/src/backend/utils/adt/tid.c
index 854097dd58..c4481ecee9 100644
--- a/src/backend/utils/adt/tid.c
+++ b/src/backend/utils/adt/tid.c
@@ -21,6 +21,7 @@
 #include <limits.h>
 
 #include "access/heapam.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_type.h"
@@ -352,7 +353,7 @@ currtid_byreloid(PG_FUNCTION_ARGS)
 	ItemPointerCopy(tid, result);
 
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	heap_get_latest_tid(rel, snapshot, result);
+	storage_get_latest_tid(rel, snapshot, result);
 	UnregisterSnapshot(snapshot);
 
 	heap_close(rel, AccessShareLock);
@@ -387,7 +388,7 @@ currtid_byrelname(PG_FUNCTION_ARGS)
 	ItemPointerCopy(tid, result);
 
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	heap_get_latest_tid(rel, snapshot, result);
+	storage_get_latest_tid(rel, snapshot, result);
 	UnregisterSnapshot(snapshot);
 
 	heap_close(rel, AccessShareLock);
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 0ba2ec5894..94ab6e6aa1 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -134,7 +134,7 @@ extern void heap_parallelscan_initialize(ParallelHeapScanDesc target,
 extern void heap_parallelscan_reinitialize(ParallelHeapScanDesc parallel_scan);
 extern HeapScanDesc heap_beginscan_parallel(Relation, ParallelHeapScanDesc);
 
-extern bool heap_fetch(Relation relation, Snapshot snapshot,
+extern bool heap_fetch(Relation relation, ItemPointer tid, Snapshot snapshot,
 		   HeapTuple tuple, Buffer *userbuf, bool keep_buf,
 		   Relation stats_relation);
 extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation,
@@ -142,7 +142,6 @@ extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation,
 					   bool *all_dead, bool first_call);
 extern bool heap_hot_search(ItemPointer tid, Relation relation,
 				Snapshot snapshot, bool *all_dead);
-
 extern void heap_get_latest_tid(Relation relation, Snapshot snapshot,
 					ItemPointer tid);
 extern void setLastTid(const ItemPointer tid);
@@ -158,16 +157,17 @@ extern void heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 extern HTSU_Result heap_delete(Relation relation, ItemPointer tid,
 			CommandId cid, Snapshot crosscheck, bool wait,
 			HeapUpdateFailureData *hufd);
-extern void heap_finish_speculative(Relation relation, HeapTuple tuple);
-extern void heap_abort_speculative(Relation relation, HeapTuple tuple);
+extern void heap_finish_speculative(Relation relation, TupleTableSlot *slot);
+extern void heap_abort_speculative(Relation relation, TupleTableSlot *slot);
 extern HTSU_Result heap_update(Relation relation, ItemPointer otid,
 			HeapTuple newtup,
 			CommandId cid, Snapshot crosscheck, bool wait,
 			HeapUpdateFailureData *hufd, LockTupleMode *lockmode);
-extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
+extern HTSU_Result heap_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * tuple,
 				CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 				bool follow_update,
 				Buffer *buffer, HeapUpdateFailureData *hufd);
+
 extern void heap_inplace_update(Relation relation, HeapTuple tuple);
 extern bool heap_freeze_tuple(HeapTupleHeader tuple,
 				  TransactionId relfrozenxid, TransactionId relminmxid,
@@ -184,6 +184,10 @@ extern void simple_heap_update(Relation relation, ItemPointer otid,
 extern void heap_sync(Relation relation);
 extern void heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot);
 
+/* in heap/heapam_visibility.c */
+extern HTSU_Result HeapTupleSatisfiesUpdate(StorageTuple stup, CommandId curcid,
+						 Buffer buffer);
+
 /* in heap/pruneheap.c */
 extern void heap_page_prune_opt(Relation relation, Buffer buffer);
 extern int heap_page_prune(Relation relation, Buffer buffer,
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index 168edb058d..489aa78731 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -816,6 +816,7 @@ extern Datum heap_getsysattr(HeapTuple tup, int attnum, TupleDesc tupleDesc,
 extern HeapTuple heap_copytuple(HeapTuple tuple);
 extern void heap_copytuple_with_tuple(HeapTuple src, HeapTuple dest);
 extern Datum heap_copy_tuple_as_datum(HeapTuple tuple, TupleDesc tupleDesc);
+extern HeapTuple heap_form_tuple_by_datum(Datum data, Oid relid);
 extern HeapTuple heap_form_tuple(TupleDesc tupleDescriptor,
 				Datum *values, bool *isnull);
 extern HeapTuple heap_modify_tuple(HeapTuple tuple,
diff --git a/src/include/access/storage_common.h b/src/include/access/storage_common.h
index e38555cd0b..0c43ae4310 100644
--- a/src/include/access/storage_common.h
+++ b/src/include/access/storage_common.h
@@ -45,9 +45,6 @@ typedef MinimalTuple (*SlotGetMinTuple_function) (TupleTableSlot *slot, bool pal
 
 typedef void (*SlotUpdateTableoid_function) (TupleTableSlot *slot, Oid tableoid);
 
-typedef void (*SpeculativeAbort_function) (Relation rel,
-										   TupleTableSlot *slot);
-
 typedef struct StorageSlotAmRoutine
 {
 	/* Operations on TupleTableSlot */
@@ -77,8 +74,6 @@ typedef enum
 
 
 /* in storage/storage_common.c */
-extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
-					 uint16 infomask, TransactionId xid);
 extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
 extern bool HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin);
 extern bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
diff --git a/src/include/access/storageam.h b/src/include/access/storageam.h
new file mode 100644
index 0000000000..542e44629e
--- /dev/null
+++ b/src/include/access/storageam.h
@@ -0,0 +1,61 @@
+/*-------------------------------------------------------------------------
+ *
+ * storageam.h
+ *	  POSTGRES storage access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/storageam.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGEAM_H
+#define STORAGEAM_H
+
+#include "access/heapam.h"
+#include "access/storageamapi.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
+
+extern bool storage_fetch(Relation relation,
+			  ItemPointer tid,
+			  Snapshot snapshot,
+			  StorageTuple * stuple,
+			  Buffer *userbuf,
+			  bool keep_buf,
+			  Relation stats_relation);
+
+extern HTSU_Result storage_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * stuple,
+				   CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
+				   bool follow_updates,
+				   Buffer *buffer, HeapUpdateFailureData *hufd);
+
+extern Oid storage_insert(Relation relation, TupleTableSlot *slot, CommandId cid,
+			   int options, BulkInsertState bistate, InsertIndexTuples IndexFunc,
+			   EState *estate, List *arbiterIndexes, List **recheckIndexes);
+
+extern HTSU_Result storage_delete(Relation relation, ItemPointer tid, CommandId cid,
+			   Snapshot crosscheck, bool wait,
+			   HeapUpdateFailureData *hufd);
+
+extern HTSU_Result storage_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
+			   EState *estate, CommandId cid, Snapshot crosscheck, bool wait,
+			   HeapUpdateFailureData *hufd, LockTupleMode *lockmode,
+			   InsertIndexTuples IndexFunc, List **recheckIndexes);
+
+extern void storage_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
+					 CommandId cid, int options, BulkInsertState bistate);
+
+extern tuple_data storage_tuple_get_data(Relation relation, StorageTuple tuple, tuple_data_flags flags);
+
+extern StorageTuple storage_tuple_by_datum(Relation relation, Datum data, Oid tableoid);
+
+extern void storage_get_latest_tid(Relation relation,
+					   Snapshot snapshot,
+					   ItemPointer tid);
+
+extern void storage_sync(Relation rel);
+
+#endif
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
index 7df51c4167..ed458ce5c3 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -11,11 +11,33 @@
 #ifndef STORAGEAMAPI_H
 #define STORAGEAMAPI_H
 
+#include "access/heapam.h"
 #include "access/storage_common.h"
+#include "nodes/execnodes.h"
 #include "nodes/nodes.h"
 #include "utils/snapshot.h"
 #include "fmgr.h"
 
+typedef union tuple_data
+{
+	TransactionId xid;
+	CommandId	cid;
+	ItemPointerData tid;
+}			tuple_data;
+
+typedef enum tuple_data_flags
+{
+	XMIN = 0,
+	UPDATED_XID,
+	CMIN,
+	TID,
+	CTID
+}			tuple_data_flags;
+
+/* Function pointer to let the index tuple insert from storage am */
+typedef List *(*InsertIndexTuples) (TupleTableSlot *slot, EState *estate, bool noDupErr,
+									bool *specConflict, List *arbiterIndexes);
+
 
 /*
  * Storage routine function hooks
@@ -25,6 +47,60 @@ typedef HTSU_Result (*SnapshotSatisfiesUpdate_function) (StorageTuple htup, Comm
 typedef HTSV_Result (*SnapshotSatisfiesVacuum_function) (StorageTuple htup, TransactionId OldestXmin, Buffer buffer);
 
 
+typedef Oid (*TupleInsert_function) (Relation rel, TupleTableSlot *slot, CommandId cid,
+									 int options, BulkInsertState bistate, InsertIndexTuples IndexFunc,
+									 EState *estate, List *arbiterIndexes, List **recheckIndexes);
+
+typedef HTSU_Result (*TupleDelete_function) (Relation relation,
+											 ItemPointer tid,
+											 CommandId cid,
+											 Snapshot crosscheck,
+											 bool wait,
+											 HeapUpdateFailureData *hufd);
+
+typedef HTSU_Result (*TupleUpdate_function) (Relation relation,
+											 ItemPointer otid,
+											 TupleTableSlot *slot,
+											 EState *estate,
+											 CommandId cid,
+											 Snapshot crosscheck,
+											 bool wait,
+											 HeapUpdateFailureData *hufd,
+											 LockTupleMode *lockmode,
+											 InsertIndexTuples IndexFunc,
+											 List **recheckIndexes);
+
+typedef bool (*TupleFetch_function) (Relation relation,
+									 ItemPointer tid,
+									 Snapshot snapshot,
+									 StorageTuple * tuple,
+									 Buffer *userbuf,
+									 bool keep_buf,
+									 Relation stats_relation);
+
+typedef HTSU_Result (*TupleLock_function) (Relation relation,
+										   ItemPointer tid,
+										   StorageTuple * tuple,
+										   CommandId cid,
+										   LockTupleMode mode,
+										   LockWaitPolicy wait_policy,
+										   bool follow_update,
+										   Buffer *buffer,
+										   HeapUpdateFailureData *hufd);
+
+typedef void (*MultiInsert_function) (Relation relation, HeapTuple *tuples, int ntuples,
+									  CommandId cid, int options, BulkInsertState bistate);
+
+typedef void (*TupleGetLatestTid_function) (Relation relation,
+											Snapshot snapshot,
+											ItemPointer tid);
+
+typedef tuple_data(*GetTupleData_function) (StorageTuple tuple, tuple_data_flags flags);
+
+typedef StorageTuple(*TupleFromDatum_function) (Datum data, Oid tableoid);
+
+typedef void (*RelationSync_function) (Relation relation);
+
 /*
  * API struct for a storage AM.  Note this must be stored in a single palloc'd
  * chunk of memory.
@@ -44,6 +120,20 @@ typedef struct StorageAmRoutine
 
 	slot_storageam_hook slot_storageam;
 
+	/* Operations on physical tuples */
+	TupleInsert_function tuple_insert;	/* heap_insert */
+	TupleUpdate_function tuple_update;	/* heap_update */
+	TupleDelete_function tuple_delete;	/* heap_delete */
+	TupleFetch_function tuple_fetch;	/* heap_fetch */
+	TupleLock_function tuple_lock;	/* heap_lock_tuple */
+	MultiInsert_function multi_insert;	/* heap_multi_insert */
+	TupleGetLatestTid_function tuple_get_latest_tid;	/* heap_get_latest_tid */
+
+	GetTupleData_function get_tuple_data;
+	TupleFromDatum_function tuple_from_datum;
+
+	RelationSync_function relation_sync;	/* heap_sync */
+
 }			StorageAmRoutine;
 
 extern StorageAmRoutine * GetStorageAmRoutine(Oid amhandler);
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index adbcfa1297..203371148c 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -190,7 +190,7 @@ extern TupleTableSlot *ExecBRInsertTriggers(EState *estate,
 					 TupleTableSlot *slot);
 extern void ExecARInsertTriggers(EState *estate,
 					 ResultRelInfo *relinfo,
-					 HeapTuple trigtuple,
+					 TupleTableSlot *slot,
 					 List *recheckIndexes,
 					 TransitionCaptureState *transition_capture);
 extern TupleTableSlot *ExecIRInsertTriggers(EState *estate,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 2cc74da0ba..cea687d328 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -198,16 +198,16 @@ 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 HeapTuple EvalPlanQualFetch(EState *estate, Relation relation,
-				  int lockmode, LockWaitPolicy wait_policy, ItemPointer tid,
-				  TransactionId priorXmax);
+extern StorageTuple EvalPlanQualFetch(EState *estate, Relation relation,
+									  int lockmode, LockWaitPolicy wait_policy, ItemPointer tid,
+									  TransactionId priorXmax);
 extern void EvalPlanQualInit(EPQState *epqstate, EState *estate,
 				 Plan *subplan, List *auxrowmarks, int epqParam);
 extern void EvalPlanQualSetPlan(EPQState *epqstate,
 					Plan *subplan, List *auxrowmarks);
 extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
-					 HeapTuple tuple);
-extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+					 StorageTuple tuple);
+extern StorageTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
 
 #define EvalPlanQualSetSlot(epqstate, slot)  ((epqstate)->origslot = (slot))
 extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
@@ -522,9 +522,8 @@ extern int	ExecCleanTargetListLength(List *targetlist);
  */
 extern void ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative);
 extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
-extern List *ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid,
-					  EState *estate, bool noDupErr, bool *specConflict,
-					  List *arbiterIndexes);
+extern List *ExecInsertIndexTuples(TupleTableSlot *slot, EState *estate, bool noDupErr,
+					  bool *specConflict, List *arbiterIndexes);
 extern bool ExecCheckIndexConstraints(TupleTableSlot *slot, EState *estate,
 						  ItemPointer conflictTid, List *arbiterIndexes);
 extern void check_exclusion_constraint(Relation heap, Relation index,
diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h
index e6ff66f14b..7839d5e9b7 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -133,6 +133,7 @@ typedef struct TupleTableSlot
 	Oid			tts_tupleOid;	/* XXX describe */
 	int			tts_nvalid;		/* # of valid values in tts_values */
 	uint32		tts_speculativeToken;	/* XXX describe */
+	bool		tts_specConflict;	/* XXX describe */
 	bool		tts_shouldFree;
 	bool		tts_shouldFreeMin;
 	Datum	   *tts_values;		/* current per-attribute values */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index c9a5279dc5..75ca1543fc 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -512,7 +512,7 @@ typedef struct EState
 	 * remember if the tuple has been returned already.  Arrays are of size
 	 * list_length(es_range_table) and are indexed by scan node scanrelid - 1.
 	 */
-	HeapTuple  *es_epqTuple;	/* array of EPQ substitute tuples */
+	StorageTuple *es_epqTuple;	/* array of EPQ substitute tuples */
 	bool	   *es_epqTupleSet; /* true if EPQ tuple is provided */
 	bool	   *es_epqScanDone; /* true if EPQ tuple has been fetched */
 
@@ -2007,7 +2007,7 @@ typedef struct HashInstrumentation
 	int			nbatch;			/* number of batches at end of execution */
 	int			nbatch_original;	/* planned number of batches */
 	size_t		space_peak;		/* speak memory usage in bytes */
-} HashInstrumentation;
+}			HashInstrumentation;
 
 /* ----------------
  *	 Shared memory container for per-worker hash information
@@ -2017,7 +2017,7 @@ typedef struct SharedHashInfo
 {
 	int			num_workers;
 	HashInstrumentation hinstrument[FLEXIBLE_ARRAY_MEMBER];
-} SharedHashInfo;
+}			SharedHashInfo;
 
 /* ----------------
  *	 HashState information
@@ -2078,7 +2078,7 @@ typedef struct LockRowsState
 	PlanState	ps;				/* its first field is NodeTag */
 	List	   *lr_arowMarks;	/* List of ExecAuxRowMarks */
 	EPQState	lr_epqstate;	/* for evaluating EvalPlanQual rechecks */
-	HeapTuple  *lr_curtuples;	/* locked tuples (one entry per RT entry) */
+	StorageTuple *lr_curtuples; /* locked tuples (one entry per RT entry) */
 	int			lr_ntables;		/* length of lr_curtuples[] array */
 } LockRowsState;
 
-- 
2.15.0.windows.1



  [application/octet-stream] 0007-Scan-functions-are-added-to-storage-AM.patch (105.3K, ../../CAJrrPGc951F-R4Kfa4W47B5vHKeHsB2Y34zewp=b+AWSkF9RVA@mail.gmail.com/11-0007-Scan-functions-are-added-to-storage-AM.patch)
  download | inline diff:
From ae2e9564a60334ac004838db7ad06b57eb57f1a1 Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Wed, 27 Dec 2017 12:59:25 +1100
Subject: [PATCH 07/11] Scan functions are added to storage AM

All the scan functions that are present
in heapam module are moved into heapm_storage
and corresponding function hooks are added.

Replaced HeapTuple with StorageTuple whereever
possible.

Currently directly returning slot functionality
instead of tuple is added only to limited number
of places.
---
 contrib/pgrowlocks/pgrowlocks.c            |   6 +-
 contrib/pgstattuple/pgstattuple.c          |   6 +-
 src/backend/access/heap/heapam.c           | 212 ++++++++++-----------------
 src/backend/access/heap/heapam_storage.c   |  11 +-
 src/backend/access/index/genam.c           |  11 +-
 src/backend/access/index/indexam.c         |  13 +-
 src/backend/access/nbtree/nbtinsert.c      |   7 +-
 src/backend/access/storage/storageam.c     | 223 +++++++++++++++++++++++++++++
 src/backend/bootstrap/bootstrap.c          |  25 ++--
 src/backend/catalog/aclchk.c               |  13 +-
 src/backend/catalog/index.c                |  59 ++++----
 src/backend/catalog/partition.c            |   7 +-
 src/backend/catalog/pg_conversion.c        |   7 +-
 src/backend/catalog/pg_db_role_setting.c   |   7 +-
 src/backend/catalog/pg_publication.c       |   7 +-
 src/backend/catalog/pg_subscription.c      |   7 +-
 src/backend/commands/cluster.c             |  13 +-
 src/backend/commands/constraint.c          |   3 +-
 src/backend/commands/copy.c                |   6 +-
 src/backend/commands/dbcommands.c          |  19 +--
 src/backend/commands/indexcmds.c           |   7 +-
 src/backend/commands/tablecmds.c           |  30 ++--
 src/backend/commands/tablespace.c          |  39 ++---
 src/backend/commands/typecmds.c            |  13 +-
 src/backend/commands/vacuum.c              |  13 +-
 src/backend/executor/execAmi.c             |   2 +-
 src/backend/executor/execIndexing.c        |  13 +-
 src/backend/executor/execReplication.c     |  15 +-
 src/backend/executor/execTuples.c          |   8 +-
 src/backend/executor/functions.c           |   4 +-
 src/backend/executor/nodeAgg.c             |   4 +-
 src/backend/executor/nodeBitmapHeapscan.c  |  19 +--
 src/backend/executor/nodeForeignscan.c     |   6 +-
 src/backend/executor/nodeGather.c          |   8 +-
 src/backend/executor/nodeGatherMerge.c     |  14 +-
 src/backend/executor/nodeIndexonlyscan.c   |   8 +-
 src/backend/executor/nodeIndexscan.c       |  16 +--
 src/backend/executor/nodeSamplescan.c      |  34 ++---
 src/backend/executor/nodeSeqscan.c         |  45 ++----
 src/backend/executor/nodeWindowAgg.c       |   4 +-
 src/backend/executor/spi.c                 |  20 +--
 src/backend/executor/tqueue.c              |   2 +-
 src/backend/postmaster/autovacuum.c        |  18 +--
 src/backend/postmaster/pgstat.c            |   7 +-
 src/backend/replication/logical/launcher.c |   7 +-
 src/backend/rewrite/rewriteDefine.c        |   7 +-
 src/backend/utils/init/postinit.c          |   7 +-
 src/include/access/heapam.h                |  27 ++--
 src/include/access/storage_common.h        |   1 +
 src/include/access/storageam.h             |  58 +++++++-
 src/include/access/storageamapi.h          |  67 ++++++---
 src/include/executor/functions.h           |   2 +-
 src/include/executor/spi.h                 |  12 +-
 src/include/executor/tqueue.h              |   4 +-
 src/include/funcapi.h                      |   2 +-
 55 files changed, 735 insertions(+), 470 deletions(-)

diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index 830e74fd07..bc8b423975 100644
--- a/contrib/pgrowlocks/pgrowlocks.c
+++ b/contrib/pgrowlocks/pgrowlocks.c
@@ -125,7 +125,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
 			aclcheck_error(aclresult, ACL_KIND_CLASS,
 						   RelationGetRelationName(rel));
 
-		scan = heap_beginscan(rel, GetActiveSnapshot(), 0, NULL);
+		scan = storage_beginscan(rel, GetActiveSnapshot(), 0, NULL);
 		mydata = palloc(sizeof(*mydata));
 		mydata->rel = rel;
 		mydata->scan = scan;
@@ -141,7 +141,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
 	scan = mydata->scan;
 
 	/* scan the relation */
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		HTSU_Result htsu;
 		TransactionId xmax;
@@ -306,7 +306,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
 		}
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(mydata->rel, AccessShareLock);
 
 	SRF_RETURN_DONE(funcctx);
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index e098202f84..c4b10d6efc 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -325,13 +325,13 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 	StorageAmRoutine *method = rel->rd_stamroutine;
 
 	/* Disable syncscan because we assume we scan from block zero upwards */
-	scan = heap_beginscan_strat(rel, SnapshotAny, 0, NULL, true, false);
+	scan = storage_beginscan_strat(rel, SnapshotAny, 0, NULL, true, false);
 	InitDirtySnapshot(SnapshotDirty);
 
 	nblocks = scan->rs_nblocks; /* # blocks to be scanned */
 
 	/* scan the relation */
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		CHECK_FOR_INTERRUPTS();
 
@@ -384,7 +384,7 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 		block++;
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	relation_close(rel, AccessShareLock);
 
 	stat.table_len = (uint64) nblocks * BLCKSZ;
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 8da5720e57..5226f39f1e 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -80,17 +80,6 @@
 /* GUC variable */
 bool		synchronize_seqscans = true;
 
-
-static HeapScanDesc heap_beginscan_internal(Relation relation,
-						Snapshot snapshot,
-						int nkeys, ScanKey key,
-						ParallelHeapScanDesc parallel_scan,
-						bool allow_strat,
-						bool allow_sync,
-						bool allow_pagemode,
-						bool is_bitmapscan,
-						bool is_samplescan,
-						bool temp_snap);
 static void heap_parallelscan_startblock_init(HeapScanDesc scan);
 static BlockNumber heap_parallelscan_nextpage(HeapScanDesc scan);
 static HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup,
@@ -1387,87 +1376,16 @@ heap_openrv_extended(const RangeVar *relation, LOCKMODE lockmode,
 	return r;
 }
 
-
-/* ----------------
- *		heap_beginscan	- begin relation scan
- *
- * heap_beginscan is the "standard" case.
- *
- * heap_beginscan_catalog differs in setting up its own temporary snapshot.
- *
- * heap_beginscan_strat offers an extended API that lets the caller control
- * whether a nondefault buffer access strategy can be used, and whether
- * syncscan can be chosen (possibly resulting in the scan not starting from
- * block zero).  Both of these default to true with plain heap_beginscan.
- *
- * heap_beginscan_bm is an alternative entry point for setting up a
- * HeapScanDesc for a bitmap heap scan.  Although that scan technology is
- * really quite unlike a standard seqscan, there is just enough commonality
- * to make it worth using the same data structure.
- *
- * heap_beginscan_sampling is an alternative entry point for setting up a
- * HeapScanDesc for a TABLESAMPLE scan.  As with bitmap scans, it's worth
- * using the same data structure although the behavior is rather different.
- * In addition to the options offered by heap_beginscan_strat, this call
- * also allows control of whether page-mode visibility checking is used.
- * ----------------
- */
 HeapScanDesc
 heap_beginscan(Relation relation, Snapshot snapshot,
-			   int nkeys, ScanKey key)
-{
-	return heap_beginscan_internal(relation, snapshot, nkeys, key, NULL,
-								   true, true, true, false, false, false);
-}
-
-HeapScanDesc
-heap_beginscan_catalog(Relation relation, int nkeys, ScanKey key)
-{
-	Oid			relid = RelationGetRelid(relation);
-	Snapshot	snapshot = RegisterSnapshot(GetCatalogSnapshot(relid));
-
-	return heap_beginscan_internal(relation, snapshot, nkeys, key, NULL,
-								   true, true, true, false, false, true);
-}
-
-HeapScanDesc
-heap_beginscan_strat(Relation relation, Snapshot snapshot,
-					 int nkeys, ScanKey key,
-					 bool allow_strat, bool allow_sync)
-{
-	return heap_beginscan_internal(relation, snapshot, nkeys, key, NULL,
-								   allow_strat, allow_sync, true,
-								   false, false, false);
-}
-
-HeapScanDesc
-heap_beginscan_bm(Relation relation, Snapshot snapshot,
-				  int nkeys, ScanKey key)
-{
-	return heap_beginscan_internal(relation, snapshot, nkeys, key, NULL,
-								   false, false, true, true, false, false);
-}
-
-HeapScanDesc
-heap_beginscan_sampling(Relation relation, Snapshot snapshot,
-						int nkeys, ScanKey key,
-						bool allow_strat, bool allow_sync, bool allow_pagemode)
-{
-	return heap_beginscan_internal(relation, snapshot, nkeys, key, NULL,
-								   allow_strat, allow_sync, allow_pagemode,
-								   false, true, false);
-}
-
-static HeapScanDesc
-heap_beginscan_internal(Relation relation, Snapshot snapshot,
-						int nkeys, ScanKey key,
-						ParallelHeapScanDesc parallel_scan,
-						bool allow_strat,
-						bool allow_sync,
-						bool allow_pagemode,
-						bool is_bitmapscan,
-						bool is_samplescan,
-						bool temp_snap)
+			   int nkeys, ScanKey key,
+			   ParallelHeapScanDesc parallel_scan,
+			   bool allow_strat,
+			   bool allow_sync,
+			   bool allow_pagemode,
+			   bool is_bitmapscan,
+			   bool is_samplescan,
+			   bool temp_snap)
 {
 	HeapScanDesc scan;
 
@@ -1537,9 +1455,16 @@ heap_beginscan_internal(Relation relation, Snapshot snapshot,
  * ----------------
  */
 void
-heap_rescan(HeapScanDesc scan,
-			ScanKey key)
+heap_rescan(HeapScanDesc scan, ScanKey key, bool set_params,
+			bool allow_strat, bool allow_sync, bool allow_pagemode)
 {
+	if (set_params)
+	{
+		scan->rs_allow_strat = allow_strat;
+		scan->rs_allow_sync = allow_sync;
+		scan->rs_pageatatime = allow_pagemode && IsMVCCSnapshot(scan->rs_snapshot);
+	}
+
 	/*
 	 * unpin scan buffers
 	 */
@@ -1550,27 +1475,21 @@ heap_rescan(HeapScanDesc scan,
 	 * reinitialize scan descriptor
 	 */
 	initscan(scan, key, true);
-}
 
-/* ----------------
- *		heap_rescan_set_params	- restart a relation scan after changing params
- *
- * This call allows changing the buffer strategy, syncscan, and pagemode
- * options before starting a fresh scan.  Note that although the actual use
- * of syncscan might change (effectively, enabling or disabling reporting),
- * the previously selected startblock will be kept.
- * ----------------
- */
-void
-heap_rescan_set_params(HeapScanDesc scan, ScanKey key,
-					   bool allow_strat, bool allow_sync, bool allow_pagemode)
-{
-	/* adjust parameters */
-	scan->rs_allow_strat = allow_strat;
-	scan->rs_allow_sync = allow_sync;
-	scan->rs_pageatatime = allow_pagemode && IsMVCCSnapshot(scan->rs_snapshot);
-	/* ... and rescan */
-	heap_rescan(scan, key);
+	/*
+	 * reset parallel scan, if present
+	 */
+	if (scan->rs_parallel != NULL)
+	{
+		ParallelHeapScanDesc parallel_scan;
+
+		/*
+		 * Caller is responsible for making sure that all workers have
+		 * finished the scan before calling this.
+		 */
+		parallel_scan = scan->rs_parallel;
+		pg_atomic_write_u64(&parallel_scan->phs_nallocated, 0);
+	}
 }
 
 /* ----------------
@@ -1659,25 +1578,6 @@ heap_parallelscan_reinitialize(ParallelHeapScanDesc parallel_scan)
 	pg_atomic_write_u64(&parallel_scan->phs_nallocated, 0);
 }
 
-/* ----------------
- *		heap_beginscan_parallel - join a parallel scan
- *
- *		Caller must hold a suitable lock on the correct relation.
- * ----------------
- */
-HeapScanDesc
-heap_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan)
-{
-	Snapshot	snapshot;
-
-	Assert(RelationGetRelid(relation) == parallel_scan->phs_relid);
-	snapshot = RestoreSnapshot(parallel_scan->phs_snapshot_data);
-	RegisterSnapshot(snapshot);
-
-	return heap_beginscan_internal(relation, snapshot, 0, NULL, parallel_scan,
-								   true, true, true, false, false, true);
-}
-
 /* ----------------
  *		heap_parallelscan_startblock_init - find and set the scan's startblock
  *
@@ -1822,8 +1722,7 @@ heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
 #define HEAPDEBUG_3
 #endif							/* !defined(HEAPDEBUGALL) */
 
-
-HeapTuple
+StorageTuple
 heap_getnext(HeapScanDesc scan, ScanDirection direction)
 {
 	/* Note: no locking manipulations needed */
@@ -1853,6 +1752,53 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 	return heap_copytuple(&(scan->rs_ctup));
 }
 
+#ifdef HEAPAMSLOTDEBUGALL
+#define HEAPAMSLOTDEBUG_1 \
+	elog(DEBUG2, "heapam_getnext([%s,nkeys=%d],dir=%d) called", \
+		 RelationGetRelationName(scan->rs_rd), scan->rs_nkeys, (int) direction)
+#define HEAPAMSLOTDEBUG_2 \
+	elog(DEBUG2, "heapam_getnext returning EOS")
+#define HEAPAMSLOTDEBUG_3 \
+	elog(DEBUG2, "heapam_getnext returning tuple")
+#else
+#define HEAPAMSLOTDEBUG_1
+#define HEAPAMSLOTDEBUG_2
+#define HEAPAMSLOTDEBUG_3
+#endif
+
+TupleTableSlot *
+heap_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
+{
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
+	/* Note: no locking manipulations needed */
+
+	HEAPAMSLOTDEBUG_1;			/* heap_getnext( info ) */
+
+	if (scan->rs_pageatatime)
+		heapgettup_pagemode(scan, direction,
+							scan->rs_nkeys, scan->rs_key);
+	else
+		heapgettup(scan, direction, scan->rs_nkeys, scan->rs_key);
+
+	if (scan->rs_ctup.t_data == NULL)
+	{
+		HEAPAMSLOTDEBUG_2;		/* heap_getnext returning EOS */
+		ExecClearTuple(slot);
+		return slot;
+	}
+
+	/*
+	 * if we get here it means we have a new current scan tuple, so point to
+	 * the proper return buffer and return the tuple.
+	 */
+	HEAPAMSLOTDEBUG_3;			/* heap_getnext returning tuple */
+
+	pgstat_count_heap_getnext(scan->rs_rd);
+	return ExecStoreTuple(heap_copytuple(&(scan->rs_ctup)),
+						  slot, InvalidBuffer, true);
+}
+
 /*
  *	heap_fetch		- retrieve tuple with given tid
  *
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index d85ebed274..e7b669a2dd 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -144,8 +144,6 @@ heapam_heap_delete(Relation relation, ItemPointer tid, CommandId cid,
 	return heap_delete(relation, tid, cid, crosscheck, wait, hufd);
 }
 
-
-
 static HTSU_Result
 heapam_heap_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 				   EState *estate, CommandId cid, Snapshot crosscheck,
@@ -255,6 +253,15 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 
 	amroutine->slot_storageam = heapam_storage_slot_handler;
 
+	amroutine->scan_begin = heap_beginscan;
+	amroutine->scansetlimits = heap_setscanlimits;
+	amroutine->scan_getnext = heap_getnext;
+	amroutine->scan_getnextslot = heap_getnextslot;
+	amroutine->scan_end = heap_endscan;
+	amroutine->scan_rescan = heap_rescan;
+	amroutine->scan_update_snapshot = heap_update_snapshot;
+	amroutine->hot_search_buffer = heap_hot_search_buffer;
+
 	amroutine->tuple_fetch = heapam_fetch;
 	amroutine->tuple_insert = heapam_heap_insert;
 	amroutine->tuple_delete = heapam_heap_delete;
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 01321a2543..26a9ccb657 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -20,6 +20,7 @@
 #include "postgres.h"
 
 #include "access/relscan.h"
+#include "access/storageam.h"
 #include "access/transam.h"
 #include "catalog/index.h"
 #include "lib/stringinfo.h"
@@ -394,9 +395,9 @@ systable_beginscan(Relation heapRelation,
 		 * disadvantage; and there are no compensating advantages, because
 		 * it's unlikely that such scans will occur in parallel.
 		 */
-		sysscan->scan = heap_beginscan_strat(heapRelation, snapshot,
-											 nkeys, key,
-											 true, false);
+		sysscan->scan = storage_beginscan_strat(heapRelation, snapshot,
+												nkeys, key,
+												true, false);
 		sysscan->iscan = NULL;
 	}
 
@@ -432,7 +433,7 @@ systable_getnext(SysScanDesc sysscan)
 			elog(ERROR, "system catalog scans with lossy index conditions are not implemented");
 	}
 	else
-		htup = heap_getnext(sysscan->scan, ForwardScanDirection);
+		htup = storage_getnext(sysscan->scan, ForwardScanDirection);
 
 	return htup;
 }
@@ -504,7 +505,7 @@ systable_endscan(SysScanDesc sysscan)
 		index_close(sysscan->irel, AccessShareLock);
 	}
 	else
-		heap_endscan(sysscan->scan);
+		storage_endscan(sysscan->scan);
 
 	if (sysscan->snapshot)
 		UnregisterSnapshot(sysscan->snapshot);
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index edf4172eb2..0afc00625b 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -71,6 +71,7 @@
 
 #include "access/amapi.h"
 #include "access/relscan.h"
+#include "access/storageam.h"
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "catalog/catalog.h"
@@ -605,12 +606,12 @@ index_fetch_heap(IndexScanDesc scan)
 
 	/* Obtain share-lock on the buffer so we can examine visibility */
 	LockBuffer(scan->xs_cbuf, BUFFER_LOCK_SHARE);
-	got_heap_tuple = heap_hot_search_buffer(tid, scan->heapRelation,
-											scan->xs_cbuf,
-											scan->xs_snapshot,
-											&scan->xs_ctup,
-											&all_dead,
-											!scan->xs_continue_hot);
+	got_heap_tuple = storage_hot_search_buffer(tid, scan->heapRelation,
+											   scan->xs_cbuf,
+											   scan->xs_snapshot,
+											   &scan->xs_ctup,
+											   &all_dead,
+											   !scan->xs_continue_hot);
 	LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK);
 
 	if (got_heap_tuple)
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index 310589da4e..0368a10dbc 100644
--- a/src/backend/access/nbtree/nbtinsert.c
+++ b/src/backend/access/nbtree/nbtinsert.c
@@ -18,6 +18,7 @@
 #include "access/heapam.h"
 #include "access/nbtree.h"
 #include "access/nbtxlog.h"
+#include "access/storageam.h"
 #include "access/transam.h"
 #include "access/xloginsert.h"
 #include "miscadmin.h"
@@ -325,8 +326,8 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
 				 * that satisfies SnapshotDirty.  This is necessary because we
 				 * have just a single index entry for the entire chain.
 				 */
-				else if (heap_hot_search(&htid, heapRel, &SnapshotDirty,
-										 &all_dead))
+				else if (storage_hot_search(&htid, heapRel, &SnapshotDirty,
+											&all_dead))
 				{
 					TransactionId xwait;
 
@@ -379,7 +380,7 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
 					 * entry.
 					 */
 					htid = itup->t_tid;
-					if (heap_hot_search(&htid, heapRel, SnapshotSelf, NULL))
+					if (storage_hot_search(&htid, heapRel, SnapshotSelf, NULL))
 					{
 						/* Normal case --- it's still live */
 					}
diff --git a/src/backend/access/storage/storageam.c b/src/backend/access/storage/storageam.c
index 9b2215effa..75e69db6ab 100644
--- a/src/backend/access/storage/storageam.c
+++ b/src/backend/access/storage/storageam.c
@@ -16,7 +16,9 @@
 
 #include "access/storageam.h"
 #include "access/storageamapi.h"
+#include "access/relscan.h"
 #include "utils/rel.h"
+#include "utils/tqual.h"
 
 /*
  *	storage_fetch		- retrieve tuple with given tid
@@ -48,6 +50,174 @@ storage_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * stuple,
 												follow_updates, buffer, hufd);
 }
 
+/* ----------------
+ *		heap_beginscan_parallel - join a parallel scan
+ *
+ *		Caller must hold a suitable lock on the correct relation.
+ * ----------------
+ */
+HeapScanDesc
+storage_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan)
+{
+	Snapshot	snapshot;
+
+	Assert(RelationGetRelid(relation) == parallel_scan->phs_relid);
+	snapshot = RestoreSnapshot(parallel_scan->phs_snapshot_data);
+	RegisterSnapshot(snapshot);
+
+	return relation->rd_stamroutine->scan_begin(relation, snapshot, 0, NULL, parallel_scan,
+												true, true, true, false, false, true);
+}
+
+/*
+ * heap_setscanlimits - restrict range of a heapscan
+ *
+ * startBlk is the page to start at
+ * numBlks is number of pages to scan (InvalidBlockNumber means "all")
+ */
+void
+storage_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
+{
+	sscan->rs_rd->rd_stamroutine->scansetlimits(sscan, startBlk, numBlks);
+}
+
+
+/* ----------------
+ *		heap_beginscan	- begin relation scan
+ *
+ * heap_beginscan is the "standard" case.
+ *
+ * heap_beginscan_catalog differs in setting up its own temporary snapshot.
+ *
+ * heap_beginscan_strat offers an extended API that lets the caller control
+ * whether a nondefault buffer access strategy can be used, and whether
+ * syncscan can be chosen (possibly resulting in the scan not starting from
+ * block zero).  Both of these default to true with plain heap_beginscan.
+ *
+ * heap_beginscan_bm is an alternative entry point for setting up a
+ * HeapScanDesc for a bitmap heap scan.  Although that scan technology is
+ * really quite unlike a standard seqscan, there is just enough commonality
+ * to make it worth using the same data structure.
+ *
+ * heap_beginscan_sampling is an alternative entry point for setting up a
+ * HeapScanDesc for a TABLESAMPLE scan.  As with bitmap scans, it's worth
+ * using the same data structure although the behavior is rather different.
+ * In addition to the options offered by heap_beginscan_strat, this call
+ * also allows control of whether page-mode visibility checking is used.
+ * ----------------
+ */
+HeapScanDesc
+storage_beginscan(Relation relation, Snapshot snapshot,
+				  int nkeys, ScanKey key)
+{
+	return relation->rd_stamroutine->scan_begin(relation, snapshot, nkeys, key, NULL,
+												true, true, true, false, false, false);
+}
+
+HeapScanDesc
+storage_beginscan_catalog(Relation relation, int nkeys, ScanKey key)
+{
+	Oid			relid = RelationGetRelid(relation);
+	Snapshot	snapshot = RegisterSnapshot(GetCatalogSnapshot(relid));
+
+	return relation->rd_stamroutine->scan_begin(relation, snapshot, nkeys, key, NULL,
+												true, true, true, false, false, true);
+}
+
+HeapScanDesc
+storage_beginscan_strat(Relation relation, Snapshot snapshot,
+						int nkeys, ScanKey key,
+						bool allow_strat, bool allow_sync)
+{
+	return relation->rd_stamroutine->scan_begin(relation, snapshot, nkeys, key, NULL,
+												allow_strat, allow_sync, true,
+												false, false, false);
+}
+
+HeapScanDesc
+storage_beginscan_bm(Relation relation, Snapshot snapshot,
+					 int nkeys, ScanKey key)
+{
+	return relation->rd_stamroutine->scan_begin(relation, snapshot, nkeys, key, NULL,
+												false, false, true, true, false, false);
+}
+
+HeapScanDesc
+storage_beginscan_sampling(Relation relation, Snapshot snapshot,
+						   int nkeys, ScanKey key,
+						   bool allow_strat, bool allow_sync, bool allow_pagemode)
+{
+	return relation->rd_stamroutine->scan_begin(relation, snapshot, nkeys, key, NULL,
+												allow_strat, allow_sync, allow_pagemode,
+												false, true, false);
+}
+
+/* ----------------
+ *		heap_rescan		- restart a relation scan
+ * ----------------
+ */
+void
+storage_rescan(HeapScanDesc scan,
+			   ScanKey key)
+{
+	scan->rs_rd->rd_stamroutine->scan_rescan(scan, key, false, false, false, false);
+}
+
+/* ----------------
+ *		heap_rescan_set_params	- restart a relation scan after changing params
+ *
+ * This call allows changing the buffer strategy, syncscan, and pagemode
+ * options before starting a fresh scan.  Note that although the actual use
+ * of syncscan might change (effectively, enabling or disabling reporting),
+ * the previously selected startblock will be kept.
+ * ----------------
+ */
+void
+storage_rescan_set_params(HeapScanDesc scan, ScanKey key,
+						  bool allow_strat, bool allow_sync, bool allow_pagemode)
+{
+	scan->rs_rd->rd_stamroutine->scan_rescan(scan, key, true,
+											 allow_strat, allow_sync, (allow_pagemode && IsMVCCSnapshot(scan->rs_snapshot)));
+}
+
+/* ----------------
+ *		heap_endscan	- end relation scan
+ *
+ *		See how to integrate with index scans.
+ *		Check handling if reldesc caching.
+ * ----------------
+ */
+void
+storage_endscan(HeapScanDesc scan)
+{
+	scan->rs_rd->rd_stamroutine->scan_end(scan);
+}
+
+
+/* ----------------
+ *		heap_update_snapshot
+ *
+ *		Update snapshot info in heap scan descriptor.
+ * ----------------
+ */
+void
+storage_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
+{
+	scan->rs_rd->rd_stamroutine->scan_update_snapshot(scan, snapshot);
+}
+
+StorageTuple
+storage_getnext(HeapScanDesc sscan, ScanDirection direction)
+{
+	return sscan->rs_rd->rd_stamroutine->scan_getnext(sscan, direction);
+}
+
+TupleTableSlot *
+storage_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
+{
+	return sscan->rs_rd->rd_stamroutine->scan_getnextslot(sscan, direction, slot);
+}
+
 /*
  * Insert a tuple from a slot into storage AM routine
  */
@@ -87,6 +257,59 @@ storage_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 												  lockmode, IndexFunc, recheckIndexes);
 }
 
+/*
+ *	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
+ * of a HOT chain), and buffer is the buffer holding this tuple.  We search
+ * for the first chain member satisfying the given snapshot.  If one is
+ * found, we update *tid to reference that tuple's offset number, and
+ * return true.  If no match, return false without modifying *tid.
+ *
+ * heapTuple is a caller-supplied buffer.  When a match is found, we return
+ * the tuple here, in addition to updating *tid.  If no match is found, the
+ * contents of this buffer on return are undefined.
+ *
+ * If all_dead is not NULL, we check non-visible tuples to see if they are
+ * globally dead; *all_dead is set true if all members of the HOT chain
+ * are vacuumable, false if not.
+ *
+ * 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.
+ */
+bool
+storage_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
+						  Snapshot snapshot, HeapTuple heapTuple,
+						  bool *all_dead, bool first_call)
+{
+	return relation->rd_stamroutine->hot_search_buffer(tid, relation, buffer,
+													   snapshot, heapTuple, all_dead, first_call);
+}
+
+/*
+ *	heap_hot_search		- search HOT chain for tuple satisfying snapshot
+ *
+ * This has the same API as heap_hot_search_buffer, except that the caller
+ * does not provide the buffer containing the page, rather we access it
+ * locally.
+ */
+bool
+storage_hot_search(ItemPointer tid, Relation relation, Snapshot snapshot,
+				   bool *all_dead)
+{
+	bool		result;
+	Buffer		buffer;
+	HeapTupleData heapTuple;
+
+	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
+	LockBuffer(buffer, BUFFER_LOCK_SHARE);
+	result = relation->rd_stamroutine->hot_search_buffer(tid, relation, buffer,
+														 snapshot, &heapTuple, all_dead, true);
+	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+	ReleaseBuffer(buffer);
+	return result;
+}
 
 /*
  *	storage_multi_insert	- insert multiple tuple into a storage
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 8287de97a2..a73a363a49 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -18,6 +18,7 @@
 #include <signal.h>
 
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/xact.h"
 #include "access/xlog_internal.h"
 #include "bootstrap/bootstrap.h"
@@ -586,18 +587,18 @@ boot_openrel(char *relname)
 	{
 		/* We can now load the pg_type data */
 		rel = heap_open(TypeRelationId, NoLock);
-		scan = heap_beginscan_catalog(rel, 0, NULL);
+		scan = storage_beginscan_catalog(rel, 0, NULL);
 		i = 0;
-		while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tup = storage_getnext(scan, ForwardScanDirection)) != NULL)
 			++i;
-		heap_endscan(scan);
+		storage_endscan(scan);
 		app = Typ = ALLOC(struct typmap *, i + 1);
 		while (i-- > 0)
 			*app++ = ALLOC(struct typmap, 1);
 		*app = NULL;
-		scan = heap_beginscan_catalog(rel, 0, NULL);
+		scan = storage_beginscan_catalog(rel, 0, NULL);
 		app = Typ;
-		while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tup = storage_getnext(scan, ForwardScanDirection)) != NULL)
 		{
 			(*app)->am_oid = HeapTupleGetOid(tup);
 			memcpy((char *) &(*app)->am_typ,
@@ -605,7 +606,7 @@ boot_openrel(char *relname)
 				   sizeof((*app)->am_typ));
 			app++;
 		}
-		heap_endscan(scan);
+		storage_endscan(scan);
 		heap_close(rel, NoLock);
 	}
 
@@ -916,25 +917,25 @@ gettype(char *type)
 		}
 		elog(DEBUG4, "external type: %s", type);
 		rel = heap_open(TypeRelationId, NoLock);
-		scan = heap_beginscan_catalog(rel, 0, NULL);
+		scan = storage_beginscan_catalog(rel, 0, NULL);
 		i = 0;
-		while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tup = storage_getnext(scan, ForwardScanDirection)) != NULL)
 			++i;
-		heap_endscan(scan);
+		storage_endscan(scan);
 		app = Typ = ALLOC(struct typmap *, i + 1);
 		while (i-- > 0)
 			*app++ = ALLOC(struct typmap, 1);
 		*app = NULL;
-		scan = heap_beginscan_catalog(rel, 0, NULL);
+		scan = storage_beginscan_catalog(rel, 0, NULL);
 		app = Typ;
-		while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tup = storage_getnext(scan, ForwardScanDirection)) != NULL)
 		{
 			(*app)->am_oid = HeapTupleGetOid(tup);
 			memmove((char *) &(*app++)->am_typ,
 					(char *) GETSTRUCT(tup),
 					sizeof((*app)->am_typ));
 		}
-		heap_endscan(scan);
+		storage_endscan(scan);
 		heap_close(rel, NoLock);
 		return gettype(type);
 	}
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index e481cf3d11..0b3be57a88 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -20,6 +20,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "catalog/binary_upgrade.h"
@@ -847,14 +848,14 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 									InvalidOid);
 
 					rel = heap_open(ProcedureRelationId, AccessShareLock);
-					scan = heap_beginscan_catalog(rel, keycount, key);
+					scan = storage_beginscan_catalog(rel, keycount, key);
 
-					while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+					while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 					{
 						objects = lappend_oid(objects, HeapTupleGetOid(tuple));
 					}
 
-					heap_endscan(scan);
+					storage_endscan(scan);
 					heap_close(rel, AccessShareLock);
 				}
 				break;
@@ -892,14 +893,14 @@ getRelationsInNamespace(Oid namespaceId, char relkind)
 				CharGetDatum(relkind));
 
 	rel = heap_open(RelationRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(rel, 2, key);
+	scan = storage_beginscan_catalog(rel, 2, key);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		relations = lappend_oid(relations, HeapTupleGetOid(tuple));
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(rel, AccessShareLock);
 
 	return relations;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index ead8d2abdf..16bb70ef45 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -26,6 +26,7 @@
 #include "access/amapi.h"
 #include "access/multixact.h"
 #include "access/relscan.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/transam.h"
 #include "access/visibilitymap.h"
@@ -1907,10 +1908,10 @@ index_update_stats(Relation rel,
 					BTEqualStrategyNumber, F_OIDEQ,
 					ObjectIdGetDatum(relid));
 
-		pg_class_scan = heap_beginscan_catalog(pg_class, 1, key);
-		tuple = heap_getnext(pg_class_scan, ForwardScanDirection);
+		pg_class_scan = storage_beginscan_catalog(pg_class, 1, key);
+		tuple = storage_getnext(pg_class_scan, ForwardScanDirection);
 		tuple = heap_copytuple(tuple);
-		heap_endscan(pg_class_scan);
+		storage_endscan(pg_class_scan);
 	}
 	else
 	{
@@ -2282,16 +2283,16 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	}
 
 	method = heapRelation->rd_stamroutine;
-	scan = heap_beginscan_strat(heapRelation,	/* relation */
-								snapshot,	/* snapshot */
-								0,	/* number of keys */
-								NULL,	/* scan key */
-								true,	/* buffer access strategy OK */
-								allow_sync);	/* syncscan OK? */
+	scan = storage_beginscan_strat(heapRelation,	/* relation */
+								   snapshot,	/* snapshot */
+								   0,	/* number of keys */
+								   NULL,	/* scan key */
+								   true,	/* buffer access strategy OK */
+								   allow_sync); /* syncscan OK? */
 
 	/* set our scan endpoints */
 	if (!allow_sync)
-		heap_setscanlimits(scan, start_blockno, numblocks);
+		storage_setscanlimits(scan, start_blockno, numblocks);
 	else
 	{
 		/* syncscan can only be requested on whole relation */
@@ -2304,7 +2305,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	/*
 	 * Scan all tuples in the base relation.
 	 */
-	while ((heapTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((heapTuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		bool		tupleIsAlive;
 
@@ -2616,7 +2617,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 		}
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	/* we can now forget our snapshot, if set */
 	if (IsBootstrapProcessingMode() || indexInfo->ii_Concurrent)
@@ -2687,14 +2688,14 @@ IndexCheckExclusion(Relation heapRelation,
 	 * Scan all live tuples in the base relation.
 	 */
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	scan = heap_beginscan_strat(heapRelation,	/* relation */
-								snapshot,	/* snapshot */
-								0,	/* number of keys */
-								NULL,	/* scan key */
-								true,	/* buffer access strategy OK */
-								true);	/* syncscan OK */
-
-	while ((heapTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	scan = storage_beginscan_strat(heapRelation,	/* relation */
+								   snapshot,	/* snapshot */
+								   0,	/* number of keys */
+								   NULL,	/* scan key */
+								   true,	/* buffer access strategy OK */
+								   true);	/* syncscan OK */
+
+	while ((heapTuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		CHECK_FOR_INTERRUPTS();
 
@@ -2730,7 +2731,7 @@ IndexCheckExclusion(Relation heapRelation,
 								   estate, true);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 
 	ExecDropSingleTupleTableSlot(slot);
@@ -3007,17 +3008,17 @@ validate_index_heapscan(Relation heapRelation,
 	 * here, because it's critical that we read from block zero forward to
 	 * match the sorted TIDs.
 	 */
-	scan = heap_beginscan_strat(heapRelation,	/* relation */
-								snapshot,	/* snapshot */
-								0,	/* number of keys */
-								NULL,	/* scan key */
-								true,	/* buffer access strategy OK */
-								false); /* syncscan not OK */
+	scan = storage_beginscan_strat(heapRelation,	/* relation */
+								   snapshot,	/* snapshot */
+								   0,	/* number of keys */
+								   NULL,	/* scan key */
+								   true,	/* buffer access strategy OK */
+								   false);	/* syncscan not OK */
 
 	/*
 	 * Scan all tuples matching the snapshot.
 	 */
-	while ((heapTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((heapTuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		ItemPointer heapcursor = &heapTuple->t_self;
 		ItemPointerData rootTuple;
@@ -3174,7 +3175,7 @@ validate_index_heapscan(Relation heapRelation,
 		}
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	ExecDropSingleTupleTableSlot(slot);
 
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 5c4018e9f7..a81b79d2f1 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -19,6 +19,7 @@
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/nbtree.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
@@ -1324,7 +1325,7 @@ check_default_allows_bound(Relation parent, Relation default_rel,
 
 		econtext = GetPerTupleExprContext(estate);
 		snapshot = RegisterSnapshot(GetLatestSnapshot());
-		scan = heap_beginscan(part_rel, snapshot, 0, NULL);
+		scan = storage_beginscan(part_rel, snapshot, 0, NULL);
 		tupslot = MakeSingleTupleTableSlot(tupdesc);
 
 		/*
@@ -1333,7 +1334,7 @@ check_default_allows_bound(Relation parent, Relation default_rel,
 		 */
 		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
 
-		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 		{
 			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
 			econtext->ecxt_scantuple = tupslot;
@@ -1349,7 +1350,7 @@ check_default_allows_bound(Relation parent, Relation default_rel,
 		}
 
 		MemoryContextSwitchTo(oldCxt);
-		heap_endscan(scan);
+		storage_endscan(scan);
 		UnregisterSnapshot(snapshot);
 		ExecDropSingleTupleTableSlot(tupslot);
 		FreeExecutorState(estate);
diff --git a/src/backend/catalog/pg_conversion.c b/src/backend/catalog/pg_conversion.c
index 5746dc349a..1d048e6394 100644
--- a/src/backend/catalog/pg_conversion.c
+++ b/src/backend/catalog/pg_conversion.c
@@ -16,6 +16,7 @@
 
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
@@ -161,14 +162,14 @@ RemoveConversionById(Oid conversionOid)
 	/* open pg_conversion */
 	rel = heap_open(ConversionRelationId, RowExclusiveLock);
 
-	scan = heap_beginscan_catalog(rel, 1, &scanKeyData);
+	scan = storage_beginscan_catalog(rel, 1, &scanKeyData);
 
 	/* search for the target tuple */
-	if (HeapTupleIsValid(tuple = heap_getnext(scan, ForwardScanDirection)))
+	if (HeapTupleIsValid(tuple = storage_getnext(scan, ForwardScanDirection)))
 		CatalogTupleDelete(rel, &tuple->t_self);
 	else
 		elog(ERROR, "could not find tuple for conversion %u", conversionOid);
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(rel, RowExclusiveLock);
 }
 
diff --git a/src/backend/catalog/pg_db_role_setting.c b/src/backend/catalog/pg_db_role_setting.c
index 323471bc83..517e3101cd 100644
--- a/src/backend/catalog/pg_db_role_setting.c
+++ b/src/backend/catalog/pg_db_role_setting.c
@@ -13,6 +13,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_db_role_setting.h"
@@ -196,12 +197,12 @@ DropSetting(Oid databaseid, Oid roleid)
 		numkeys++;
 	}
 
-	scan = heap_beginscan_catalog(relsetting, numkeys, keys);
-	while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+	scan = storage_beginscan_catalog(relsetting, numkeys, keys);
+	while (HeapTupleIsValid(tup = storage_getnext(scan, ForwardScanDirection)))
 	{
 		CatalogTupleDelete(relsetting, &tup->t_self);
 	}
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	heap_close(relsetting, RowExclusiveLock);
 }
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 3ef7ba8cd5..145e3c1d65 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -21,6 +21,7 @@
 #include "access/hash.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/xact.h"
 
 #include "catalog/catalog.h"
@@ -324,9 +325,9 @@ GetAllTablesPublicationRelations(void)
 				BTEqualStrategyNumber, F_CHAREQ,
 				CharGetDatum(RELKIND_RELATION));
 
-	scan = heap_beginscan_catalog(classRel, 1, key);
+	scan = storage_beginscan_catalog(classRel, 1, key);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Oid			relid = HeapTupleGetOid(tuple);
 		Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
@@ -335,7 +336,7 @@ GetAllTablesPublicationRelations(void)
 			result = lappend_oid(result, relid);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(classRel, AccessShareLock);
 
 	return result;
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index fb53d71cd6..a51f2e4dfc 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -19,6 +19,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/xact.h"
 
 #include "catalog/indexing.h"
@@ -402,12 +403,12 @@ RemoveSubscriptionRel(Oid subid, Oid relid)
 	}
 
 	/* Do the search and delete what we found. */
-	scan = heap_beginscan_catalog(rel, nkeys, skey);
-	while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+	scan = storage_beginscan_catalog(rel, nkeys, skey);
+	while (HeapTupleIsValid(tup = storage_getnext(scan, ForwardScanDirection)))
 	{
 		CatalogTupleDelete(rel, &tup->t_self);
 	}
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	heap_close(rel, RowExclusiveLock);
 }
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index dbcc5bc172..e0f6973a3f 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -21,6 +21,7 @@
 #include "access/multixact.h"
 #include "access/relscan.h"
 #include "access/rewriteheap.h"
+#include "access/storageam.h"
 #include "access/storageamapi.h"
 #include "access/transam.h"
 #include "access/tuptoaster.h"
@@ -909,7 +910,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	}
 	else
 	{
-		heapScan = heap_beginscan(OldHeap, SnapshotAny, 0, (ScanKey) NULL);
+		heapScan = storage_beginscan(OldHeap, SnapshotAny, 0, (ScanKey) NULL);
 		indexScan = NULL;
 	}
 
@@ -959,7 +960,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 		}
 		else
 		{
-			tuple = heap_getnext(heapScan, ForwardScanDirection);
+			tuple = storage_getnext(heapScan, ForwardScanDirection);
 			if (tuple == NULL)
 				break;
 
@@ -1045,7 +1046,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	if (indexScan != NULL)
 		index_endscan(indexScan);
 	if (heapScan != NULL)
-		heap_endscan(heapScan);
+		storage_endscan(heapScan);
 
 	/*
 	 * In scan-and-sort mode, complete the sort, then read out all live tuples
@@ -1656,8 +1657,8 @@ get_tables_to_cluster(MemoryContext cluster_context)
 				Anum_pg_index_indisclustered,
 				BTEqualStrategyNumber, F_BOOLEQ,
 				BoolGetDatum(true));
-	scan = heap_beginscan_catalog(indRelation, 1, &entry);
-	while ((indexTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	scan = storage_beginscan_catalog(indRelation, 1, &entry);
+	while ((indexTuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		index = (Form_pg_index) GETSTRUCT(indexTuple);
 
@@ -1677,7 +1678,7 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 		MemoryContextSwitchTo(old_context);
 	}
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	relation_close(indRelation, AccessShareLock);
 
diff --git a/src/backend/commands/constraint.c b/src/backend/commands/constraint.c
index e2544e51ed..6727d154e1 100644
--- a/src/backend/commands/constraint.c
+++ b/src/backend/commands/constraint.c
@@ -13,6 +13,7 @@
  */
 #include "postgres.h"
 
+#include "access/storageam.h"
 #include "catalog/index.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
@@ -102,7 +103,7 @@ unique_key_recheck(PG_FUNCTION_ARGS)
 	 * removed.
 	 */
 	tmptid = new_row->t_self;
-	if (!heap_hot_search(&tmptid, trigdata->tg_relation, SnapshotSelf, NULL))
+	if (!storage_hot_search(&tmptid, trigdata->tg_relation, SnapshotSelf, NULL))
 	{
 		/*
 		 * 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 62d1ac98d5..f2209d9f81 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2038,10 +2038,10 @@ CopyTo(CopyState cstate)
 		values = (Datum *) palloc(num_phys_attrs * sizeof(Datum));
 		nulls = (bool *) palloc(num_phys_attrs * sizeof(bool));
 
-		scandesc = heap_beginscan(cstate->rel, GetActiveSnapshot(), 0, NULL);
+		scandesc = storage_beginscan(cstate->rel, GetActiveSnapshot(), 0, NULL);
 
 		processed = 0;
-		while ((tuple = heap_getnext(scandesc, ForwardScanDirection)) != NULL)
+		while ((tuple = storage_getnext(scandesc, ForwardScanDirection)) != NULL)
 		{
 			CHECK_FOR_INTERRUPTS();
 
@@ -2053,7 +2053,7 @@ CopyTo(CopyState cstate)
 			processed++;
 		}
 
-		heap_endscan(scandesc);
+		storage_endscan(scandesc);
 
 		pfree(values);
 		pfree(nulls);
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index eb1a4695c0..87d651002e 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -26,6 +26,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -590,8 +591,8 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 		 * each one to the new database.
 		 */
 		rel = heap_open(TableSpaceRelationId, AccessShareLock);
-		scan = heap_beginscan_catalog(rel, 0, NULL);
-		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		scan = storage_beginscan_catalog(rel, 0, NULL);
+		while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 		{
 			Oid			srctablespace = HeapTupleGetOid(tuple);
 			Oid			dsttablespace;
@@ -643,7 +644,7 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 								  XLOG_DBASE_CREATE | XLR_SPECIAL_REL_UPDATE);
 			}
 		}
-		heap_endscan(scan);
+		storage_endscan(scan);
 		heap_close(rel, AccessShareLock);
 
 		/*
@@ -1875,8 +1876,8 @@ remove_dbtablespaces(Oid db_id)
 	HeapTuple	tuple;
 
 	rel = heap_open(TableSpaceRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(rel, 0, NULL);
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	scan = storage_beginscan_catalog(rel, 0, NULL);
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Oid			dsttablespace = HeapTupleGetOid(tuple);
 		char	   *dstpath;
@@ -1917,7 +1918,7 @@ remove_dbtablespaces(Oid db_id)
 		pfree(dstpath);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(rel, AccessShareLock);
 }
 
@@ -1942,8 +1943,8 @@ check_db_file_conflict(Oid db_id)
 	HeapTuple	tuple;
 
 	rel = heap_open(TableSpaceRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(rel, 0, NULL);
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	scan = storage_beginscan_catalog(rel, 0, NULL);
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Oid			dsttablespace = HeapTupleGetOid(tuple);
 		char	   *dstpath;
@@ -1966,7 +1967,7 @@ check_db_file_conflict(Oid db_id)
 		pfree(dstpath);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(rel, AccessShareLock);
 
 	return result;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 97091dd9fb..400f936a2f 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -18,6 +18,7 @@
 #include "access/amapi.h"
 #include "access/htup_details.h"
 #include "access/reloptions.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
@@ -1964,8 +1965,8 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 	 * rels will be processed indirectly by reindex_relation).
 	 */
 	relationRelation = heap_open(RelationRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(relationRelation, num_keys, scan_keys);
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	scan = storage_beginscan_catalog(relationRelation, num_keys, scan_keys);
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Form_pg_class classtuple = (Form_pg_class) GETSTRUCT(tuple);
 		Oid			relid = HeapTupleGetOid(tuple);
@@ -2005,7 +2006,7 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 
 		MemoryContextSwitchTo(old);
 	}
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(relationRelation, AccessShareLock);
 
 	/* Now reindex each rel in a separate transaction */
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 73b68826f2..3d4fa831b3 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4551,7 +4551,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		 * checking all the constraints.
 		 */
 		snapshot = RegisterSnapshot(GetLatestSnapshot());
-		scan = heap_beginscan(oldrel, snapshot, 0, NULL);
+		scan = storage_beginscan(oldrel, snapshot, 0, NULL);
 
 		/*
 		 * Switch to per-tuple memory context and reset it for each tuple
@@ -4559,7 +4559,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		 */
 		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
 
-		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 		{
 			if (tab->rewrite > 0)
 			{
@@ -4673,7 +4673,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 
 		MemoryContextSwitchTo(oldCxt);
-		heap_endscan(scan);
+		storage_endscan(scan);
 		UnregisterSnapshot(snapshot);
 
 		ExecDropSingleTupleTableSlot(oldslot);
@@ -5076,9 +5076,9 @@ find_typed_table_dependencies(Oid typeOid, const char *typeName, DropBehavior be
 				BTEqualStrategyNumber, F_OIDEQ,
 				ObjectIdGetDatum(typeOid));
 
-	scan = heap_beginscan_catalog(classRel, 1, key);
+	scan = storage_beginscan_catalog(classRel, 1, key);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		if (behavior == DROP_RESTRICT)
 			ereport(ERROR,
@@ -5090,7 +5090,7 @@ find_typed_table_dependencies(Oid typeOid, const char *typeName, DropBehavior be
 			result = lappend_oid(result, HeapTupleGetOid(tuple));
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(classRel, AccessShareLock);
 
 	return result;
@@ -8260,7 +8260,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	econtext->ecxt_scantuple = slot;
 
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	scan = heap_beginscan(rel, snapshot, 0, NULL);
+	scan = storage_beginscan(rel, snapshot, 0, NULL);
 
 	/*
 	 * Switch to per-tuple memory context and reset it for each tuple
@@ -8268,7 +8268,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	 */
 	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
@@ -8283,7 +8283,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	}
 
 	MemoryContextSwitchTo(oldcxt);
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 	ExecDropSingleTupleTableSlot(slot);
 	FreeExecutorState(estate);
@@ -8338,9 +8338,9 @@ validateForeignKeyConstraint(char *conname,
 	 * ereport(ERROR) and that's that.
 	 */
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	scan = heap_beginscan(rel, snapshot, 0, NULL);
+	scan = storage_beginscan(rel, snapshot, 0, NULL);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		FunctionCallInfoData fcinfo;
 		TriggerData trigdata;
@@ -8369,7 +8369,7 @@ validateForeignKeyConstraint(char *conname,
 		RI_FKey_check_ins(&fcinfo);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 }
 
@@ -10874,8 +10874,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 				ObjectIdGetDatum(orig_tablespaceoid));
 
 	rel = heap_open(RelationRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(rel, 1, key);
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	scan = storage_beginscan_catalog(rel, 1, key);
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Oid			relOid = HeapTupleGetOid(tuple);
 		Form_pg_class relForm;
@@ -10934,7 +10934,7 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 		relations = lappend_oid(relations, relOid);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(rel, AccessShareLock);
 
 	if (relations == NIL)
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index d574e4dd00..6804182db7 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -53,6 +53,7 @@
 #include "access/heapam.h"
 #include "access/reloptions.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "access/xlog.h"
@@ -416,8 +417,8 @@ DropTableSpace(DropTableSpaceStmt *stmt)
 				Anum_pg_tablespace_spcname,
 				BTEqualStrategyNumber, F_NAMEEQ,
 				CStringGetDatum(tablespacename));
-	scandesc = heap_beginscan_catalog(rel, 1, entry);
-	tuple = heap_getnext(scandesc, ForwardScanDirection);
+	scandesc = storage_beginscan_catalog(rel, 1, entry);
+	tuple = storage_getnext(scandesc, ForwardScanDirection);
 
 	if (!HeapTupleIsValid(tuple))
 	{
@@ -434,7 +435,7 @@ DropTableSpace(DropTableSpaceStmt *stmt)
 					(errmsg("tablespace \"%s\" does not exist, skipping",
 							tablespacename)));
 			/* XXX I assume I need one or both of these next two calls */
-			heap_endscan(scandesc);
+			storage_endscan(scandesc);
 			heap_close(rel, NoLock);
 		}
 		return;
@@ -461,7 +462,7 @@ DropTableSpace(DropTableSpaceStmt *stmt)
 	 */
 	CatalogTupleDelete(rel, &tuple->t_self);
 
-	heap_endscan(scandesc);
+	storage_endscan(scandesc);
 
 	/*
 	 * Remove any comments or security labels on this tablespace.
@@ -925,8 +926,8 @@ RenameTableSpace(const char *oldname, const char *newname)
 				Anum_pg_tablespace_spcname,
 				BTEqualStrategyNumber, F_NAMEEQ,
 				CStringGetDatum(oldname));
-	scan = heap_beginscan_catalog(rel, 1, entry);
-	tup = heap_getnext(scan, ForwardScanDirection);
+	scan = storage_beginscan_catalog(rel, 1, entry);
+	tup = storage_getnext(scan, ForwardScanDirection);
 	if (!HeapTupleIsValid(tup))
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
@@ -937,7 +938,7 @@ RenameTableSpace(const char *oldname, const char *newname)
 	newtuple = heap_copytuple(tup);
 	newform = (Form_pg_tablespace) GETSTRUCT(newtuple);
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	/* Must be owner */
 	if (!pg_tablespace_ownercheck(HeapTupleGetOid(newtuple), GetUserId()))
@@ -955,15 +956,15 @@ RenameTableSpace(const char *oldname, const char *newname)
 				Anum_pg_tablespace_spcname,
 				BTEqualStrategyNumber, F_NAMEEQ,
 				CStringGetDatum(newname));
-	scan = heap_beginscan_catalog(rel, 1, entry);
-	tup = heap_getnext(scan, ForwardScanDirection);
+	scan = storage_beginscan_catalog(rel, 1, entry);
+	tup = storage_getnext(scan, ForwardScanDirection);
 	if (HeapTupleIsValid(tup))
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("tablespace \"%s\" already exists",
 						newname)));
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	/* OK, update the entry */
 	namestrcpy(&(newform->spcname), newname);
@@ -1005,8 +1006,8 @@ AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt)
 				Anum_pg_tablespace_spcname,
 				BTEqualStrategyNumber, F_NAMEEQ,
 				CStringGetDatum(stmt->tablespacename));
-	scandesc = heap_beginscan_catalog(rel, 1, entry);
-	tup = heap_getnext(scandesc, ForwardScanDirection);
+	scandesc = storage_beginscan_catalog(rel, 1, entry);
+	tup = storage_getnext(scandesc, ForwardScanDirection);
 	if (!HeapTupleIsValid(tup))
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
@@ -1047,7 +1048,7 @@ AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt)
 	heap_freetuple(newtuple);
 
 	/* Conclude heap scan. */
-	heap_endscan(scandesc);
+	storage_endscan(scandesc);
 	heap_close(rel, NoLock);
 
 	return tablespaceoid;
@@ -1396,8 +1397,8 @@ get_tablespace_oid(const char *tablespacename, bool missing_ok)
 				Anum_pg_tablespace_spcname,
 				BTEqualStrategyNumber, F_NAMEEQ,
 				CStringGetDatum(tablespacename));
-	scandesc = heap_beginscan_catalog(rel, 1, entry);
-	tuple = heap_getnext(scandesc, ForwardScanDirection);
+	scandesc = storage_beginscan_catalog(rel, 1, entry);
+	tuple = storage_getnext(scandesc, ForwardScanDirection);
 
 	/* We assume that there can be at most one matching tuple */
 	if (HeapTupleIsValid(tuple))
@@ -1405,7 +1406,7 @@ get_tablespace_oid(const char *tablespacename, bool missing_ok)
 	else
 		result = InvalidOid;
 
-	heap_endscan(scandesc);
+	storage_endscan(scandesc);
 	heap_close(rel, AccessShareLock);
 
 	if (!OidIsValid(result) && !missing_ok)
@@ -1442,8 +1443,8 @@ get_tablespace_name(Oid spc_oid)
 				ObjectIdAttributeNumber,
 				BTEqualStrategyNumber, F_OIDEQ,
 				ObjectIdGetDatum(spc_oid));
-	scandesc = heap_beginscan_catalog(rel, 1, entry);
-	tuple = heap_getnext(scandesc, ForwardScanDirection);
+	scandesc = storage_beginscan_catalog(rel, 1, entry);
+	tuple = storage_getnext(scandesc, ForwardScanDirection);
 
 	/* We assume that there can be at most one matching tuple */
 	if (HeapTupleIsValid(tuple))
@@ -1451,7 +1452,7 @@ get_tablespace_name(Oid spc_oid)
 	else
 		result = NULL;
 
-	heap_endscan(scandesc);
+	storage_endscan(scandesc);
 	heap_close(rel, AccessShareLock);
 
 	return result;
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index f86af4c054..8a339d16be 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -32,6 +32,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/xact.h"
 #include "catalog/binary_upgrade.h"
 #include "catalog/catalog.h"
@@ -2387,8 +2388,8 @@ AlterDomainNotNull(List *names, bool notNull)
 
 			/* Scan all tuples in this relation */
 			snapshot = RegisterSnapshot(GetLatestSnapshot());
-			scan = heap_beginscan(testrel, snapshot, 0, NULL);
-			while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+			scan = storage_beginscan(testrel, snapshot, 0, NULL);
+			while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 			{
 				int			i;
 
@@ -2417,7 +2418,7 @@ AlterDomainNotNull(List *names, bool notNull)
 					}
 				}
 			}
-			heap_endscan(scan);
+			storage_endscan(scan);
 			UnregisterSnapshot(snapshot);
 
 			/* Close each rel after processing, but keep lock */
@@ -2783,8 +2784,8 @@ validateDomainConstraint(Oid domainoid, char *ccbin)
 
 		/* Scan all tuples in this relation */
 		snapshot = RegisterSnapshot(GetLatestSnapshot());
-		scan = heap_beginscan(testrel, snapshot, 0, NULL);
-		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		scan = storage_beginscan(testrel, snapshot, 0, NULL);
+		while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 		{
 			int			i;
 
@@ -2827,7 +2828,7 @@ validateDomainConstraint(Oid domainoid, char *ccbin)
 
 			ResetExprContext(econtext);
 		}
-		heap_endscan(scan);
+		storage_endscan(scan);
 		UnregisterSnapshot(snapshot);
 
 		/* Hold relation lock till commit (XXX bad for concurrency) */
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 4abe6b15e0..3a8dc8eb0c 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -28,6 +28,7 @@
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/multixact.h"
+#include "access/storageam.h"
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
@@ -533,9 +534,9 @@ get_all_vacuum_rels(void)
 
 	pgclass = heap_open(RelationRelationId, AccessShareLock);
 
-	scan = heap_beginscan_catalog(pgclass, 0, NULL);
+	scan = storage_beginscan_catalog(pgclass, 0, NULL);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
 		MemoryContext oldcontext;
@@ -562,7 +563,7 @@ get_all_vacuum_rels(void)
 		MemoryContextSwitchTo(oldcontext);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(pgclass, AccessShareLock);
 
 	return vacrels;
@@ -1213,9 +1214,9 @@ vac_truncate_clog(TransactionId frozenXID,
 	 */
 	relation = heap_open(DatabaseRelationId, AccessShareLock);
 
-	scan = heap_beginscan_catalog(relation, 0, NULL);
+	scan = storage_beginscan_catalog(relation, 0, NULL);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		volatile FormData_pg_database *dbform = (Form_pg_database) GETSTRUCT(tuple);
 		TransactionId datfrozenxid = dbform->datfrozenxid;
@@ -1252,7 +1253,7 @@ vac_truncate_clog(TransactionId frozenXID,
 		}
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	heap_close(relation, AccessShareLock);
 
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index f1636a5b88..dde843174e 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -544,7 +544,7 @@ static bool
 IndexSupportsBackwardScan(Oid indexid)
 {
 	bool		result;
-	HeapTuple	ht_idxrel;
+	StorageTuple ht_idxrel;
 	Form_pg_class idxrelrec;
 	IndexAmRoutine *amroutine;
 
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index ab533cf9c7..e852718100 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -650,7 +650,7 @@ check_exclusion_or_unique_constraint(Relation heap, Relation index,
 	Oid		   *index_collations = index->rd_indcollation;
 	int			index_natts = index->rd_index->indnatts;
 	IndexScanDesc index_scan;
-	HeapTuple	tup;
+	StorageTuple tup;
 	ScanKeyData scankeys[INDEX_MAX_KEYS];
 	SnapshotData DirtySnapshot;
 	int			i;
@@ -732,12 +732,13 @@ retry:
 		bool		existing_isnull[INDEX_MAX_KEYS];
 		char	   *error_new;
 		char	   *error_existing;
+		tuple_data	t_data = storage_tuple_get_data(heap, tup, TID);
 
 		/*
 		 * Ignore the entry for the tuple we're trying to check.
 		 */
 		if (ItemPointerIsValid(tupleid) &&
-			ItemPointerEquals(tupleid, &tup->t_self))
+			ItemPointerEquals(tupleid, &(t_data.tid)))
 		{
 			if (found_self)		/* should not happen */
 				elog(ERROR, "found self tuple multiple times in index \"%s\"",
@@ -785,7 +786,8 @@ retry:
 			  DirtySnapshot.speculativeToken &&
 			  TransactionIdPrecedes(GetCurrentTransactionId(), xwait))))
 		{
-			ctid_wait = tup->t_data->t_ctid;
+			t_data = storage_tuple_get_data(heap, tup, CTID);
+			ctid_wait = t_data.tid;
 			reason_wait = indexInfo->ii_ExclusionOps ?
 				XLTW_RecheckExclusionConstr : XLTW_InsertIndex;
 			index_endscan(index_scan);
@@ -805,7 +807,10 @@ retry:
 		{
 			conflict = true;
 			if (conflictTid)
-				*conflictTid = tup->t_self;
+			{
+				t_data = storage_tuple_get_data(heap, tup, TID);
+				*conflictTid = t_data.tid;
+			}
 			break;
 		}
 
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index fcb58a0421..d5492fd260 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -118,7 +118,7 @@ RelationFindReplTupleByIndex(Relation rel, Oid idxoid,
 							 TupleTableSlot *searchslot,
 							 TupleTableSlot *outslot)
 {
-	HeapTuple	scantuple;
+	StorageTuple scantuple;
 	ScanKeyData skey[INDEX_MAX_KEYS];
 	IndexScanDesc scan;
 	SnapshotData snap;
@@ -228,8 +228,7 @@ RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 						 TupleTableSlot *searchslot, TupleTableSlot *outslot)
 {
 	TupleTableSlot *scanslot;
-	HeapTuple	scantuple;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	SnapshotData snap;
 	TransactionId xwait;
 	bool		found;
@@ -239,19 +238,19 @@ RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 
 	/* Start a heap scan. */
 	InitDirtySnapshot(snap);
-	scan = heap_beginscan(rel, &snap, 0, NULL);
+	scan = storage_beginscan(rel, &snap, 0, NULL);
 
 	scanslot = MakeSingleTupleTableSlot(desc);
 
 retry:
 	found = false;
 
-	heap_rescan(scan, NULL);
+	storage_rescan(scan, NULL);
 
 	/* Try to find the tuple */
-	while ((scantuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((scanslot = storage_getnextslot(scan, ForwardScanDirection, scanslot))
+		   && !TupIsNull(scanslot))
 	{
-		ExecStoreTuple(scantuple, scanslot, InvalidBuffer, false);
 		if (!ExecSlotCompare(scanslot, searchslot))
 			continue;
 
@@ -313,7 +312,7 @@ retry:
 		}
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	ExecDropSingleTupleTableSlot(scanslot);
 
 	return found;
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 01ca94f9e5..e9a042c02c 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -682,7 +682,7 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 Datum
 ExecFetchSlotTupleDatum(TupleTableSlot *slot)
 {
-	HeapTuple	tup;
+	StorageTuple tup;
 	TupleDesc	tupdesc;
 
 	/* Fetch slot's contents in regular-physical-tuple form */
@@ -766,7 +766,7 @@ ExecHeapifySlot(TupleTableSlot *slot)
 TupleTableSlot *
 ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
 {
-	HeapTuple	newTuple;
+	StorageTuple newTuple;
 	MemoryContext oldContext;
 
 	/*
@@ -1086,7 +1086,7 @@ TupleDescGetAttInMetadata(TupleDesc tupdesc)
  * values is an array of C strings, one for each attribute of the return tuple.
  * A NULL string pointer indicates we want to create a NULL field.
  */
-HeapTuple
+StorageTuple
 BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values)
 {
 	TupleDesc	tupdesc = attinmeta->tupdesc;
@@ -1094,7 +1094,7 @@ BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values)
 	Datum	   *dvalues;
 	bool	   *nulls;
 	int			i;
-	HeapTuple	tuple;
+	StorageTuple tuple;
 
 	dvalues = (Datum *) palloc(natts * sizeof(Datum));
 	nulls = (bool *) palloc(natts * sizeof(bool));
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 527f7d810f..e7ada907bb 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -181,7 +181,7 @@ static void sqlfunction_destroy(DestReceiver *self);
  * polymorphic arguments.
  */
 SQLFunctionParseInfoPtr
-prepare_sql_fn_parse_info(HeapTuple procedureTuple,
+prepare_sql_fn_parse_info(StorageTuple procedureTuple,
 						  Node *call_expr,
 						  Oid inputCollation)
 {
@@ -598,7 +598,7 @@ init_sql_fcache(FmgrInfo *finfo, Oid collation, bool lazyEvalOK)
 	MemoryContext fcontext;
 	MemoryContext oldcontext;
 	Oid			rettype;
-	HeapTuple	procedureTuple;
+	StorageTuple procedureTuple;
 	Form_pg_proc procedureStruct;
 	SQLFunctionCachePtr fcache;
 	List	   *raw_parsetree_list;
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index da6ef1a94c..ae4b4769ca 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -3130,7 +3130,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 		Oid			inputTypes[FUNC_MAX_ARGS];
 		int			numArguments;
 		int			numDirectArgs;
-		HeapTuple	aggTuple;
+		StorageTuple aggTuple;
 		Form_pg_aggregate aggform;
 		AclResult	aclresult;
 		Oid			transfn_oid,
@@ -3255,7 +3255,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 
 		/* Check that aggregate owner has permission to call component fns */
 		{
-			HeapTuple	procTuple;
+			StorageTuple procTuple;
 			Oid			aggOwner;
 
 			procTuple = SearchSysCache1(PROCOID,
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index 2c5c95d425..0c5ae8fa41 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -38,6 +38,7 @@
 #include <math.h>
 
 #include "access/relscan.h"
+#include "access/storageam.h"
 #include "access/transam.h"
 #include "access/visibilitymap.h"
 #include "executor/execdebug.h"
@@ -433,8 +434,8 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 			HeapTupleData heapTuple;
 
 			ItemPointerSet(&tid, page, offnum);
-			if (heap_hot_search_buffer(&tid, scan->rs_rd, buffer, snapshot,
-									   &heapTuple, NULL, true))
+			if (storage_hot_search_buffer(&tid, scan->rs_rd, buffer, snapshot,
+										  &heapTuple, NULL, true))
 				scan->rs_vistuples[ntup++] = ItemPointerGetOffsetNumber(&tid);
 		}
 	}
@@ -747,7 +748,7 @@ ExecReScanBitmapHeapScan(BitmapHeapScanState *node)
 	PlanState  *outerPlan = outerPlanState(node);
 
 	/* rescan to release any page pin */
-	heap_rescan(node->ss.ss_currentScanDesc, NULL);
+	storage_rescan(node->ss.ss_currentScanDesc, NULL);
 
 	/* release bitmaps and buffers if any */
 	if (node->tbmiterator)
@@ -837,7 +838,7 @@ ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 	/*
 	 * close heap scan
 	 */
-	heap_endscan(scanDesc);
+	storage_endscan(scanDesc);
 
 	/*
 	 * close the heap relation.
@@ -952,10 +953,10 @@ ExecInitBitmapHeapScan(BitmapHeapScan *node, EState *estate, int eflags)
 	 * Even though we aren't going to do a conventional seqscan, it is useful
 	 * to create a HeapScanDesc --- most of the fields in it are usable.
 	 */
-	scanstate->ss.ss_currentScanDesc = heap_beginscan_bm(currentRelation,
-														 estate->es_snapshot,
-														 0,
-														 NULL);
+	scanstate->ss.ss_currentScanDesc = storage_beginscan_bm(currentRelation,
+															estate->es_snapshot,
+															0,
+															NULL);
 
 	/*
 	 * get the scan type from the relation descriptor.
@@ -1123,5 +1124,5 @@ ExecBitmapHeapInitializeWorker(BitmapHeapScanState *node,
 	node->pstate = pstate;
 
 	snapshot = RestoreSnapshot(pstate->phs_snapshot_data);
-	heap_update_snapshot(node->ss.ss_currentScanDesc, snapshot);
+	storage_update_snapshot(node->ss.ss_currentScanDesc, snapshot);
 }
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 690308845c..353dcdc9ca 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -62,9 +62,9 @@ ForeignNext(ForeignScanState *node)
 	 */
 	if (plan->fsSystemCol && !TupIsNull(slot))
 	{
-		HeapTuple	tup = ExecHeapifySlot(slot);
-
-		tup->t_tableOid = RelationGetRelid(node->ss.ss_currentRelation);
+		ExecMaterializeSlot(slot);
+		ExecSlotUpdateTupleTableoid(slot,
+									RelationGetRelid(node->ss.ss_currentRelation));
 	}
 
 	return slot;
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index 1697ae650d..9f8cd88489 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -46,7 +46,7 @@
 
 static TupleTableSlot *ExecGather(PlanState *pstate);
 static TupleTableSlot *gather_getnext(GatherState *gatherstate);
-static HeapTuple gather_readnext(GatherState *gatherstate);
+static StorageTuple gather_readnext(GatherState *gatherstate);
 static void ExecShutdownGatherWorkers(GatherState *node);
 
 
@@ -254,7 +254,7 @@ gather_getnext(GatherState *gatherstate)
 	PlanState  *outerPlan = outerPlanState(gatherstate);
 	TupleTableSlot *outerTupleSlot;
 	TupleTableSlot *fslot = gatherstate->funnel_slot;
-	HeapTuple	tup;
+	StorageTuple tup;
 
 	while (gatherstate->nreaders > 0 || gatherstate->need_to_scan_locally)
 	{
@@ -298,7 +298,7 @@ gather_getnext(GatherState *gatherstate)
 /*
  * Attempt to read a tuple from one of our parallel workers.
  */
-static HeapTuple
+static StorageTuple
 gather_readnext(GatherState *gatherstate)
 {
 	int			nvisited = 0;
@@ -306,7 +306,7 @@ gather_readnext(GatherState *gatherstate)
 	for (;;)
 	{
 		TupleQueueReader *reader;
-		HeapTuple	tup;
+		StorageTuple tup;
 		bool		readerdone;
 
 		/* Check for async events, particularly messages from workers. */
diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c
index a69777aa95..cd6540ddf4 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -45,7 +45,7 @@
  */
 typedef struct GMReaderTupleBuffer
 {
-	HeapTuple  *tuple;			/* array of length MAX_TUPLE_STORE */
+	StorageTuple *tuple;		/* array of length MAX_TUPLE_STORE */
 	int			nTuples;		/* number of tuples currently stored */
 	int			readCounter;	/* index of next tuple to extract */
 	bool		done;			/* true if reader is known exhausted */
@@ -54,8 +54,8 @@ typedef struct GMReaderTupleBuffer
 static TupleTableSlot *ExecGatherMerge(PlanState *pstate);
 static int32 heap_compare_slots(Datum a, Datum b, void *arg);
 static TupleTableSlot *gather_merge_getnext(GatherMergeState *gm_state);
-static HeapTuple gm_readnext_tuple(GatherMergeState *gm_state, int nreader,
-				  bool nowait, bool *done);
+static StorageTuple gm_readnext_tuple(GatherMergeState *gm_state, int nreader,
+									  bool nowait, bool *done);
 static void ExecShutdownGatherMergeWorkers(GatherMergeState *node);
 static void gather_merge_setup(GatherMergeState *gm_state);
 static void gather_merge_init(GatherMergeState *gm_state);
@@ -407,7 +407,7 @@ gather_merge_setup(GatherMergeState *gm_state)
 	{
 		/* Allocate the tuple array with length MAX_TUPLE_STORE */
 		gm_state->gm_tuple_buffers[i].tuple =
-			(HeapTuple *) palloc0(sizeof(HeapTuple) * MAX_TUPLE_STORE);
+			(StorageTuple *) palloc0(sizeof(StorageTuple) * MAX_TUPLE_STORE);
 
 		/* Initialize tuple slot for worker */
 		gm_state->gm_slots[i + 1] = ExecInitExtraTupleSlot(gm_state->ps.state);
@@ -625,7 +625,7 @@ static bool
 gather_merge_readnext(GatherMergeState *gm_state, int reader, bool nowait)
 {
 	GMReaderTupleBuffer *tuple_buffer;
-	HeapTuple	tup;
+	StorageTuple tup;
 
 	/*
 	 * If we're being asked to generate a tuple from the leader, then we just
@@ -700,12 +700,12 @@ gather_merge_readnext(GatherMergeState *gm_state, int reader, bool nowait)
 /*
  * Attempt to read a tuple from given worker.
  */
-static HeapTuple
+static StorageTuple
 gm_readnext_tuple(GatherMergeState *gm_state, int nreader, bool nowait,
 				  bool *done)
 {
 	TupleQueueReader *reader;
-	HeapTuple	tup;
+	StorageTuple tup;
 
 	/* Check for async events, particularly messages from workers. */
 	CHECK_FOR_INTERRUPTS();
diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c
index c54c5aa659..e00c9bdbfc 100644
--- a/src/backend/executor/nodeIndexonlyscan.c
+++ b/src/backend/executor/nodeIndexonlyscan.c
@@ -117,7 +117,7 @@ IndexOnlyNext(IndexOnlyScanState *node)
 	 */
 	while ((tid = index_getnext_tid(scandesc, direction)) != NULL)
 	{
-		HeapTuple	tuple = NULL;
+		StorageTuple tuple = NULL;
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -186,9 +186,9 @@ IndexOnlyNext(IndexOnlyScanState *node)
 
 		/*
 		 * Fill the scan tuple slot with data from the index.  This might be
-		 * provided in either HeapTuple or IndexTuple format.  Conceivably an
-		 * index AM might fill both fields, in which case we prefer the heap
-		 * format, since it's probably a bit cheaper to fill a slot from.
+		 * provided in either StorageTuple or IndexTuple format.  Conceivably
+		 * an index AM might fill both fields, in which case we prefer the
+		 * heap format, since it's probably a bit cheaper to fill a slot from.
 		 */
 		if (scandesc->xs_hitup)
 		{
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 2ffef23107..60780e4a99 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -51,7 +51,7 @@
 typedef struct
 {
 	pairingheap_node ph_node;
-	HeapTuple	htup;
+	StorageTuple htup;
 	Datum	   *orderbyvals;
 	bool	   *orderbynulls;
 } ReorderTuple;
@@ -65,9 +65,9 @@ static int cmp_orderbyvals(const Datum *adist, const bool *anulls,
 				IndexScanState *node);
 static int reorderqueue_cmp(const pairingheap_node *a,
 				 const pairingheap_node *b, void *arg);
-static void reorderqueue_push(IndexScanState *node, HeapTuple tuple,
+static void reorderqueue_push(IndexScanState *node, StorageTuple tuple,
 				  Datum *orderbyvals, bool *orderbynulls);
-static HeapTuple reorderqueue_pop(IndexScanState *node);
+static StorageTuple reorderqueue_pop(IndexScanState *node);
 
 
 /* ----------------------------------------------------------------
@@ -84,7 +84,7 @@ IndexNext(IndexScanState *node)
 	ExprContext *econtext;
 	ScanDirection direction;
 	IndexScanDesc scandesc;
-	HeapTuple	tuple;
+	StorageTuple tuple;
 	TupleTableSlot *slot;
 
 	/*
@@ -185,7 +185,7 @@ IndexNextWithReorder(IndexScanState *node)
 	EState	   *estate;
 	ExprContext *econtext;
 	IndexScanDesc scandesc;
-	HeapTuple	tuple;
+	StorageTuple tuple;
 	TupleTableSlot *slot;
 	ReorderTuple *topmost = NULL;
 	bool		was_exact;
@@ -483,7 +483,7 @@ reorderqueue_cmp(const pairingheap_node *a, const pairingheap_node *b,
  * Helper function to push a tuple to the reorder queue.
  */
 static void
-reorderqueue_push(IndexScanState *node, HeapTuple tuple,
+reorderqueue_push(IndexScanState *node, StorageTuple tuple,
 				  Datum *orderbyvals, bool *orderbynulls)
 {
 	IndexScanDesc scandesc = node->iss_ScanDesc;
@@ -516,10 +516,10 @@ reorderqueue_push(IndexScanState *node, HeapTuple tuple,
 /*
  * Helper function to pop the next tuple from the reorder queue.
  */
-static HeapTuple
+static StorageTuple
 reorderqueue_pop(IndexScanState *node)
 {
-	HeapTuple	result;
+	StorageTuple result;
 	ReorderTuple *topmost;
 	int			i;
 
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index 6a118d1883..7e990dc35e 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -29,10 +29,12 @@
 static void InitScanRelation(SampleScanState *node, EState *estate, int eflags);
 static TupleTableSlot *SampleNext(SampleScanState *node);
 static void tablesample_init(SampleScanState *scanstate);
-static HeapTuple tablesample_getnext(SampleScanState *scanstate);
-static bool SampleTupleVisible(HeapTuple tuple, OffsetNumber tupoffset,
+static StorageTuple tablesample_getnext(SampleScanState *scanstate);
+static bool SampleTupleVisible(StorageTuple tuple, OffsetNumber tupoffset,
 				   HeapScanDesc scan);
 
+/* hari */
+
 /* ----------------------------------------------------------------
  *						Scan Support
  * ----------------------------------------------------------------
@@ -47,7 +49,7 @@ static bool SampleTupleVisible(HeapTuple tuple, OffsetNumber tupoffset,
 static TupleTableSlot *
 SampleNext(SampleScanState *node)
 {
-	HeapTuple	tuple;
+	StorageTuple tuple;
 	TupleTableSlot *slot;
 
 	/*
@@ -244,7 +246,7 @@ ExecEndSampleScan(SampleScanState *node)
 	 * close heap scan
 	 */
 	if (node->ss.ss_currentScanDesc)
-		heap_endscan(node->ss.ss_currentScanDesc);
+		storage_endscan(node->ss.ss_currentScanDesc);
 
 	/*
 	 * close the heap relation.
@@ -349,19 +351,19 @@ tablesample_init(SampleScanState *scanstate)
 	if (scanstate->ss.ss_currentScanDesc == NULL)
 	{
 		scanstate->ss.ss_currentScanDesc =
-			heap_beginscan_sampling(scanstate->ss.ss_currentRelation,
-									scanstate->ss.ps.state->es_snapshot,
-									0, NULL,
-									scanstate->use_bulkread,
-									allow_sync,
-									scanstate->use_pagemode);
+			storage_beginscan_sampling(scanstate->ss.ss_currentRelation,
+									   scanstate->ss.ps.state->es_snapshot,
+									   0, NULL,
+									   scanstate->use_bulkread,
+									   allow_sync,
+									   scanstate->use_pagemode);
 	}
 	else
 	{
-		heap_rescan_set_params(scanstate->ss.ss_currentScanDesc, NULL,
-							   scanstate->use_bulkread,
-							   allow_sync,
-							   scanstate->use_pagemode);
+		storage_rescan_set_params(scanstate->ss.ss_currentScanDesc, NULL,
+								  scanstate->use_bulkread,
+								  allow_sync,
+								  scanstate->use_pagemode);
 	}
 
 	pfree(params);
@@ -376,7 +378,7 @@ tablesample_init(SampleScanState *scanstate)
  * Note: an awful lot of this is copied-and-pasted from heapam.c.  It would
  * perhaps be better to refactor to share more code.
  */
-static HeapTuple
+static StorageTuple
 tablesample_getnext(SampleScanState *scanstate)
 {
 	TsmRoutine *tsm = scanstate->tsmroutine;
@@ -554,7 +556,7 @@ tablesample_getnext(SampleScanState *scanstate)
  * Check visibility of the tuple.
  */
 static bool
-SampleTupleVisible(HeapTuple tuple, OffsetNumber tupoffset, HeapScanDesc scan)
+SampleTupleVisible(StorageTuple tuple, OffsetNumber tupoffset, HeapScanDesc scan) //hari
 {
 	if (scan->rs_pageatatime)
 	{
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index a5bd60e579..7cfc2107f6 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -28,6 +28,7 @@
 #include "postgres.h"
 
 #include "access/relscan.h"
+#include "access/storageam.h"
 #include "executor/execdebug.h"
 #include "executor/nodeSeqscan.h"
 #include "utils/rel.h"
@@ -49,8 +50,7 @@ static TupleTableSlot *SeqNext(SeqScanState *node);
 static TupleTableSlot *
 SeqNext(SeqScanState *node)
 {
-	HeapTuple	tuple;
-	HeapScanDesc scandesc;
+	StorageScanDesc scandesc;
 	EState	   *estate;
 	ScanDirection direction;
 	TupleTableSlot *slot;
@@ -69,35 +69,16 @@ SeqNext(SeqScanState *node)
 		 * We reach here if the scan is not parallel, or if we're executing a
 		 * scan that was intended to be parallel serially.
 		 */
-		scandesc = heap_beginscan(node->ss.ss_currentRelation,
-								  estate->es_snapshot,
-								  0, NULL);
+		scandesc = storage_beginscan(node->ss.ss_currentRelation,
+									 estate->es_snapshot,
+									 0, NULL);
 		node->ss.ss_currentScanDesc = scandesc;
 	}
 
 	/*
 	 * get the next tuple from the table
 	 */
-	tuple = heap_getnext(scandesc, direction);
-
-	/*
-	 * save the tuple and the buffer returned to us by the access methods in
-	 * our scan tuple slot and return the slot.  Note: we pass 'false' because
-	 * tuples returned by heap_getnext() are pointers onto disk pages and were
-	 * not created with palloc() and so should not be pfree()'d.  Note also
-	 * that ExecStoreTuple will increment the refcount of the buffer; the
-	 * refcount will not be dropped until the tuple table slot is cleared.
-	 */
-	if (tuple)
-		ExecStoreTuple(tuple,	/* tuple to store */
-					   slot,	/* slot to store in */
-					   scandesc->rs_cbuf,	/* buffer associated with this
-											 * tuple */
-					   false);	/* don't pfree this pointer */
-	else
-		ExecClearTuple(slot);
-
-	return slot;
+	return storage_getnextslot(scandesc, direction, slot);
 }
 
 /*
@@ -225,7 +206,7 @@ void
 ExecEndSeqScan(SeqScanState *node)
 {
 	Relation	relation;
-	HeapScanDesc scanDesc;
+	StorageScanDesc scanDesc;
 
 	/*
 	 * get information from node
@@ -248,7 +229,7 @@ ExecEndSeqScan(SeqScanState *node)
 	 * close heap scan
 	 */
 	if (scanDesc != NULL)
-		heap_endscan(scanDesc);
+		storage_endscan(scanDesc);
 
 	/*
 	 * close the heap relation.
@@ -270,13 +251,13 @@ ExecEndSeqScan(SeqScanState *node)
 void
 ExecReScanSeqScan(SeqScanState *node)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 
 	scan = node->ss.ss_currentScanDesc;
 
 	if (scan != NULL)
-		heap_rescan(scan,		/* scan desc */
-					NULL);		/* new scan keys */
+		storage_rescan(scan,	/* scan desc */
+					   NULL);	/* new scan keys */
 
 	ExecScanReScan((ScanState *) node);
 }
@@ -323,7 +304,7 @@ ExecSeqScanInitializeDSM(SeqScanState *node,
 								 estate->es_snapshot);
 	shm_toc_insert(pcxt->toc, node->ss.ps.plan->plan_node_id, pscan);
 	node->ss.ss_currentScanDesc =
-		heap_beginscan_parallel(node->ss.ss_currentRelation, pscan);
+		storage_beginscan_parallel(node->ss.ss_currentRelation, pscan);
 }
 
 /* ----------------------------------------------------------------
@@ -355,5 +336,5 @@ ExecSeqScanInitializeWorker(SeqScanState *node,
 
 	pscan = shm_toc_lookup(pwcxt->toc, node->ss.ps.plan->plan_node_id, false);
 	node->ss.ss_currentScanDesc =
-		heap_beginscan_parallel(node->ss.ss_currentRelation, pscan);
+		storage_beginscan_parallel(node->ss.ss_currentRelation, pscan);
 }
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 02868749f6..a68584378b 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -2092,7 +2092,7 @@ initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc,
 {
 	Oid			inputTypes[FUNC_MAX_ARGS];
 	int			numArguments;
-	HeapTuple	aggTuple;
+	StorageTuple aggTuple;
 	Form_pg_aggregate aggform;
 	Oid			aggtranstype;
 	AttrNumber	initvalAttNo;
@@ -2175,7 +2175,7 @@ initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc,
 
 	/* Check that aggregate owner has permission to call component fns */
 	{
-		HeapTuple	procTuple;
+		StorageTuple procTuple;
 		Oid			aggOwner;
 
 		procTuple = SearchSysCache1(PROCOID,
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 977f317420..b72e806802 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -633,11 +633,11 @@ SPI_freeplan(SPIPlanPtr plan)
 	return 0;
 }
 
-HeapTuple
-SPI_copytuple(HeapTuple tuple)
+StorageTuple
+SPI_copytuple(StorageTuple tuple)
 {
 	MemoryContext oldcxt;
-	HeapTuple	ctuple;
+	StorageTuple ctuple;
 
 	if (tuple == NULL)
 	{
@@ -661,7 +661,7 @@ SPI_copytuple(HeapTuple tuple)
 }
 
 HeapTupleHeader
-SPI_returntuple(HeapTuple tuple, TupleDesc tupdesc)
+SPI_returntuple(StorageTuple tuple, TupleDesc tupdesc)
 {
 	MemoryContext oldcxt;
 	HeapTupleHeader dtup;
@@ -692,7 +692,7 @@ SPI_returntuple(HeapTuple tuple, TupleDesc tupdesc)
 	return dtup;
 }
 
-HeapTuple
+StorageTuple
 SPI_modifytuple(Relation rel, HeapTuple tuple, int natts, int *attnum,
 				Datum *Values, const char *Nulls)
 {
@@ -860,7 +860,7 @@ char *
 SPI_gettype(TupleDesc tupdesc, int fnumber)
 {
 	Oid			typoid;
-	HeapTuple	typeTuple;
+	StorageTuple typeTuple;
 	char	   *result;
 
 	SPI_result = 0;
@@ -968,7 +968,7 @@ SPI_datumTransfer(Datum value, bool typByVal, int typLen)
 }
 
 void
-SPI_freetuple(HeapTuple tuple)
+SPI_freetuple(StorageTuple tuple)
 {
 	/* No longer need to worry which context tuple was in... */
 	heap_freetuple(tuple);
@@ -1689,7 +1689,7 @@ spi_dest_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 
 	/* set up initial allocations */
 	tuptable->alloced = tuptable->free = 128;
-	tuptable->vals = (HeapTuple *) palloc(tuptable->alloced * sizeof(HeapTuple));
+	tuptable->vals = (StorageTuple *) palloc(tuptable->alloced * sizeof(StorageTuple));
 	tuptable->tupdesc = CreateTupleDescCopy(typeinfo);
 
 	MemoryContextSwitchTo(oldcxt);
@@ -1720,8 +1720,8 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self)
 		/* Double the size of the pointer array */
 		tuptable->free = tuptable->alloced;
 		tuptable->alloced += tuptable->free;
-		tuptable->vals = (HeapTuple *) repalloc_huge(tuptable->vals,
-													 tuptable->alloced * sizeof(HeapTuple));
+		tuptable->vals = (StorageTuple *) repalloc_huge(tuptable->vals,
+														tuptable->alloced * sizeof(StorageTuple));
 	}
 
 	tuptable->vals[tuptable->alloced - tuptable->free] =
diff --git a/src/backend/executor/tqueue.c b/src/backend/executor/tqueue.c
index ef681e2ec2..41d94774cb 100644
--- a/src/backend/executor/tqueue.c
+++ b/src/backend/executor/tqueue.c
@@ -168,7 +168,7 @@ DestroyTupleQueueReader(TupleQueueReader *reader)
  * accumulate bytes from a partially-read message, so it's useful to call
  * this with nowait = true even if nothing is returned.
  */
-HeapTuple
+StorageTuple
 TupleQueueReaderNext(TupleQueueReader *reader, bool nowait, bool *done)
 {
 	HeapTupleData htup;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 48765bb01b..0d2e1733bf 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1883,9 +1883,9 @@ get_database_list(void)
 	(void) GetTransactionSnapshot();
 
 	rel = heap_open(DatabaseRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(rel, 0, NULL);
+	scan = storage_beginscan_catalog(rel, 0, NULL);
 
-	while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+	while (HeapTupleIsValid(tup = storage_getnext(scan, ForwardScanDirection)))
 	{
 		Form_pg_database pgdatabase = (Form_pg_database) GETSTRUCT(tup);
 		avw_dbase  *avdb;
@@ -1912,7 +1912,7 @@ get_database_list(void)
 		MemoryContextSwitchTo(oldcxt);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(rel, AccessShareLock);
 
 	CommitTransactionCommand();
@@ -2043,13 +2043,13 @@ do_autovacuum(void)
 	 * wide tables there might be proportionally much more activity in the
 	 * TOAST table than in its parent.
 	 */
-	relScan = heap_beginscan_catalog(classRel, 0, NULL);
+	relScan = storage_beginscan_catalog(classRel, 0, NULL);
 
 	/*
 	 * On the first pass, we collect main tables to vacuum, and also the main
 	 * table relid to TOAST relid mapping.
 	 */
-	while ((tuple = heap_getnext(relScan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(relScan, ForwardScanDirection)) != NULL)
 	{
 		Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
 		PgStat_StatTabEntry *tabentry;
@@ -2135,7 +2135,7 @@ do_autovacuum(void)
 		}
 	}
 
-	heap_endscan(relScan);
+	storage_endscan(relScan);
 
 	/* second pass: check TOAST tables */
 	ScanKeyInit(&key,
@@ -2143,8 +2143,8 @@ do_autovacuum(void)
 				BTEqualStrategyNumber, F_CHAREQ,
 				CharGetDatum(RELKIND_TOASTVALUE));
 
-	relScan = heap_beginscan_catalog(classRel, 1, &key);
-	while ((tuple = heap_getnext(relScan, ForwardScanDirection)) != NULL)
+	relScan = storage_beginscan_catalog(classRel, 1, &key);
+	while ((tuple = storage_getnext(relScan, ForwardScanDirection)) != NULL)
 	{
 		Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
 		PgStat_StatTabEntry *tabentry;
@@ -2190,7 +2190,7 @@ do_autovacuum(void)
 			table_oids = lappend_oid(table_oids, relid);
 	}
 
-	heap_endscan(relScan);
+	storage_endscan(relScan);
 	heap_close(classRel, AccessShareLock);
 
 	/*
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index b502c1bb9b..a4cbedd54a 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -36,6 +36,7 @@
 
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/transam.h"
 #include "access/twophase_rmgr.h"
 #include "access/xact.h"
@@ -1221,8 +1222,8 @@ pgstat_collect_oids(Oid catalogid)
 
 	rel = heap_open(catalogid, AccessShareLock);
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	scan = heap_beginscan(rel, snapshot, 0, NULL);
-	while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	scan = storage_beginscan(rel, snapshot, 0, NULL);
+	while ((tup = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Oid			thisoid = HeapTupleGetOid(tup);
 
@@ -1230,7 +1231,7 @@ pgstat_collect_oids(Oid catalogid)
 
 		(void) hash_search(htab, (void *) &thisoid, HASH_ENTER, NULL);
 	}
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 	heap_close(rel, AccessShareLock);
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 24be3cef53..bade3fefc5 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -24,6 +24,7 @@
 #include "access/heapam.h"
 #include "access/htup.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/xact.h"
 
 #include "catalog/pg_subscription.h"
@@ -124,9 +125,9 @@ get_subscription_list(void)
 	(void) GetTransactionSnapshot();
 
 	rel = heap_open(SubscriptionRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(rel, 0, NULL);
+	scan = storage_beginscan_catalog(rel, 0, NULL);
 
-	while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+	while (HeapTupleIsValid(tup = storage_getnext(scan, ForwardScanDirection)))
 	{
 		Form_pg_subscription subform = (Form_pg_subscription) GETSTRUCT(tup);
 		Subscription *sub;
@@ -152,7 +153,7 @@ get_subscription_list(void)
 		MemoryContextSwitchTo(oldcxt);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(rel, AccessShareLock);
 
 	CommitTransactionCommand();
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 247809927a..c6791c758c 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -17,6 +17,7 @@
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/multixact.h"
+#include "access/storageam.h"
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
@@ -435,13 +436,13 @@ DefineQueryRewrite(const char *rulename,
 								RelationGetRelationName(event_relation))));
 
 			snapshot = RegisterSnapshot(GetLatestSnapshot());
-			scanDesc = heap_beginscan(event_relation, snapshot, 0, NULL);
-			if (heap_getnext(scanDesc, ForwardScanDirection) != NULL)
+			scanDesc = storage_beginscan(event_relation, snapshot, 0, NULL);
+			if (storage_getnext(scanDesc, ForwardScanDirection) != NULL)
 				ereport(ERROR,
 						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 						 errmsg("could not convert table \"%s\" to a view because it is not empty",
 								RelationGetRelationName(event_relation))));
-			heap_endscan(scanDesc);
+			storage_endscan(scanDesc);
 			UnregisterSnapshot(snapshot);
 
 			if (event_relation->rd_rel->relhastriggers)
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 20f1d279e9..03b7cc76d7 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -22,6 +22,7 @@
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/session.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "access/xlog.h"
@@ -1212,10 +1213,10 @@ ThereIsAtLeastOneRole(void)
 
 	pg_authid_rel = heap_open(AuthIdRelationId, AccessShareLock);
 
-	scan = heap_beginscan_catalog(pg_authid_rel, 0, NULL);
-	result = (heap_getnext(scan, ForwardScanDirection) != NULL);
+	scan = storage_beginscan_catalog(pg_authid_rel, 0, NULL);
+	result = (storage_getnext(scan, ForwardScanDirection) != NULL);
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(pg_authid_rel, AccessShareLock);
 
 	return result;
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 94ab6e6aa1..4b16de02bd 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -108,26 +108,25 @@ typedef struct ParallelHeapScanDescData *ParallelHeapScanDesc;
 #define HeapScanIsValid(scan) PointerIsValid(scan)
 
 extern HeapScanDesc heap_beginscan(Relation relation, Snapshot snapshot,
-			   int nkeys, ScanKey key);
-extern HeapScanDesc heap_beginscan_catalog(Relation relation, int nkeys,
-					   ScanKey key);
-extern HeapScanDesc heap_beginscan_strat(Relation relation, Snapshot snapshot,
-					 int nkeys, ScanKey key,
-					 bool allow_strat, bool allow_sync);
-extern HeapScanDesc heap_beginscan_bm(Relation relation, Snapshot snapshot,
-				  int nkeys, ScanKey key);
-extern HeapScanDesc heap_beginscan_sampling(Relation relation,
-						Snapshot snapshot, int nkeys, ScanKey key,
-						bool allow_strat, bool allow_sync, bool allow_pagemode);
+			   int nkeys, ScanKey key,
+			   ParallelHeapScanDesc parallel_scan,
+			   bool allow_strat,
+			   bool allow_sync,
+			   bool allow_pagemode,
+			   bool is_bitmapscan,
+			   bool is_samplescan,
+			   bool temp_snap);
 extern void heap_setscanlimits(HeapScanDesc scan, BlockNumber startBlk,
 				   BlockNumber endBlk);
 extern void heapgetpage(HeapScanDesc scan, BlockNumber page);
-extern void heap_rescan(HeapScanDesc scan, ScanKey key);
+extern void heap_rescan(HeapScanDesc scan, ScanKey key, bool set_params,
+			bool allow_strat, bool allow_sync, bool allow_pagemode);
 extern void heap_rescan_set_params(HeapScanDesc scan, ScanKey key,
 					   bool allow_strat, bool allow_sync, bool allow_pagemode);
 extern void heap_endscan(HeapScanDesc scan);
-extern HeapTuple heap_getnext(HeapScanDesc scan, ScanDirection direction);
-
+extern StorageTuple heap_getnext(HeapScanDesc scan, ScanDirection direction);
+extern TupleTableSlot *heap_getnextslot(HeapScanDesc sscan, ScanDirection direction,
+				 TupleTableSlot *slot);
 extern Size heap_parallelscan_estimate(Snapshot snapshot);
 extern void heap_parallelscan_initialize(ParallelHeapScanDesc target,
 							 Relation relation, Snapshot snapshot);
diff --git a/src/include/access/storage_common.h b/src/include/access/storage_common.h
index 0c43ae4310..f9a1075339 100644
--- a/src/include/access/storage_common.h
+++ b/src/include/access/storage_common.h
@@ -27,6 +27,7 @@
 
 /* A physical tuple coming from a storage AM scan */
 typedef void *StorageTuple;
+typedef void *StorageScanDesc;
 
 /*
  * slot storage routine functions
diff --git a/src/include/access/storageam.h b/src/include/access/storageam.h
index 542e44629e..b08d7f401e 100644
--- a/src/include/access/storageam.h
+++ b/src/include/access/storageam.h
@@ -15,10 +15,59 @@
 #define STORAGEAM_H
 
 #include "access/heapam.h"
-#include "access/storageamapi.h"
+#include "access/storage_common.h"
 #include "executor/tuptable.h"
 #include "nodes/execnodes.h"
 
+typedef union tuple_data
+{
+	TransactionId xid;
+	CommandId	cid;
+	ItemPointerData tid;
+}			tuple_data;
+
+typedef enum tuple_data_flags
+{
+	XMIN = 0,
+	UPDATED_XID,
+	CMIN,
+	TID,
+	CTID
+}			tuple_data_flags;
+
+/* Function pointer to let the index tuple insert from storage am */
+typedef List *(*InsertIndexTuples) (TupleTableSlot *slot, EState *estate, bool noDupErr,
+									bool *specConflict, List *arbiterIndexes);
+
+
+extern HeapScanDesc storage_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan);
+
+extern void storage_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
+extern HeapScanDesc storage_beginscan(Relation relation, Snapshot snapshot,
+				  int nkeys, ScanKey key);
+extern HeapScanDesc storage_beginscan_catalog(Relation relation, int nkeys, ScanKey key);
+extern HeapScanDesc storage_beginscan_strat(Relation relation, Snapshot snapshot,
+						int nkeys, ScanKey key,
+						bool allow_strat, bool allow_sync);
+extern HeapScanDesc storage_beginscan_bm(Relation relation, Snapshot snapshot,
+					 int nkeys, ScanKey key);
+extern HeapScanDesc storage_beginscan_sampling(Relation relation, Snapshot snapshot,
+						   int nkeys, ScanKey key,
+						   bool allow_strat, bool allow_sync, bool allow_pagemode);
+
+extern void storage_endscan(HeapScanDesc scan);
+extern void storage_rescan(HeapScanDesc scan, ScanKey key);
+extern void storage_rescan_set_params(HeapScanDesc scan, ScanKey key,
+						  bool allow_strat, bool allow_sync, bool allow_pagemode);
+extern void storage_update_snapshot(HeapScanDesc scan, Snapshot snapshot);
+
+extern StorageTuple storage_getnext(HeapScanDesc sscan, ScanDirection direction);
+extern TupleTableSlot *storage_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *slot);
+
+extern void storage_get_latest_tid(Relation relation,
+					   Snapshot snapshot,
+					   ItemPointer tid);
+
 extern bool storage_fetch(Relation relation,
 			  ItemPointer tid,
 			  Snapshot snapshot,
@@ -27,6 +76,13 @@ extern bool storage_fetch(Relation relation,
 			  bool keep_buf,
 			  Relation stats_relation);
 
+extern bool storage_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
+						  Snapshot snapshot, HeapTuple heapTuple,
+						  bool *all_dead, bool first_call);
+
+extern bool storage_hot_search(ItemPointer tid, Relation relation, Snapshot snapshot,
+				   bool *all_dead);
+
 extern HTSU_Result storage_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * stuple,
 				   CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 				   bool follow_updates,
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
index ed458ce5c3..4ea2ac4388 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -12,33 +12,12 @@
 #define STORAGEAMAPI_H
 
 #include "access/heapam.h"
-#include "access/storage_common.h"
+#include "access/storageam.h"
 #include "nodes/execnodes.h"
 #include "nodes/nodes.h"
 #include "utils/snapshot.h"
 #include "fmgr.h"
 
-typedef union tuple_data
-{
-	TransactionId xid;
-	CommandId	cid;
-	ItemPointerData tid;
-}			tuple_data;
-
-typedef enum tuple_data_flags
-{
-	XMIN = 0,
-	UPDATED_XID,
-	CMIN,
-	TID,
-	CTID
-}			tuple_data_flags;
-
-/* Function pointer to let the index tuple insert from storage am */
-typedef List *(*InsertIndexTuples) (TupleTableSlot *slot, EState *estate, bool noDupErr,
-									bool *specConflict, List *arbiterIndexes);
-
-
 /*
  * Storage routine function hooks
  */
@@ -101,6 +80,39 @@ typedef StorageTuple(*TupleFromDatum_function) (Datum data, Oid tableoid);
 
 typedef void (*RelationSync_function) (Relation relation);
 
+
+typedef HeapScanDesc (*ScanBegin_function) (Relation relation,
+											Snapshot snapshot,
+											int nkeys, ScanKey key,
+											ParallelHeapScanDesc parallel_scan,
+											bool allow_strat,
+											bool allow_sync,
+											bool allow_pagemode,
+											bool is_bitmapscan,
+											bool is_samplescan,
+											bool temp_snap);
+typedef void (*ScanSetlimits_function) (HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
+
+/* must return a TupleTableSlot? */
+typedef StorageTuple(*ScanGetnext_function) (HeapScanDesc scan,
+											 ScanDirection direction);
+
+typedef TupleTableSlot *(*ScanGetnextSlot_function) (HeapScanDesc scan,
+													 ScanDirection direction, TupleTableSlot *slot);
+
+typedef void (*ScanEnd_function) (HeapScanDesc scan);
+
+
+typedef void (*ScanGetpage_function) (HeapScanDesc scan, BlockNumber page);
+typedef void (*ScanRescan_function) (HeapScanDesc scan, ScanKey key, bool set_params,
+									 bool allow_strat, bool allow_sync, bool allow_pagemode);
+typedef void (*ScanUpdateSnapshot_function) (HeapScanDesc scan, Snapshot snapshot);
+
+typedef bool (*HotSearchBuffer_function) (ItemPointer tid, Relation relation,
+										  Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
+										  bool *all_dead, bool first_call);
+
+
 /*
  * API struct for a storage AM.  Note this must be stored in a single palloc'd
  * chunk of memory.
@@ -134,6 +146,17 @@ typedef struct StorageAmRoutine
 
 	RelationSync_function relation_sync;	/* heap_sync */
 
+	/* Operations on relation scans */
+	ScanBegin_function scan_begin;
+	ScanSetlimits_function scansetlimits;
+	ScanGetnext_function scan_getnext;
+	ScanGetnextSlot_function scan_getnextslot;
+	ScanEnd_function scan_end;
+	ScanGetpage_function scan_getpage;
+	ScanRescan_function scan_rescan;
+	ScanUpdateSnapshot_function scan_update_snapshot;
+	HotSearchBuffer_function hot_search_buffer; /* heap_hot_search_buffer */
+
 }			StorageAmRoutine;
 
 extern StorageAmRoutine * GetStorageAmRoutine(Oid amhandler);
diff --git a/src/include/executor/functions.h b/src/include/executor/functions.h
index 718d8947a3..7f9bef1374 100644
--- a/src/include/executor/functions.h
+++ b/src/include/executor/functions.h
@@ -22,7 +22,7 @@ typedef struct SQLFunctionParseInfo *SQLFunctionParseInfoPtr;
 
 extern Datum fmgr_sql(PG_FUNCTION_ARGS);
 
-extern SQLFunctionParseInfoPtr prepare_sql_fn_parse_info(HeapTuple procedureTuple,
+extern SQLFunctionParseInfoPtr prepare_sql_fn_parse_info(StorageTuple procedureTuple,
 						  Node *call_expr,
 						  Oid inputCollation);
 
diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h
index acade7e92e..2781975530 100644
--- a/src/include/executor/spi.h
+++ b/src/include/executor/spi.h
@@ -25,7 +25,7 @@ typedef struct SPITupleTable
 	uint64		alloced;		/* # of alloced vals */
 	uint64		free;			/* # of free vals */
 	TupleDesc	tupdesc;		/* tuple descriptor */
-	HeapTuple  *vals;			/* tuples */
+	StorageTuple *vals;			/* tuples */
 	slist_node	next;			/* link for internal bookkeeping */
 	SubTransactionId subid;		/* subxact in which tuptable was created */
 } SPITupleTable;
@@ -117,10 +117,10 @@ extern const char *SPI_result_code_string(int code);
 extern List *SPI_plan_get_plan_sources(SPIPlanPtr plan);
 extern CachedPlan *SPI_plan_get_cached_plan(SPIPlanPtr plan);
 
-extern HeapTuple SPI_copytuple(HeapTuple tuple);
-extern HeapTupleHeader SPI_returntuple(HeapTuple tuple, TupleDesc tupdesc);
-extern HeapTuple SPI_modifytuple(Relation rel, HeapTuple tuple, int natts,
-				int *attnum, Datum *Values, const char *Nulls);
+extern StorageTuple SPI_copytuple(StorageTuple tuple);
+extern HeapTupleHeader SPI_returntuple(StorageTuple tuple, TupleDesc tupdesc);
+extern StorageTuple SPI_modifytuple(Relation rel, HeapTuple tuple, int natts,
+									int *attnum, Datum *Values, const char *Nulls);
 extern int	SPI_fnumber(TupleDesc tupdesc, const char *fname);
 extern char *SPI_fname(TupleDesc tupdesc, int fnumber);
 extern char *SPI_getvalue(HeapTuple tuple, TupleDesc tupdesc, int fnumber);
@@ -133,7 +133,7 @@ extern void *SPI_palloc(Size size);
 extern void *SPI_repalloc(void *pointer, Size size);
 extern void SPI_pfree(void *pointer);
 extern Datum SPI_datumTransfer(Datum value, bool typByVal, int typLen);
-extern void SPI_freetuple(HeapTuple pointer);
+extern void SPI_freetuple(StorageTuple pointer);
 extern void SPI_freetuptable(SPITupleTable *tuptable);
 
 extern Portal SPI_cursor_open(const char *name, SPIPlanPtr plan,
diff --git a/src/include/executor/tqueue.h b/src/include/executor/tqueue.h
index fdc9deb2b2..9905ecd8ba 100644
--- a/src/include/executor/tqueue.h
+++ b/src/include/executor/tqueue.h
@@ -26,7 +26,7 @@ extern DestReceiver *CreateTupleQueueDestReceiver(shm_mq_handle *handle);
 /* Use these to receive tuples from a shm_mq. */
 extern TupleQueueReader *CreateTupleQueueReader(shm_mq_handle *handle);
 extern void DestroyTupleQueueReader(TupleQueueReader *reader);
-extern HeapTuple TupleQueueReaderNext(TupleQueueReader *reader,
-					 bool nowait, bool *done);
+extern StorageTuple TupleQueueReaderNext(TupleQueueReader *reader,
+										 bool nowait, bool *done);
 
 #endif							/* TQUEUE_H */
diff --git a/src/include/funcapi.h b/src/include/funcapi.h
index 223eef28d1..54b37f7db5 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -237,7 +237,7 @@ extern TupleDesc TypeGetTupleDesc(Oid typeoid, List *colaliases);
 /* from execTuples.c */
 extern TupleDesc BlessTupleDesc(TupleDesc tupdesc);
 extern AttInMetadata *TupleDescGetAttInMetadata(TupleDesc tupdesc);
-extern HeapTuple BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values);
+extern StorageTuple BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values);
 extern Datum HeapTupleHeaderGetDatum(HeapTupleHeader tuple);
 extern TupleTableSlot *TupleDescGetSlot(TupleDesc tupdesc);
 
-- 
2.15.0.windows.1



  [application/octet-stream] 0008-Remove-HeapScanDesc-usage-outside-heap.patch (95.6K, ../../CAJrrPGc951F-R4Kfa4W47B5vHKeHsB2Y34zewp=b+AWSkF9RVA@mail.gmail.com/12-0008-Remove-HeapScanDesc-usage-outside-heap.patch)
  download | inline diff:
From b6d92d8a8d0fc0826de0c1618540e04b4255989d Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Wed, 27 Dec 2017 13:03:24 +1100
Subject: [PATCH 08/11] Remove HeapScanDesc usage outside heap

HeapScanDesc is divided into two scan descriptors.
StorageScanDesc and HeapPageScanDesc.

StorageScanDesc has common members that are should
be available across all the storage routines and
HeapPageScanDesc is avaiable only for the storage
routine that supports Heap storage with page format.
The HeapPageScanDesc is used internally by the heapam
storage routine and also this is exposed to Bitmap Heap
and Sample scan's as they depend on the Heap page format.

while generating the Bitmap Heap and Sample scan's,
the planner now checks whether the storage routine
supports returning HeapPageScanDesc or not? Based on
this decision, the planner plans above two plans.
---
 contrib/pgrowlocks/pgrowlocks.c            |   4 +-
 contrib/pgstattuple/pgstattuple.c          |  10 +-
 contrib/tsm_system_rows/tsm_system_rows.c  |  18 +-
 contrib/tsm_system_time/tsm_system_time.c  |   8 +-
 src/backend/access/heap/heapam.c           | 424 +++++++++++++++--------------
 src/backend/access/heap/heapam_storage.c   |  53 ++++
 src/backend/access/index/genam.c           |   4 +-
 src/backend/access/storage/storageam.c     |  51 +++-
 src/backend/access/tablesample/system.c    |   2 +-
 src/backend/bootstrap/bootstrap.c          |   4 +-
 src/backend/catalog/aclchk.c               |   4 +-
 src/backend/catalog/index.c                |   8 +-
 src/backend/catalog/partition.c            |   2 +-
 src/backend/catalog/pg_conversion.c        |   2 +-
 src/backend/catalog/pg_db_role_setting.c   |   2 +-
 src/backend/catalog/pg_publication.c       |   2 +-
 src/backend/catalog/pg_subscription.c      |   2 +-
 src/backend/commands/cluster.c             |   4 +-
 src/backend/commands/copy.c                |   2 +-
 src/backend/commands/dbcommands.c          |   6 +-
 src/backend/commands/indexcmds.c           |   2 +-
 src/backend/commands/tablecmds.c           |  10 +-
 src/backend/commands/tablespace.c          |  10 +-
 src/backend/commands/typecmds.c            |   4 +-
 src/backend/commands/vacuum.c              |   4 +-
 src/backend/executor/nodeBitmapHeapscan.c  |  69 +++--
 src/backend/executor/nodeSamplescan.c      |  50 ++--
 src/backend/executor/nodeSeqscan.c         |   5 +-
 src/backend/optimizer/util/plancat.c       |   4 +-
 src/backend/postmaster/autovacuum.c        |   4 +-
 src/backend/postmaster/pgstat.c            |   2 +-
 src/backend/replication/logical/launcher.c |   2 +-
 src/backend/rewrite/rewriteDefine.c        |   2 +-
 src/backend/utils/init/postinit.c          |   2 +-
 src/include/access/heapam.h                |  36 +--
 src/include/access/relscan.h               |  47 ++--
 src/include/access/storage_common.h        |   1 -
 src/include/access/storageam.h             |  44 +--
 src/include/access/storageamapi.h          |  42 +--
 src/include/nodes/execnodes.h              |   4 +-
 40 files changed, 535 insertions(+), 421 deletions(-)

diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index bc8b423975..762d969ecd 100644
--- a/contrib/pgrowlocks/pgrowlocks.c
+++ b/contrib/pgrowlocks/pgrowlocks.c
@@ -56,7 +56,7 @@ PG_FUNCTION_INFO_V1(pgrowlocks);
 typedef struct
 {
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	int			ncolumns;
 } MyData;
 
@@ -71,7 +71,7 @@ Datum
 pgrowlocks(PG_FUNCTION_ARGS)
 {
 	FuncCallContext *funcctx;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	TupleDesc	tupdesc;
 	AttInMetadata *attinmeta;
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index c4b10d6efc..32ac121e92 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -314,7 +314,8 @@ pgstat_relation(Relation rel, FunctionCallInfo fcinfo)
 static Datum
 pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
+	HeapPageScanDesc pagescan;
 	HeapTuple	tuple;
 	BlockNumber nblocks;
 	BlockNumber block = 0;		/* next block to count free space in */
@@ -328,7 +329,8 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 	scan = storage_beginscan_strat(rel, SnapshotAny, 0, NULL, true, false);
 	InitDirtySnapshot(SnapshotDirty);
 
-	nblocks = scan->rs_nblocks; /* # blocks to be scanned */
+	pagescan = storageam_get_heappagescandesc(scan);
+	nblocks = pagescan->rs_nblocks; /* # blocks to be scanned */
 
 	/* scan the relation */
 	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
@@ -364,7 +366,7 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 			CHECK_FOR_INTERRUPTS();
 
 			buffer = ReadBufferExtended(rel, MAIN_FORKNUM, block,
-										RBM_NORMAL, scan->rs_strategy);
+										RBM_NORMAL, pagescan->rs_strategy);
 			LockBuffer(buffer, BUFFER_LOCK_SHARE);
 			stat.free_space += PageGetHeapFreeSpace((Page) BufferGetPage(buffer));
 			UnlockReleaseBuffer(buffer);
@@ -377,7 +379,7 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 		CHECK_FOR_INTERRUPTS();
 
 		buffer = ReadBufferExtended(rel, MAIN_FORKNUM, block,
-									RBM_NORMAL, scan->rs_strategy);
+									RBM_NORMAL, pagescan->rs_strategy);
 		LockBuffer(buffer, BUFFER_LOCK_SHARE);
 		stat.free_space += PageGetHeapFreeSpace((Page) BufferGetPage(buffer));
 		UnlockReleaseBuffer(buffer);
diff --git a/contrib/tsm_system_rows/tsm_system_rows.c b/contrib/tsm_system_rows/tsm_system_rows.c
index 544458ec91..14120291d0 100644
--- a/contrib/tsm_system_rows/tsm_system_rows.c
+++ b/contrib/tsm_system_rows/tsm_system_rows.c
@@ -71,7 +71,7 @@ static BlockNumber system_rows_nextsampleblock(SampleScanState *node);
 static OffsetNumber system_rows_nextsampletuple(SampleScanState *node,
 							BlockNumber blockno,
 							OffsetNumber maxoffset);
-static bool SampleOffsetVisible(OffsetNumber tupoffset, HeapScanDesc scan);
+static bool SampleOffsetVisible(OffsetNumber tupoffset, HeapPageScanDesc scan);
 static uint32 random_relative_prime(uint32 n, SamplerRandomState randstate);
 
 
@@ -209,7 +209,7 @@ static BlockNumber
 system_rows_nextsampleblock(SampleScanState *node)
 {
 	SystemRowsSamplerData *sampler = (SystemRowsSamplerData *) node->tsm_state;
-	HeapScanDesc scan = node->ss.ss_currentScanDesc;
+	HeapPageScanDesc pagescan = node->pagescan;
 
 	/* First call within scan? */
 	if (sampler->doneblocks == 0)
@@ -221,14 +221,14 @@ system_rows_nextsampleblock(SampleScanState *node)
 			SamplerRandomState randstate;
 
 			/* If relation is empty, there's nothing to scan */
-			if (scan->rs_nblocks == 0)
+			if (pagescan->rs_nblocks == 0)
 				return InvalidBlockNumber;
 
 			/* We only need an RNG during this setup step */
 			sampler_random_init_state(sampler->seed, randstate);
 
 			/* Compute nblocks/firstblock/step only once per query */
-			sampler->nblocks = scan->rs_nblocks;
+			sampler->nblocks = pagescan->rs_nblocks;
 
 			/* Choose random starting block within the relation */
 			/* (Actually this is the predecessor of the first block visited) */
@@ -258,7 +258,7 @@ system_rows_nextsampleblock(SampleScanState *node)
 	{
 		/* Advance lb, using uint64 arithmetic to forestall overflow */
 		sampler->lb = ((uint64) sampler->lb + sampler->step) % sampler->nblocks;
-	} while (sampler->lb >= scan->rs_nblocks);
+	} while (sampler->lb >= pagescan->rs_nblocks);
 
 	return sampler->lb;
 }
@@ -278,7 +278,7 @@ system_rows_nextsampletuple(SampleScanState *node,
 							OffsetNumber maxoffset)
 {
 	SystemRowsSamplerData *sampler = (SystemRowsSamplerData *) node->tsm_state;
-	HeapScanDesc scan = node->ss.ss_currentScanDesc;
+	HeapPageScanDesc pagescan = node->pagescan;
 	OffsetNumber tupoffset = sampler->lt;
 
 	/* Quit if we've returned all needed tuples */
@@ -291,7 +291,7 @@ system_rows_nextsampletuple(SampleScanState *node,
 	 */
 
 	/* We rely on the data accumulated in pagemode access */
-	Assert(scan->rs_pageatatime);
+	Assert(pagescan->rs_pageatatime);
 	for (;;)
 	{
 		/* Advance to next possible offset on page */
@@ -308,7 +308,7 @@ system_rows_nextsampletuple(SampleScanState *node,
 		}
 
 		/* Found a candidate? */
-		if (SampleOffsetVisible(tupoffset, scan))
+		if (SampleOffsetVisible(tupoffset, pagescan))
 		{
 			sampler->donetuples++;
 			break;
@@ -327,7 +327,7 @@ system_rows_nextsampletuple(SampleScanState *node,
  * so just look at the info it left in rs_vistuples[].
  */
 static bool
-SampleOffsetVisible(OffsetNumber tupoffset, HeapScanDesc scan)
+SampleOffsetVisible(OffsetNumber tupoffset, HeapPageScanDesc scan)
 {
 	int			start = 0,
 				end = scan->rs_ntuples - 1;
diff --git a/contrib/tsm_system_time/tsm_system_time.c b/contrib/tsm_system_time/tsm_system_time.c
index af8d025414..aa7252215a 100644
--- a/contrib/tsm_system_time/tsm_system_time.c
+++ b/contrib/tsm_system_time/tsm_system_time.c
@@ -219,7 +219,7 @@ static BlockNumber
 system_time_nextsampleblock(SampleScanState *node)
 {
 	SystemTimeSamplerData *sampler = (SystemTimeSamplerData *) node->tsm_state;
-	HeapScanDesc scan = node->ss.ss_currentScanDesc;
+	HeapPageScanDesc pagescan = node->pagescan;
 	instr_time	cur_time;
 
 	/* First call within scan? */
@@ -232,14 +232,14 @@ system_time_nextsampleblock(SampleScanState *node)
 			SamplerRandomState randstate;
 
 			/* If relation is empty, there's nothing to scan */
-			if (scan->rs_nblocks == 0)
+			if (pagescan->rs_nblocks == 0)
 				return InvalidBlockNumber;
 
 			/* We only need an RNG during this setup step */
 			sampler_random_init_state(sampler->seed, randstate);
 
 			/* Compute nblocks/firstblock/step only once per query */
-			sampler->nblocks = scan->rs_nblocks;
+			sampler->nblocks = pagescan->rs_nblocks;
 
 			/* Choose random starting block within the relation */
 			/* (Actually this is the predecessor of the first block visited) */
@@ -275,7 +275,7 @@ system_time_nextsampleblock(SampleScanState *node)
 	{
 		/* Advance lb, using uint64 arithmetic to forestall overflow */
 		sampler->lb = ((uint64) sampler->lb + sampler->step) % sampler->nblocks;
-	} while (sampler->lb >= scan->rs_nblocks);
+	} while (sampler->lb >= pagescan->rs_nblocks);
 
 	return sampler->lb;
 }
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 5226f39f1e..90bbed9900 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -220,9 +220,9 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
 	 * lock that ensures the interesting tuple(s) won't change.)
 	 */
 	if (scan->rs_parallel != NULL)
-		scan->rs_nblocks = scan->rs_parallel->phs_nblocks;
+		scan->rs_pagescan.rs_nblocks = scan->rs_parallel->phs_nblocks;
 	else
-		scan->rs_nblocks = RelationGetNumberOfBlocks(scan->rs_rd);
+		scan->rs_pagescan.rs_nblocks = RelationGetNumberOfBlocks(scan->rs_scan.rs_rd);
 
 	/*
 	 * If the table is large relative to NBuffers, use a bulk-read access
@@ -236,8 +236,8 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
 	 * Note that heap_parallelscan_initialize has a very similar test; if you
 	 * change this, consider changing that one, too.
 	 */
-	if (!RelationUsesLocalBuffers(scan->rs_rd) &&
-		scan->rs_nblocks > NBuffers / 4)
+	if (!RelationUsesLocalBuffers(scan->rs_scan.rs_rd) &&
+		scan->rs_pagescan.rs_nblocks > NBuffers / 4)
 	{
 		allow_strat = scan->rs_allow_strat;
 		allow_sync = scan->rs_allow_sync;
@@ -248,20 +248,20 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
 	if (allow_strat)
 	{
 		/* During a rescan, keep the previous strategy object. */
-		if (scan->rs_strategy == NULL)
-			scan->rs_strategy = GetAccessStrategy(BAS_BULKREAD);
+		if (scan->rs_pagescan.rs_strategy == NULL)
+			scan->rs_pagescan.rs_strategy = GetAccessStrategy(BAS_BULKREAD);
 	}
 	else
 	{
-		if (scan->rs_strategy != NULL)
-			FreeAccessStrategy(scan->rs_strategy);
-		scan->rs_strategy = NULL;
+		if (scan->rs_pagescan.rs_strategy != NULL)
+			FreeAccessStrategy(scan->rs_pagescan.rs_strategy);
+		scan->rs_pagescan.rs_strategy = NULL;
 	}
 
 	if (scan->rs_parallel != NULL)
 	{
 		/* For parallel scan, believe whatever ParallelHeapScanDesc says. */
-		scan->rs_syncscan = scan->rs_parallel->phs_syncscan;
+		scan->rs_pagescan.rs_syncscan = scan->rs_parallel->phs_syncscan;
 	}
 	else if (keep_startblock)
 	{
@@ -270,25 +270,25 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
 		 * so that rewinding a cursor doesn't generate surprising results.
 		 * Reset the active syncscan setting, though.
 		 */
-		scan->rs_syncscan = (allow_sync && synchronize_seqscans);
+		scan->rs_pagescan.rs_syncscan = (allow_sync && synchronize_seqscans);
 	}
 	else if (allow_sync && synchronize_seqscans)
 	{
-		scan->rs_syncscan = true;
-		scan->rs_startblock = ss_get_location(scan->rs_rd, scan->rs_nblocks);
+		scan->rs_pagescan.rs_syncscan = true;
+		scan->rs_pagescan.rs_startblock = ss_get_location(scan->rs_scan.rs_rd, scan->rs_pagescan.rs_nblocks);
 	}
 	else
 	{
-		scan->rs_syncscan = false;
-		scan->rs_startblock = 0;
+		scan->rs_pagescan.rs_syncscan = false;
+		scan->rs_pagescan.rs_startblock = 0;
 	}
 
-	scan->rs_numblocks = InvalidBlockNumber;
-	scan->rs_inited = false;
+	scan->rs_pagescan.rs_numblocks = InvalidBlockNumber;
+	scan->rs_scan.rs_inited = false;
 	scan->rs_ctup.t_data = NULL;
 	ItemPointerSetInvalid(&scan->rs_ctup.t_self);
-	scan->rs_cbuf = InvalidBuffer;
-	scan->rs_cblock = InvalidBlockNumber;
+	scan->rs_scan.rs_cbuf = InvalidBuffer;
+	scan->rs_scan.rs_cblock = InvalidBlockNumber;
 
 	/* page-at-a-time fields are always invalid when not rs_inited */
 
@@ -296,7 +296,7 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
 	 * copy the scan key, if appropriate
 	 */
 	if (key != NULL)
-		memcpy(scan->rs_key, key, scan->rs_nkeys * sizeof(ScanKeyData));
+		memcpy(scan->rs_scan.rs_key, key, scan->rs_scan.rs_nkeys * sizeof(ScanKeyData));
 
 	/*
 	 * Currently, we don't have a stats counter for bitmap heap scans (but the
@@ -304,7 +304,7 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
 	 * update stats for tuple fetches there)
 	 */
 	if (!scan->rs_bitmapscan && !scan->rs_samplescan)
-		pgstat_count_heap_scan(scan->rs_rd);
+		pgstat_count_heap_scan(scan->rs_scan.rs_rd);
 }
 
 /*
@@ -314,16 +314,19 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
  * numBlks is number of pages to scan (InvalidBlockNumber means "all")
  */
 void
-heap_setscanlimits(HeapScanDesc scan, BlockNumber startBlk, BlockNumber numBlks)
+heap_setscanlimits(StorageScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
 {
-	Assert(!scan->rs_inited);	/* else too late to change */
-	Assert(!scan->rs_syncscan); /* else rs_startblock is significant */
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
+	Assert(!scan->rs_scan.rs_inited);	/* else too late to change */
+	Assert(!scan->rs_pagescan.rs_syncscan); /* else rs_startblock is
+											 * significant */
 
 	/* Check startBlk is valid (but allow case of zero blocks...) */
-	Assert(startBlk == 0 || startBlk < scan->rs_nblocks);
+	Assert(startBlk == 0 || startBlk < scan->rs_pagescan.rs_nblocks);
 
-	scan->rs_startblock = startBlk;
-	scan->rs_numblocks = numBlks;
+	scan->rs_pagescan.rs_startblock = startBlk;
+	scan->rs_pagescan.rs_numblocks = numBlks;
 }
 
 /*
@@ -334,8 +337,9 @@ heap_setscanlimits(HeapScanDesc scan, BlockNumber startBlk, BlockNumber numBlks)
  * which tuples on the page are visible.
  */
 void
-heapgetpage(HeapScanDesc scan, BlockNumber page)
+heapgetpage(StorageScanDesc sscan, BlockNumber page)
 {
+	HeapScanDesc scan = (HeapScanDesc) sscan;
 	Buffer		buffer;
 	Snapshot	snapshot;
 	Page		dp;
@@ -345,13 +349,13 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 	ItemId		lpp;
 	bool		all_visible;
 
-	Assert(page < scan->rs_nblocks);
+	Assert(page < scan->rs_pagescan.rs_nblocks);
 
 	/* release previous scan buffer, if any */
-	if (BufferIsValid(scan->rs_cbuf))
+	if (BufferIsValid(scan->rs_scan.rs_cbuf))
 	{
-		ReleaseBuffer(scan->rs_cbuf);
-		scan->rs_cbuf = InvalidBuffer;
+		ReleaseBuffer(scan->rs_scan.rs_cbuf);
+		scan->rs_scan.rs_cbuf = InvalidBuffer;
 	}
 
 	/*
@@ -362,20 +366,20 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 	CHECK_FOR_INTERRUPTS();
 
 	/* read page using selected strategy */
-	scan->rs_cbuf = ReadBufferExtended(scan->rs_rd, MAIN_FORKNUM, page,
-									   RBM_NORMAL, scan->rs_strategy);
-	scan->rs_cblock = page;
+	scan->rs_scan.rs_cbuf = ReadBufferExtended(scan->rs_scan.rs_rd, MAIN_FORKNUM, page,
+											   RBM_NORMAL, scan->rs_pagescan.rs_strategy);
+	scan->rs_scan.rs_cblock = page;
 
-	if (!scan->rs_pageatatime)
+	if (!scan->rs_pagescan.rs_pageatatime)
 		return;
 
-	buffer = scan->rs_cbuf;
-	snapshot = scan->rs_snapshot;
+	buffer = scan->rs_scan.rs_cbuf;
+	snapshot = scan->rs_scan.rs_snapshot;
 
 	/*
 	 * Prune and repair fragmentation for the whole page, if possible.
 	 */
-	heap_page_prune_opt(scan->rs_rd, buffer);
+	heap_page_prune_opt(scan->rs_scan.rs_rd, buffer);
 
 	/*
 	 * We must hold share lock on the buffer content while examining tuple
@@ -385,7 +389,7 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 	LockBuffer(buffer, BUFFER_LOCK_SHARE);
 
 	dp = BufferGetPage(buffer);
-	TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+	TestForOldSnapshot(snapshot, scan->rs_scan.rs_rd, dp);
 	lines = PageGetMaxOffsetNumber(dp);
 	ntup = 0;
 
@@ -420,7 +424,7 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 			HeapTupleData loctup;
 			bool		valid;
 
-			loctup.t_tableOid = RelationGetRelid(scan->rs_rd);
+			loctup.t_tableOid = RelationGetRelid(scan->rs_scan.rs_rd);
 			loctup.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
 			loctup.t_len = ItemIdGetLength(lpp);
 			ItemPointerSet(&(loctup.t_self), page, lineoff);
@@ -428,20 +432,20 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 			if (all_visible)
 				valid = true;
 			else
-				valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine, &loctup, snapshot, buffer);
+				valid = HeapTupleSatisfiesVisibility(scan->rs_scan.rs_rd->rd_stamroutine, &loctup, snapshot, buffer);
 
-			CheckForSerializableConflictOut(valid, scan->rs_rd, &loctup,
+			CheckForSerializableConflictOut(valid, scan->rs_scan.rs_rd, &loctup,
 											buffer, snapshot);
 
 			if (valid)
-				scan->rs_vistuples[ntup++] = lineoff;
+				scan->rs_pagescan.rs_vistuples[ntup++] = lineoff;
 		}
 	}
 
 	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
 
 	Assert(ntup <= MaxHeapTuplesPerPage);
-	scan->rs_ntuples = ntup;
+	scan->rs_pagescan.rs_ntuples = ntup;
 }
 
 /* ----------------
@@ -474,7 +478,7 @@ heapgettup(HeapScanDesc scan,
 		   ScanKey key)
 {
 	HeapTuple	tuple = &(scan->rs_ctup);
-	Snapshot	snapshot = scan->rs_snapshot;
+	Snapshot	snapshot = scan->rs_scan.rs_snapshot;
 	bool		backward = ScanDirectionIsBackward(dir);
 	BlockNumber page;
 	bool		finished;
@@ -489,14 +493,14 @@ heapgettup(HeapScanDesc scan,
 	 */
 	if (ScanDirectionIsForward(dir))
 	{
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
 			/*
 			 * return null immediately if relation is empty
 			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			if (scan->rs_pagescan.rs_nblocks == 0 || scan->rs_pagescan.rs_numblocks == 0)
 			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
+				Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 				tuple->t_data = NULL;
 				return;
 			}
@@ -509,29 +513,29 @@ heapgettup(HeapScanDesc scan,
 				/* Other processes might have already finished the scan. */
 				if (page == InvalidBlockNumber)
 				{
-					Assert(!BufferIsValid(scan->rs_cbuf));
+					Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 					tuple->t_data = NULL;
 					return;
 				}
 			}
 			else
-				page = scan->rs_startblock; /* first page */
-			heapgetpage(scan, page);
+				page = scan->rs_pagescan.rs_startblock; /* first page */
+			heapgetpage((StorageScanDesc) scan, page);
 			lineoff = FirstOffsetNumber;	/* first offnum */
-			scan->rs_inited = true;
+			scan->rs_scan.rs_inited = true;
 		}
 		else
 		{
 			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
+			page = scan->rs_scan.rs_cblock; /* current page */
 			lineoff =			/* next offnum */
 				OffsetNumberNext(ItemPointerGetOffsetNumber(&(tuple->t_self)));
 		}
 
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_SHARE);
 
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_scan.rs_rd, dp);
 		lines = PageGetMaxOffsetNumber(dp);
 		/* page and lineoff now reference the physically next tid */
 
@@ -542,14 +546,14 @@ heapgettup(HeapScanDesc scan,
 		/* backward parallel scan not supported */
 		Assert(scan->rs_parallel == NULL);
 
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
 			/*
 			 * return null immediately if relation is empty
 			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			if (scan->rs_pagescan.rs_nblocks == 0 || scan->rs_pagescan.rs_numblocks == 0)
 			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
+				Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 				tuple->t_data = NULL;
 				return;
 			}
@@ -560,30 +564,30 @@ heapgettup(HeapScanDesc scan,
 			 * time, and much more likely that we'll just bollix things for
 			 * forward scanners.
 			 */
-			scan->rs_syncscan = false;
+			scan->rs_pagescan.rs_syncscan = false;
 			/* start from last page of the scan */
-			if (scan->rs_startblock > 0)
-				page = scan->rs_startblock - 1;
+			if (scan->rs_pagescan.rs_startblock > 0)
+				page = scan->rs_pagescan.rs_startblock - 1;
 			else
-				page = scan->rs_nblocks - 1;
-			heapgetpage(scan, page);
+				page = scan->rs_pagescan.rs_nblocks - 1;
+			heapgetpage((StorageScanDesc) scan, page);
 		}
 		else
 		{
 			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
+			page = scan->rs_scan.rs_cblock; /* current page */
 		}
 
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_SHARE);
 
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_scan.rs_rd, dp);
 		lines = PageGetMaxOffsetNumber(dp);
 
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
 			lineoff = lines;	/* final offnum */
-			scan->rs_inited = true;
+			scan->rs_scan.rs_inited = true;
 		}
 		else
 		{
@@ -599,20 +603,20 @@ heapgettup(HeapScanDesc scan,
 		/*
 		 * ``no movement'' scan direction: refetch prior tuple
 		 */
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
-			Assert(!BufferIsValid(scan->rs_cbuf));
+			Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 			tuple->t_data = NULL;
 			return;
 		}
 
 		page = ItemPointerGetBlockNumber(&(tuple->t_self));
-		if (page != scan->rs_cblock)
-			heapgetpage(scan, page);
+		if (page != scan->rs_scan.rs_cblock)
+			heapgetpage((StorageScanDesc) scan, page);
 
 		/* Since the tuple was previously fetched, needn't lock page here */
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_scan.rs_rd, dp);
 		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
 		lpp = PageGetItemId(dp, lineoff);
 		Assert(ItemIdIsNormal(lpp));
@@ -643,21 +647,21 @@ heapgettup(HeapScanDesc scan,
 				/*
 				 * if current tuple qualifies, return it.
 				 */
-				valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine,
+				valid = HeapTupleSatisfiesVisibility(scan->rs_scan.rs_rd->rd_stamroutine,
 													 tuple,
 													 snapshot,
-													 scan->rs_cbuf);
+													 scan->rs_scan.rs_cbuf);
 
-				CheckForSerializableConflictOut(valid, scan->rs_rd, tuple,
-												scan->rs_cbuf, snapshot);
+				CheckForSerializableConflictOut(valid, scan->rs_scan.rs_rd, tuple,
+												scan->rs_scan.rs_cbuf, snapshot);
 
 				if (valid && key != NULL)
-					HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
+					HeapKeyTest(tuple, RelationGetDescr(scan->rs_scan.rs_rd),
 								nkeys, key, valid);
 
 				if (valid)
 				{
-					LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+					LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_UNLOCK);
 					return;
 				}
 			}
@@ -682,17 +686,17 @@ heapgettup(HeapScanDesc scan,
 		 * if we get here, it means we've exhausted the items on this page and
 		 * it's time to move to the next.
 		 */
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_UNLOCK);
 
 		/*
 		 * advance to next/prior page and detect end of scan
 		 */
 		if (backward)
 		{
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+			finished = (page == scan->rs_pagescan.rs_startblock) ||
+				(scan->rs_pagescan.rs_numblocks != InvalidBlockNumber ? --scan->rs_pagescan.rs_numblocks == 0 : false);
 			if (page == 0)
-				page = scan->rs_nblocks;
+				page = scan->rs_pagescan.rs_nblocks;
 			page--;
 		}
 		else if (scan->rs_parallel != NULL)
@@ -703,10 +707,10 @@ heapgettup(HeapScanDesc scan,
 		else
 		{
 			page++;
-			if (page >= scan->rs_nblocks)
+			if (page >= scan->rs_pagescan.rs_nblocks)
 				page = 0;
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+			finished = (page == scan->rs_pagescan.rs_startblock) ||
+				(scan->rs_pagescan.rs_numblocks != InvalidBlockNumber ? --scan->rs_pagescan.rs_numblocks == 0 : false);
 
 			/*
 			 * Report our new scan position for synchronization purposes. We
@@ -720,8 +724,8 @@ heapgettup(HeapScanDesc scan,
 			 * a little bit backwards on every invocation, which is confusing.
 			 * We don't guarantee any specific ordering in general, though.
 			 */
-			if (scan->rs_syncscan)
-				ss_report_location(scan->rs_rd, page);
+			if (scan->rs_pagescan.rs_syncscan)
+				ss_report_location(scan->rs_scan.rs_rd, page);
 		}
 
 		/*
@@ -729,21 +733,21 @@ heapgettup(HeapScanDesc scan,
 		 */
 		if (finished)
 		{
-			if (BufferIsValid(scan->rs_cbuf))
-				ReleaseBuffer(scan->rs_cbuf);
-			scan->rs_cbuf = InvalidBuffer;
-			scan->rs_cblock = InvalidBlockNumber;
+			if (BufferIsValid(scan->rs_scan.rs_cbuf))
+				ReleaseBuffer(scan->rs_scan.rs_cbuf);
+			scan->rs_scan.rs_cbuf = InvalidBuffer;
+			scan->rs_scan.rs_cblock = InvalidBlockNumber;
 			tuple->t_data = NULL;
-			scan->rs_inited = false;
+			scan->rs_scan.rs_inited = false;
 			return;
 		}
 
-		heapgetpage(scan, page);
+		heapgetpage((StorageScanDesc) scan, page);
 
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_SHARE);
 
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_scan.rs_rd, dp);
 		lines = PageGetMaxOffsetNumber((Page) dp);
 		linesleft = lines;
 		if (backward)
@@ -794,14 +798,14 @@ heapgettup_pagemode(HeapScanDesc scan,
 	 */
 	if (ScanDirectionIsForward(dir))
 	{
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
 			/*
 			 * return null immediately if relation is empty
 			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			if (scan->rs_pagescan.rs_nblocks == 0 || scan->rs_pagescan.rs_numblocks == 0)
 			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
+				Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 				tuple->t_data = NULL;
 				return;
 			}
@@ -814,28 +818,28 @@ heapgettup_pagemode(HeapScanDesc scan,
 				/* Other processes might have already finished the scan. */
 				if (page == InvalidBlockNumber)
 				{
-					Assert(!BufferIsValid(scan->rs_cbuf));
+					Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 					tuple->t_data = NULL;
 					return;
 				}
 			}
 			else
-				page = scan->rs_startblock; /* first page */
-			heapgetpage(scan, page);
+				page = scan->rs_pagescan.rs_startblock; /* first page */
+			heapgetpage((StorageScanDesc) scan, page);
 			lineindex = 0;
-			scan->rs_inited = true;
+			scan->rs_scan.rs_inited = true;
 		}
 		else
 		{
 			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
-			lineindex = scan->rs_cindex + 1;
+			page = scan->rs_scan.rs_cblock; /* current page */
+			lineindex = scan->rs_pagescan.rs_cindex + 1;
 		}
 
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_SHARE);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(scan->rs_scan.rs_snapshot, scan->rs_scan.rs_rd, dp);
+		lines = scan->rs_pagescan.rs_ntuples;
 		/* page and lineindex now reference the next visible tid */
 
 		linesleft = lines - lineindex;
@@ -845,14 +849,14 @@ heapgettup_pagemode(HeapScanDesc scan,
 		/* backward parallel scan not supported */
 		Assert(scan->rs_parallel == NULL);
 
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
 			/*
 			 * return null immediately if relation is empty
 			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			if (scan->rs_pagescan.rs_nblocks == 0 || scan->rs_pagescan.rs_numblocks == 0)
 			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
+				Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 				tuple->t_data = NULL;
 				return;
 			}
@@ -863,33 +867,33 @@ heapgettup_pagemode(HeapScanDesc scan,
 			 * time, and much more likely that we'll just bollix things for
 			 * forward scanners.
 			 */
-			scan->rs_syncscan = false;
+			scan->rs_pagescan.rs_syncscan = false;
 			/* start from last page of the scan */
-			if (scan->rs_startblock > 0)
-				page = scan->rs_startblock - 1;
+			if (scan->rs_pagescan.rs_startblock > 0)
+				page = scan->rs_pagescan.rs_startblock - 1;
 			else
-				page = scan->rs_nblocks - 1;
-			heapgetpage(scan, page);
+				page = scan->rs_pagescan.rs_nblocks - 1;
+			heapgetpage((StorageScanDesc) scan, page);
 		}
 		else
 		{
 			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
+			page = scan->rs_scan.rs_cblock; /* current page */
 		}
 
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_SHARE);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(scan->rs_scan.rs_snapshot, scan->rs_scan.rs_rd, dp);
+		lines = scan->rs_pagescan.rs_ntuples;
 
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
 			lineindex = lines - 1;
-			scan->rs_inited = true;
+			scan->rs_scan.rs_inited = true;
 		}
 		else
 		{
-			lineindex = scan->rs_cindex - 1;
+			lineindex = scan->rs_pagescan.rs_cindex - 1;
 		}
 		/* page and lineindex now reference the previous visible tid */
 
@@ -900,20 +904,20 @@ heapgettup_pagemode(HeapScanDesc scan,
 		/*
 		 * ``no movement'' scan direction: refetch prior tuple
 		 */
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
-			Assert(!BufferIsValid(scan->rs_cbuf));
+			Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 			tuple->t_data = NULL;
 			return;
 		}
 
 		page = ItemPointerGetBlockNumber(&(tuple->t_self));
-		if (page != scan->rs_cblock)
-			heapgetpage(scan, page);
+		if (page != scan->rs_scan.rs_cblock)
+			heapgetpage((StorageScanDesc) scan, page);
 
 		/* Since the tuple was previously fetched, needn't lock page here */
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(scan->rs_scan.rs_snapshot, scan->rs_scan.rs_rd, dp);
 		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
 		lpp = PageGetItemId(dp, lineoff);
 		Assert(ItemIdIsNormal(lpp));
@@ -922,8 +926,8 @@ heapgettup_pagemode(HeapScanDesc scan,
 		tuple->t_len = ItemIdGetLength(lpp);
 
 		/* check that rs_cindex is in sync */
-		Assert(scan->rs_cindex < scan->rs_ntuples);
-		Assert(lineoff == scan->rs_vistuples[scan->rs_cindex]);
+		Assert(scan->rs_pagescan.rs_cindex < scan->rs_pagescan.rs_ntuples);
+		Assert(lineoff == scan->rs_pagescan.rs_vistuples[scan->rs_pagescan.rs_cindex]);
 
 		return;
 	}
@@ -936,7 +940,7 @@ heapgettup_pagemode(HeapScanDesc scan,
 	{
 		while (linesleft > 0)
 		{
-			lineoff = scan->rs_vistuples[lineindex];
+			lineoff = scan->rs_pagescan.rs_vistuples[lineindex];
 			lpp = PageGetItemId(dp, lineoff);
 			Assert(ItemIdIsNormal(lpp));
 
@@ -947,7 +951,7 @@ heapgettup_pagemode(HeapScanDesc scan,
 			/*
 			 * if current tuple qualifies, return it.
 			 */
-			if (HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine, tuple, scan->rs_snapshot, scan->rs_cbuf))
+			if (HeapTupleSatisfiesVisibility(scan->rs_scan.rs_rd->rd_stamroutine, tuple, scan->rs_scan.rs_snapshot, scan->rs_scan.rs_cbuf))
 			{
 				/*
 				 * if current tuple qualifies, return it.
@@ -956,19 +960,19 @@ heapgettup_pagemode(HeapScanDesc scan,
 				{
 					bool		valid;
 
-					HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
+					HeapKeyTest(tuple, RelationGetDescr(scan->rs_scan.rs_rd),
 								nkeys, key, valid);
 					if (valid)
 					{
-						scan->rs_cindex = lineindex;
-						LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+						scan->rs_pagescan.rs_cindex = lineindex;
+						LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_UNLOCK);
 						return;
 					}
 				}
 				else
 				{
-					scan->rs_cindex = lineindex;
-					LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+					scan->rs_pagescan.rs_cindex = lineindex;
+					LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_UNLOCK);
 					return;
 				}
 			}
@@ -987,7 +991,7 @@ heapgettup_pagemode(HeapScanDesc scan,
 		 * if we get here, it means we've exhausted the items on this page and
 		 * it's time to move to the next.
 		 */
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_UNLOCK);
 
 		/*
 		 * if we get here, it means we've exhausted the items on this page and
@@ -995,10 +999,10 @@ heapgettup_pagemode(HeapScanDesc scan,
 		 */
 		if (backward)
 		{
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+			finished = (page == scan->rs_pagescan.rs_startblock) ||
+				(scan->rs_pagescan.rs_numblocks != InvalidBlockNumber ? --scan->rs_pagescan.rs_numblocks == 0 : false);
 			if (page == 0)
-				page = scan->rs_nblocks;
+				page = scan->rs_pagescan.rs_nblocks;
 			page--;
 		}
 		else if (scan->rs_parallel != NULL)
@@ -1009,10 +1013,10 @@ heapgettup_pagemode(HeapScanDesc scan,
 		else
 		{
 			page++;
-			if (page >= scan->rs_nblocks)
+			if (page >= scan->rs_pagescan.rs_nblocks)
 				page = 0;
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+			finished = (page == scan->rs_pagescan.rs_startblock) ||
+				(scan->rs_pagescan.rs_numblocks != InvalidBlockNumber ? --scan->rs_pagescan.rs_numblocks == 0 : false);
 
 			/*
 			 * Report our new scan position for synchronization purposes. We
@@ -1026,8 +1030,8 @@ heapgettup_pagemode(HeapScanDesc scan,
 			 * a little bit backwards on every invocation, which is confusing.
 			 * We don't guarantee any specific ordering in general, though.
 			 */
-			if (scan->rs_syncscan)
-				ss_report_location(scan->rs_rd, page);
+			if (scan->rs_pagescan.rs_syncscan)
+				ss_report_location(scan->rs_scan.rs_rd, page);
 		}
 
 		/*
@@ -1035,21 +1039,21 @@ heapgettup_pagemode(HeapScanDesc scan,
 		 */
 		if (finished)
 		{
-			if (BufferIsValid(scan->rs_cbuf))
-				ReleaseBuffer(scan->rs_cbuf);
-			scan->rs_cbuf = InvalidBuffer;
-			scan->rs_cblock = InvalidBlockNumber;
+			if (BufferIsValid(scan->rs_scan.rs_cbuf))
+				ReleaseBuffer(scan->rs_scan.rs_cbuf);
+			scan->rs_scan.rs_cbuf = InvalidBuffer;
+			scan->rs_scan.rs_cblock = InvalidBlockNumber;
 			tuple->t_data = NULL;
-			scan->rs_inited = false;
+			scan->rs_scan.rs_inited = false;
 			return;
 		}
 
-		heapgetpage(scan, page);
+		heapgetpage((StorageScanDesc) scan, page);
 
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_SHARE);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(scan->rs_scan.rs_snapshot, scan->rs_scan.rs_rd, dp);
+		lines = scan->rs_pagescan.rs_ntuples;
 		linesleft = lines;
 		if (backward)
 			lineindex = lines - 1;
@@ -1376,7 +1380,7 @@ heap_openrv_extended(const RangeVar *relation, LOCKMODE lockmode,
 	return r;
 }
 
-HeapScanDesc
+StorageScanDesc
 heap_beginscan(Relation relation, Snapshot snapshot,
 			   int nkeys, ScanKey key,
 			   ParallelHeapScanDesc parallel_scan,
@@ -1403,12 +1407,12 @@ heap_beginscan(Relation relation, Snapshot snapshot,
 	 */
 	scan = (HeapScanDesc) palloc(sizeof(HeapScanDescData));
 
-	scan->rs_rd = relation;
-	scan->rs_snapshot = snapshot;
-	scan->rs_nkeys = nkeys;
+	scan->rs_scan.rs_rd = relation;
+	scan->rs_scan.rs_snapshot = snapshot;
+	scan->rs_scan.rs_nkeys = nkeys;
 	scan->rs_bitmapscan = is_bitmapscan;
 	scan->rs_samplescan = is_samplescan;
-	scan->rs_strategy = NULL;	/* set in initscan */
+	scan->rs_pagescan.rs_strategy = NULL;	/* set in initscan */
 	scan->rs_allow_strat = allow_strat;
 	scan->rs_allow_sync = allow_sync;
 	scan->rs_temp_snap = temp_snap;
@@ -1417,7 +1421,7 @@ heap_beginscan(Relation relation, Snapshot snapshot,
 	/*
 	 * we can use page-at-a-time mode if it's an MVCC-safe snapshot
 	 */
-	scan->rs_pageatatime = allow_pagemode && IsMVCCSnapshot(snapshot);
+	scan->rs_pagescan.rs_pageatatime = allow_pagemode && IsMVCCSnapshot(snapshot);
 
 	/*
 	 * For a seqscan in a serializable transaction, acquire a predicate lock
@@ -1441,13 +1445,13 @@ heap_beginscan(Relation relation, Snapshot snapshot,
 	 * initscan() and we don't want to allocate memory again
 	 */
 	if (nkeys > 0)
-		scan->rs_key = (ScanKey) palloc(sizeof(ScanKeyData) * nkeys);
+		scan->rs_scan.rs_key = (ScanKey) palloc(sizeof(ScanKeyData) * nkeys);
 	else
-		scan->rs_key = NULL;
+		scan->rs_scan.rs_key = NULL;
 
 	initscan(scan, key, false);
 
-	return scan;
+	return (StorageScanDesc) scan;
 }
 
 /* ----------------
@@ -1455,21 +1459,23 @@ heap_beginscan(Relation relation, Snapshot snapshot,
  * ----------------
  */
 void
-heap_rescan(HeapScanDesc scan, ScanKey key, bool set_params,
+heap_rescan(StorageScanDesc sscan, ScanKey key, bool set_params,
 			bool allow_strat, bool allow_sync, bool allow_pagemode)
 {
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
 	if (set_params)
 	{
 		scan->rs_allow_strat = allow_strat;
 		scan->rs_allow_sync = allow_sync;
-		scan->rs_pageatatime = allow_pagemode && IsMVCCSnapshot(scan->rs_snapshot);
+		scan->rs_pagescan.rs_pageatatime = allow_pagemode && IsMVCCSnapshot(scan->rs_scan.rs_snapshot);
 	}
 
 	/*
 	 * unpin scan buffers
 	 */
-	if (BufferIsValid(scan->rs_cbuf))
-		ReleaseBuffer(scan->rs_cbuf);
+	if (BufferIsValid(scan->rs_scan.rs_cbuf))
+		ReleaseBuffer(scan->rs_scan.rs_cbuf);
 
 	/*
 	 * reinitialize scan descriptor
@@ -1500,29 +1506,31 @@ heap_rescan(HeapScanDesc scan, ScanKey key, bool set_params,
  * ----------------
  */
 void
-heap_endscan(HeapScanDesc scan)
+heap_endscan(StorageScanDesc sscan)
 {
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
 	/* Note: no locking manipulations needed */
 
 	/*
 	 * unpin scan buffers
 	 */
-	if (BufferIsValid(scan->rs_cbuf))
-		ReleaseBuffer(scan->rs_cbuf);
+	if (BufferIsValid(scan->rs_scan.rs_cbuf))
+		ReleaseBuffer(scan->rs_scan.rs_cbuf);
 
 	/*
 	 * decrement relation reference count and free scan descriptor storage
 	 */
-	RelationDecrementReferenceCount(scan->rs_rd);
+	RelationDecrementReferenceCount(scan->rs_scan.rs_rd);
 
-	if (scan->rs_key)
-		pfree(scan->rs_key);
+	if (scan->rs_scan.rs_key)
+		pfree(scan->rs_scan.rs_key);
 
-	if (scan->rs_strategy != NULL)
-		FreeAccessStrategy(scan->rs_strategy);
+	if (scan->rs_pagescan.rs_strategy != NULL)
+		FreeAccessStrategy(scan->rs_pagescan.rs_strategy);
 
 	if (scan->rs_temp_snap)
-		UnregisterSnapshot(scan->rs_snapshot);
+		UnregisterSnapshot(scan->rs_scan.rs_snapshot);
 
 	pfree(scan);
 }
@@ -1618,7 +1626,7 @@ retry:
 		else
 		{
 			SpinLockRelease(&parallel_scan->phs_mutex);
-			sync_startpage = ss_get_location(scan->rs_rd, scan->rs_nblocks);
+			sync_startpage = ss_get_location(scan->rs_scan.rs_rd, scan->rs_pagescan.rs_nblocks);
 			goto retry;
 		}
 	}
@@ -1660,10 +1668,10 @@ heap_parallelscan_nextpage(HeapScanDesc scan)
 	 * starting block number, modulo nblocks.
 	 */
 	nallocated = pg_atomic_fetch_add_u64(&parallel_scan->phs_nallocated, 1);
-	if (nallocated >= scan->rs_nblocks)
+	if (nallocated >= scan->rs_pagescan.rs_nblocks)
 		page = InvalidBlockNumber;	/* all blocks have been allocated */
 	else
-		page = (nallocated + parallel_scan->phs_startblock) % scan->rs_nblocks;
+		page = (nallocated + parallel_scan->phs_startblock) % scan->rs_pagescan.rs_nblocks;
 
 	/*
 	 * Report scan location.  Normally, we report the current page number.
@@ -1672,12 +1680,12 @@ heap_parallelscan_nextpage(HeapScanDesc scan)
 	 * doesn't slew backwards.  We only report the position at the end of the
 	 * scan once, though: subsequent callers will report nothing.
 	 */
-	if (scan->rs_syncscan)
+	if (scan->rs_pagescan.rs_syncscan)
 	{
 		if (page != InvalidBlockNumber)
-			ss_report_location(scan->rs_rd, page);
-		else if (nallocated == scan->rs_nblocks)
-			ss_report_location(scan->rs_rd, parallel_scan->phs_startblock);
+			ss_report_location(scan->rs_scan.rs_rd, page);
+		else if (nallocated == scan->rs_pagescan.rs_nblocks)
+			ss_report_location(scan->rs_scan.rs_rd, parallel_scan->phs_startblock);
 	}
 
 	return page;
@@ -1690,12 +1698,14 @@ heap_parallelscan_nextpage(HeapScanDesc scan)
  * ----------------
  */
 void
-heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
+heap_update_snapshot(StorageScanDesc sscan, Snapshot snapshot)
 {
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
 	Assert(IsMVCCSnapshot(snapshot));
 
 	RegisterSnapshot(snapshot);
-	scan->rs_snapshot = snapshot;
+	scan->rs_scan.rs_snapshot = snapshot;
 	scan->rs_temp_snap = true;
 }
 
@@ -1723,17 +1733,19 @@ heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
 #endif							/* !defined(HEAPDEBUGALL) */
 
 StorageTuple
-heap_getnext(HeapScanDesc scan, ScanDirection direction)
+heap_getnext(StorageScanDesc sscan, ScanDirection direction)
 {
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
 	/* Note: no locking manipulations needed */
 
 	HEAPDEBUG_1;				/* heap_getnext( info ) */
 
-	if (scan->rs_pageatatime)
+	if (scan->rs_pagescan.rs_pageatatime)
 		heapgettup_pagemode(scan, direction,
-							scan->rs_nkeys, scan->rs_key);
+							scan->rs_scan.rs_nkeys, scan->rs_scan.rs_key);
 	else
-		heapgettup(scan, direction, scan->rs_nkeys, scan->rs_key);
+		heapgettup(scan, direction, scan->rs_scan.rs_nkeys, scan->rs_scan.rs_key);
 
 	if (scan->rs_ctup.t_data == NULL)
 	{
@@ -1747,7 +1759,7 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 	 */
 	HEAPDEBUG_3;				/* heap_getnext returning tuple */
 
-	pgstat_count_heap_getnext(scan->rs_rd);
+	pgstat_count_heap_getnext(scan->rs_scan.rs_rd);
 
 	return heap_copytuple(&(scan->rs_ctup));
 }
@@ -1755,7 +1767,7 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 #ifdef HEAPAMSLOTDEBUGALL
 #define HEAPAMSLOTDEBUG_1 \
 	elog(DEBUG2, "heapam_getnext([%s,nkeys=%d],dir=%d) called", \
-		 RelationGetRelationName(scan->rs_rd), scan->rs_nkeys, (int) direction)
+		 RelationGetRelationName(scan->rs_scan.rs_rd), scan->rs_scan.rs_nkeys, (int) direction)
 #define HEAPAMSLOTDEBUG_2 \
 	elog(DEBUG2, "heapam_getnext returning EOS")
 #define HEAPAMSLOTDEBUG_3 \
@@ -1767,7 +1779,7 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 #endif
 
 TupleTableSlot *
-heap_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
+heap_getnextslot(StorageScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
 {
 	HeapScanDesc scan = (HeapScanDesc) sscan;
 
@@ -1775,11 +1787,11 @@ heap_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *sl
 
 	HEAPAMSLOTDEBUG_1;			/* heap_getnext( info ) */
 
-	if (scan->rs_pageatatime)
+	if (scan->rs_pagescan.rs_pageatatime)
 		heapgettup_pagemode(scan, direction,
-							scan->rs_nkeys, scan->rs_key);
+							scan->rs_scan.rs_nkeys, scan->rs_scan.rs_key);
 	else
-		heapgettup(scan, direction, scan->rs_nkeys, scan->rs_key);
+		heapgettup(scan, direction, scan->rs_scan.rs_nkeys, scan->rs_scan.rs_key);
 
 	if (scan->rs_ctup.t_data == NULL)
 	{
@@ -1794,7 +1806,7 @@ heap_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *sl
 	 */
 	HEAPAMSLOTDEBUG_3;			/* heap_getnext returning tuple */
 
-	pgstat_count_heap_getnext(scan->rs_rd);
+	pgstat_count_heap_getnext(scan->rs_scan.rs_rd);
 	return ExecStoreTuple(heap_copytuple(&(scan->rs_ctup)),
 						  slot, InvalidBuffer, true);
 }
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index e7b669a2dd..ea2b640693 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -21,7 +21,9 @@
 #include "postgres.h"
 
 #include "access/heapam.h"
+#include "access/relscan.h"
 #include "access/storageamapi.h"
+#include "pgstat.h"
 #include "utils/builtins.h"
 #include "utils/rel.h"
 
@@ -241,6 +243,44 @@ heapam_form_tuple_by_datum(Datum data, Oid tableoid)
 	return heap_form_tuple_by_datum(data, tableoid);
 }
 
+static ParallelHeapScanDesc
+heapam_get_parallelheapscandesc(StorageScanDesc sscan)
+{
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
+	return scan->rs_parallel;
+}
+
+static HeapPageScanDesc
+heapam_get_heappagescandesc(StorageScanDesc sscan)
+{
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
+	return &scan->rs_pagescan;
+}
+
+static StorageTuple
+heapam_fetch_tuple_from_offset(StorageScanDesc sscan, BlockNumber blkno, OffsetNumber offset)
+{
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+	Page		dp;
+	ItemId		lp;
+
+	dp = (Page) BufferGetPage(scan->rs_scan.rs_cbuf);
+	lp = PageGetItemId(dp, offset);
+	Assert(ItemIdIsNormal(lp));
+
+	scan->rs_ctup.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lp);
+	scan->rs_ctup.t_len = ItemIdGetLength(lp);
+	scan->rs_ctup.t_tableOid = scan->rs_scan.rs_rd->rd_id;
+	ItemPointerSet(&scan->rs_ctup.t_self, blkno, offset);
+
+	pgstat_count_heap_fetch(scan->rs_scan.rs_rd);
+
+	return &(scan->rs_ctup);
+}
+
+
 Datum
 heapam_storage_handler(PG_FUNCTION_ARGS)
 {
@@ -261,6 +301,19 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 	amroutine->scan_rescan = heap_rescan;
 	amroutine->scan_update_snapshot = heap_update_snapshot;
 	amroutine->hot_search_buffer = heap_hot_search_buffer;
+	amroutine->scan_fetch_tuple_from_offset = heapam_fetch_tuple_from_offset;
+
+	/*
+	 * The following routine needs to be provided when the storage support
+	 * parallel sequential scan
+	 */
+	amroutine->scan_get_parallelheapscandesc = heapam_get_parallelheapscandesc;
+
+	/*
+	 * The following routine needs to be provided when the storage support
+	 * BitmapHeap and Sample Scans
+	 */
+	amroutine->scan_get_heappagescandesc = heapam_get_heappagescandesc;
 
 	amroutine->tuple_fetch = heapam_fetch;
 	amroutine->tuple_insert = heapam_heap_insert;
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 26a9ccb657..ee9352df06 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -478,10 +478,10 @@ systable_recheck_tuple(SysScanDesc sysscan, HeapTuple tup)
 	}
 	else
 	{
-		HeapScanDesc scan = sysscan->scan;
+		StorageScanDesc scan = sysscan->scan;
 
 		Assert(IsMVCCSnapshot(scan->rs_snapshot));
-		Assert(tup == &scan->rs_ctup);
+		/* hari Assert(tup == &scan->rs_ctup); */
 		Assert(BufferIsValid(scan->rs_cbuf));
 		/* must hold a buffer lock to call HeapTupleSatisfiesVisibility */
 		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
diff --git a/src/backend/access/storage/storageam.c b/src/backend/access/storage/storageam.c
index 75e69db6ab..f14673e88e 100644
--- a/src/backend/access/storage/storageam.c
+++ b/src/backend/access/storage/storageam.c
@@ -56,7 +56,7 @@ storage_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * stuple,
  *		Caller must hold a suitable lock on the correct relation.
  * ----------------
  */
-HeapScanDesc
+StorageScanDesc
 storage_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan)
 {
 	Snapshot	snapshot;
@@ -69,6 +69,25 @@ storage_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan
 												true, true, true, false, false, true);
 }
 
+ParallelHeapScanDesc
+storageam_get_parallelheapscandesc(StorageScanDesc sscan)
+{
+	return sscan->rs_rd->rd_stamroutine->scan_get_parallelheapscandesc(sscan);
+}
+
+HeapPageScanDesc
+storageam_get_heappagescandesc(StorageScanDesc sscan)
+{
+	/*
+	 * Planner should have already validated whether the current storage
+	 * supports Page scans are not? This function will be called only from
+	 * Bitmap Heap scan and sample scan
+	 */
+	Assert(sscan->rs_rd->rd_stamroutine->scan_get_heappagescandesc != NULL);
+
+	return sscan->rs_rd->rd_stamroutine->scan_get_heappagescandesc(sscan);
+}
+
 /*
  * heap_setscanlimits - restrict range of a heapscan
  *
@@ -76,7 +95,7 @@ storage_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan
  * numBlks is number of pages to scan (InvalidBlockNumber means "all")
  */
 void
-storage_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
+storage_setscanlimits(StorageScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
 {
 	sscan->rs_rd->rd_stamroutine->scansetlimits(sscan, startBlk, numBlks);
 }
@@ -106,7 +125,7 @@ storage_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numB
  * also allows control of whether page-mode visibility checking is used.
  * ----------------
  */
-HeapScanDesc
+StorageScanDesc
 storage_beginscan(Relation relation, Snapshot snapshot,
 				  int nkeys, ScanKey key)
 {
@@ -114,7 +133,7 @@ storage_beginscan(Relation relation, Snapshot snapshot,
 												true, true, true, false, false, false);
 }
 
-HeapScanDesc
+StorageScanDesc
 storage_beginscan_catalog(Relation relation, int nkeys, ScanKey key)
 {
 	Oid			relid = RelationGetRelid(relation);
@@ -124,7 +143,7 @@ storage_beginscan_catalog(Relation relation, int nkeys, ScanKey key)
 												true, true, true, false, false, true);
 }
 
-HeapScanDesc
+StorageScanDesc
 storage_beginscan_strat(Relation relation, Snapshot snapshot,
 						int nkeys, ScanKey key,
 						bool allow_strat, bool allow_sync)
@@ -134,7 +153,7 @@ storage_beginscan_strat(Relation relation, Snapshot snapshot,
 												false, false, false);
 }
 
-HeapScanDesc
+StorageScanDesc
 storage_beginscan_bm(Relation relation, Snapshot snapshot,
 					 int nkeys, ScanKey key)
 {
@@ -142,7 +161,7 @@ storage_beginscan_bm(Relation relation, Snapshot snapshot,
 												false, false, true, true, false, false);
 }
 
-HeapScanDesc
+StorageScanDesc
 storage_beginscan_sampling(Relation relation, Snapshot snapshot,
 						   int nkeys, ScanKey key,
 						   bool allow_strat, bool allow_sync, bool allow_pagemode)
@@ -157,7 +176,7 @@ storage_beginscan_sampling(Relation relation, Snapshot snapshot,
  * ----------------
  */
 void
-storage_rescan(HeapScanDesc scan,
+storage_rescan(StorageScanDesc scan,
 			   ScanKey key)
 {
 	scan->rs_rd->rd_stamroutine->scan_rescan(scan, key, false, false, false, false);
@@ -173,7 +192,7 @@ storage_rescan(HeapScanDesc scan,
  * ----------------
  */
 void
-storage_rescan_set_params(HeapScanDesc scan, ScanKey key,
+storage_rescan_set_params(StorageScanDesc scan, ScanKey key,
 						  bool allow_strat, bool allow_sync, bool allow_pagemode)
 {
 	scan->rs_rd->rd_stamroutine->scan_rescan(scan, key, true,
@@ -188,7 +207,7 @@ storage_rescan_set_params(HeapScanDesc scan, ScanKey key,
  * ----------------
  */
 void
-storage_endscan(HeapScanDesc scan)
+storage_endscan(StorageScanDesc scan)
 {
 	scan->rs_rd->rd_stamroutine->scan_end(scan);
 }
@@ -201,23 +220,29 @@ storage_endscan(HeapScanDesc scan)
  * ----------------
  */
 void
-storage_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
+storage_update_snapshot(StorageScanDesc scan, Snapshot snapshot)
 {
 	scan->rs_rd->rd_stamroutine->scan_update_snapshot(scan, snapshot);
 }
 
 StorageTuple
-storage_getnext(HeapScanDesc sscan, ScanDirection direction)
+storage_getnext(StorageScanDesc sscan, ScanDirection direction)
 {
 	return sscan->rs_rd->rd_stamroutine->scan_getnext(sscan, direction);
 }
 
 TupleTableSlot *
-storage_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
+storage_getnextslot(StorageScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
 {
 	return sscan->rs_rd->rd_stamroutine->scan_getnextslot(sscan, direction, slot);
 }
 
+StorageTuple
+storage_fetch_tuple_from_offset(StorageScanDesc sscan, BlockNumber blkno, OffsetNumber offset)
+{
+	return sscan->rs_rd->rd_stamroutine->scan_fetch_tuple_from_offset(sscan, blkno, offset);
+}
+
 /*
  * Insert a tuple from a slot into storage AM routine
  */
diff --git a/src/backend/access/tablesample/system.c b/src/backend/access/tablesample/system.c
index e270cbc4a0..8793b95c08 100644
--- a/src/backend/access/tablesample/system.c
+++ b/src/backend/access/tablesample/system.c
@@ -183,7 +183,7 @@ static BlockNumber
 system_nextsampleblock(SampleScanState *node)
 {
 	SystemSamplerData *sampler = (SystemSamplerData *) node->tsm_state;
-	HeapScanDesc scan = node->ss.ss_currentScanDesc;
+	HeapPageScanDesc scan = node->pagescan;
 	BlockNumber nextblock = sampler->nextblock;
 	uint32		hashinput[2];
 
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index a73a363a49..da3d48b9cc 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -577,7 +577,7 @@ boot_openrel(char *relname)
 	int			i;
 	struct typmap **app;
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tup;
 
 	if (strlen(relname) >= NAMEDATALEN)
@@ -893,7 +893,7 @@ gettype(char *type)
 {
 	int			i;
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tup;
 	struct typmap **app;
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 0b3be57a88..c058a1bccd 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -822,7 +822,7 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 					ScanKeyData key[2];
 					int			keycount;
 					Relation	rel;
-					HeapScanDesc scan;
+					StorageScanDesc scan;
 					HeapTuple	tuple;
 
 					keycount = 0;
@@ -880,7 +880,7 @@ getRelationsInNamespace(Oid namespaceId, char relkind)
 	List	   *relations = NIL;
 	ScanKeyData key[2];
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 
 	ScanKeyInit(&key[0],
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 16bb70ef45..1c508767cf 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1900,7 +1900,7 @@ index_update_stats(Relation rel,
 		ReindexIsProcessingHeap(RelationRelationId))
 	{
 		/* don't assume syscache will work */
-		HeapScanDesc pg_class_scan;
+		StorageScanDesc pg_class_scan;
 		ScanKeyData key[1];
 
 		ScanKeyInit(&key[0],
@@ -2213,7 +2213,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 {
 	bool		is_system_catalog;
 	bool		checking_uniqueness;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	heapTuple;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
@@ -2652,7 +2652,7 @@ IndexCheckExclusion(Relation heapRelation,
 					Relation indexRelation,
 					IndexInfo *indexInfo)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	heapTuple;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
@@ -2966,7 +2966,7 @@ validate_index_heapscan(Relation heapRelation,
 						Snapshot snapshot,
 						v_i_state *state)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	heapTuple;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index a81b79d2f1..c6a580abb8 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1266,7 +1266,7 @@ check_default_allows_bound(Relation parent, Relation default_rel,
 		Snapshot	snapshot;
 		TupleDesc	tupdesc;
 		ExprContext *econtext;
-		HeapScanDesc scan;
+		StorageScanDesc scan;
 		MemoryContext oldCxt;
 		TupleTableSlot *tupslot;
 
diff --git a/src/backend/catalog/pg_conversion.c b/src/backend/catalog/pg_conversion.c
index 1d048e6394..842abcc8b5 100644
--- a/src/backend/catalog/pg_conversion.c
+++ b/src/backend/catalog/pg_conversion.c
@@ -151,7 +151,7 @@ RemoveConversionById(Oid conversionOid)
 {
 	Relation	rel;
 	HeapTuple	tuple;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	ScanKeyData scanKeyData;
 
 	ScanKeyInit(&scanKeyData,
diff --git a/src/backend/catalog/pg_db_role_setting.c b/src/backend/catalog/pg_db_role_setting.c
index 517e3101cd..63324cfc8e 100644
--- a/src/backend/catalog/pg_db_role_setting.c
+++ b/src/backend/catalog/pg_db_role_setting.c
@@ -171,7 +171,7 @@ void
 DropSetting(Oid databaseid, Oid roleid)
 {
 	Relation	relsetting;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	ScanKeyData keys[2];
 	HeapTuple	tup;
 	int			numkeys = 0;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 145e3c1d65..5e3915b438 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -314,7 +314,7 @@ GetAllTablesPublicationRelations(void)
 {
 	Relation	classRel;
 	ScanKeyData key[1];
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	List	   *result = NIL;
 
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index a51f2e4dfc..050dfa3e4c 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -377,7 +377,7 @@ void
 RemoveSubscriptionRel(Oid subid, Oid relid)
 {
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	ScanKeyData skey[2];
 	HeapTuple	tup;
 	int			nkeys = 0;
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index e0f6973a3f..ccdbe70ff6 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -746,7 +746,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	Datum	   *values;
 	bool	   *isnull;
 	IndexScanDesc indexScan;
-	HeapScanDesc heapScan;
+	StorageScanDesc heapScan;
 	bool		use_wal;
 	bool		is_system_catalog;
 	TransactionId OldestXmin;
@@ -1638,7 +1638,7 @@ static List *
 get_tables_to_cluster(MemoryContext cluster_context)
 {
 	Relation	indRelation;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	ScanKeyData entry;
 	HeapTuple	indexTuple;
 	Form_pg_index index;
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index f2209d9f81..42803c687b 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2032,7 +2032,7 @@ CopyTo(CopyState cstate)
 	{
 		Datum	   *values;
 		bool	   *nulls;
-		HeapScanDesc scandesc;
+		StorageScanDesc scandesc;
 		HeapTuple	tuple;
 
 		values = (Datum *) palloc(num_phys_attrs * sizeof(Datum));
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 87d651002e..4a224b5bbc 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -99,7 +99,7 @@ static int	errdetail_busy_db(int notherbackends, int npreparedxacts);
 Oid
 createdb(ParseState *pstate, const CreatedbStmt *stmt)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	Relation	rel;
 	Oid			src_dboid;
 	Oid			src_owner;
@@ -1872,7 +1872,7 @@ static void
 remove_dbtablespaces(Oid db_id)
 {
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 
 	rel = heap_open(TableSpaceRelationId, AccessShareLock);
@@ -1939,7 +1939,7 @@ check_db_file_conflict(Oid db_id)
 {
 	bool		result = false;
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 
 	rel = heap_open(TableSpaceRelationId, AccessShareLock);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 400f936a2f..2152aa86e5 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1891,7 +1891,7 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 {
 	Oid			objectOid;
 	Relation	relationRelation;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	ScanKeyData scan_keys[1];
 	HeapTuple	tuple;
 	MemoryContext private_context;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3d4fa831b3..28874a27ae 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4492,7 +4492,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		bool	   *isnull;
 		TupleTableSlot *oldslot;
 		TupleTableSlot *newslot;
-		HeapScanDesc scan;
+		StorageScanDesc scan;
 		HeapTuple	tuple;
 		MemoryContext oldCxt;
 		List	   *dropped_attrs = NIL;
@@ -5065,7 +5065,7 @@ find_typed_table_dependencies(Oid typeOid, const char *typeName, DropBehavior be
 {
 	Relation	classRel;
 	ScanKeyData key[1];
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	List	   *result = NIL;
 
@@ -8219,7 +8219,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	Expr	   *origexpr;
 	ExprState  *exprstate;
 	TupleDesc	tupdesc;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	ExprContext *econtext;
 	MemoryContext oldcxt;
@@ -8302,7 +8302,7 @@ validateForeignKeyConstraint(char *conname,
 							 Oid pkindOid,
 							 Oid constraintOid)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	Trigger		trig;
 	Snapshot	snapshot;
@@ -10809,7 +10809,7 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 	ListCell   *l;
 	ScanKeyData key[1];
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	Oid			orig_tablespaceoid;
 	Oid			new_tablespaceoid;
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 6804182db7..0e2b93a5df 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -402,7 +402,7 @@ DropTableSpace(DropTableSpaceStmt *stmt)
 {
 #ifdef HAVE_SYMLINK
 	char	   *tablespacename = stmt->tablespacename;
-	HeapScanDesc scandesc;
+	StorageScanDesc scandesc;
 	Relation	rel;
 	HeapTuple	tuple;
 	ScanKeyData entry[1];
@@ -913,7 +913,7 @@ RenameTableSpace(const char *oldname, const char *newname)
 	Oid			tspId;
 	Relation	rel;
 	ScanKeyData entry[1];
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tup;
 	HeapTuple	newtuple;
 	Form_pg_tablespace newform;
@@ -988,7 +988,7 @@ AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt)
 {
 	Relation	rel;
 	ScanKeyData entry[1];
-	HeapScanDesc scandesc;
+	StorageScanDesc scandesc;
 	HeapTuple	tup;
 	Oid			tablespaceoid;
 	Datum		datum;
@@ -1382,7 +1382,7 @@ get_tablespace_oid(const char *tablespacename, bool missing_ok)
 {
 	Oid			result;
 	Relation	rel;
-	HeapScanDesc scandesc;
+	StorageScanDesc scandesc;
 	HeapTuple	tuple;
 	ScanKeyData entry[1];
 
@@ -1428,7 +1428,7 @@ get_tablespace_name(Oid spc_oid)
 {
 	char	   *result;
 	Relation	rel;
-	HeapScanDesc scandesc;
+	StorageScanDesc scandesc;
 	HeapTuple	tuple;
 	ScanKeyData entry[1];
 
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 8a339d16be..e539570d9c 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2382,7 +2382,7 @@ AlterDomainNotNull(List *names, bool notNull)
 			RelToCheck *rtc = (RelToCheck *) lfirst(rt);
 			Relation	testrel = rtc->rel;
 			TupleDesc	tupdesc = RelationGetDescr(testrel);
-			HeapScanDesc scan;
+			StorageScanDesc scan;
 			HeapTuple	tuple;
 			Snapshot	snapshot;
 
@@ -2778,7 +2778,7 @@ validateDomainConstraint(Oid domainoid, char *ccbin)
 		RelToCheck *rtc = (RelToCheck *) lfirst(rt);
 		Relation	testrel = rtc->rel;
 		TupleDesc	tupdesc = RelationGetDescr(testrel);
-		HeapScanDesc scan;
+		StorageScanDesc scan;
 		HeapTuple	tuple;
 		Snapshot	snapshot;
 
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 3a8dc8eb0c..42e462861b 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -529,7 +529,7 @@ get_all_vacuum_rels(void)
 {
 	List	   *vacrels = NIL;
 	Relation	pgclass;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 
 	pgclass = heap_open(RelationRelationId, AccessShareLock);
@@ -1183,7 +1183,7 @@ vac_truncate_clog(TransactionId frozenXID,
 {
 	TransactionId nextXID = ReadNewTransactionId();
 	Relation	relation;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	Oid			oldestxid_datoid;
 	Oid			minmulti_datoid;
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index 0c5ae8fa41..fc6c4f909a 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -55,14 +55,14 @@
 
 
 static TupleTableSlot *BitmapHeapNext(BitmapHeapScanState *node);
-static void bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres);
+static void bitgetpage(BitmapHeapScanState *node, TBMIterateResult *tbmres);
 static inline void BitmapDoneInitializingSharedState(
 								  ParallelBitmapHeapState *pstate);
 static inline void BitmapAdjustPrefetchIterator(BitmapHeapScanState *node,
 							 TBMIterateResult *tbmres);
 static inline void BitmapAdjustPrefetchTarget(BitmapHeapScanState *node);
 static inline void BitmapPrefetch(BitmapHeapScanState *node,
-			   HeapScanDesc scan);
+			   StorageScanDesc scan);
 static bool BitmapShouldInitializeSharedState(
 								  ParallelBitmapHeapState *pstate);
 
@@ -77,7 +77,8 @@ static TupleTableSlot *
 BitmapHeapNext(BitmapHeapScanState *node)
 {
 	ExprContext *econtext;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
+	HeapPageScanDesc pagescan;
 	TIDBitmap  *tbm;
 	TBMIterator *tbmiterator = NULL;
 	TBMSharedIterator *shared_tbmiterator = NULL;
@@ -93,6 +94,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 	econtext = node->ss.ps.ps_ExprContext;
 	slot = node->ss.ss_ScanTupleSlot;
 	scan = node->ss.ss_currentScanDesc;
+	pagescan = node->pagescan;
 	tbm = node->tbm;
 	if (pstate == NULL)
 		tbmiterator = node->tbmiterator;
@@ -192,8 +194,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 
 	for (;;)
 	{
-		Page		dp;
-		ItemId		lp;
+		StorageTuple tuple;
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -220,7 +221,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 			 * least AccessShareLock on the table before performing any of the
 			 * indexscans, but let's be safe.)
 			 */
-			if (tbmres->blockno >= scan->rs_nblocks)
+			if (tbmres->blockno >= pagescan->rs_nblocks)
 			{
 				node->tbmres = tbmres = NULL;
 				continue;
@@ -243,14 +244,14 @@ BitmapHeapNext(BitmapHeapScanState *node)
 				 * The number of tuples on this page is put into
 				 * scan->rs_ntuples; note we don't fill scan->rs_vistuples.
 				 */
-				scan->rs_ntuples = tbmres->ntuples;
+				pagescan->rs_ntuples = tbmres->ntuples;
 			}
 			else
 			{
 				/*
 				 * Fetch the current heap page and identify candidate tuples.
 				 */
-				bitgetpage(scan, tbmres);
+				bitgetpage(node, tbmres);
 			}
 
 			if (tbmres->ntuples >= 0)
@@ -261,7 +262,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 			/*
 			 * Set rs_cindex to first slot to examine
 			 */
-			scan->rs_cindex = 0;
+			pagescan->rs_cindex = 0;
 
 			/* Adjust the prefetch target */
 			BitmapAdjustPrefetchTarget(node);
@@ -271,7 +272,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 			/*
 			 * Continuing in previously obtained page; advance rs_cindex
 			 */
-			scan->rs_cindex++;
+			pagescan->rs_cindex++;
 
 #ifdef USE_PREFETCH
 
@@ -298,7 +299,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 		/*
 		 * Out of range?  If so, nothing more to look at on this page
 		 */
-		if (scan->rs_cindex < 0 || scan->rs_cindex >= scan->rs_ntuples)
+		if (pagescan->rs_cindex < 0 || pagescan->rs_cindex >= pagescan->rs_ntuples)
 		{
 			node->tbmres = tbmres = NULL;
 			continue;
@@ -325,23 +326,14 @@ BitmapHeapNext(BitmapHeapScanState *node)
 			/*
 			 * Okay to fetch the tuple.
 			 */
-			targoffset = scan->rs_vistuples[scan->rs_cindex];
-			dp = (Page) BufferGetPage(scan->rs_cbuf);
-			lp = PageGetItemId(dp, targoffset);
-			Assert(ItemIdIsNormal(lp));
-
-			scan->rs_ctup.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lp);
-			scan->rs_ctup.t_len = ItemIdGetLength(lp);
-			scan->rs_ctup.t_tableOid = scan->rs_rd->rd_id;
-			ItemPointerSet(&scan->rs_ctup.t_self, tbmres->blockno, targoffset);
-
-			pgstat_count_heap_fetch(scan->rs_rd);
+			targoffset = pagescan->rs_vistuples[pagescan->rs_cindex];
+			tuple = storage_fetch_tuple_from_offset(scan, tbmres->blockno, targoffset);
 
 			/*
 			 * Set up the result slot to point to this tuple.  Note that the
 			 * slot acquires a pin on the buffer.
 			 */
-			ExecStoreTuple(&scan->rs_ctup,
+			ExecStoreTuple(tuple,
 						   slot,
 						   scan->rs_cbuf,
 						   false);
@@ -383,8 +375,10 @@ BitmapHeapNext(BitmapHeapScanState *node)
  * interesting according to the bitmap, and visible according to the snapshot.
  */
 static void
-bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
+bitgetpage(BitmapHeapScanState *node, TBMIterateResult *tbmres)
 {
+	StorageScanDesc scan = node->ss.ss_currentScanDesc;
+	HeapPageScanDesc pagescan = node->pagescan;
 	BlockNumber page = tbmres->blockno;
 	Buffer		buffer;
 	Snapshot	snapshot;
@@ -393,7 +387,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 	/*
 	 * Acquire pin on the target heap page, trading in any pin we held before.
 	 */
-	Assert(page < scan->rs_nblocks);
+	Assert(page < pagescan->rs_nblocks);
 
 	scan->rs_cbuf = ReleaseAndReadBuffer(scan->rs_cbuf,
 										 scan->rs_rd,
@@ -436,7 +430,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 			ItemPointerSet(&tid, page, offnum);
 			if (storage_hot_search_buffer(&tid, scan->rs_rd, buffer, snapshot,
 										  &heapTuple, NULL, true))
-				scan->rs_vistuples[ntup++] = ItemPointerGetOffsetNumber(&tid);
+				pagescan->rs_vistuples[ntup++] = ItemPointerGetOffsetNumber(&tid);
 		}
 	}
 	else
@@ -452,23 +446,20 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 		for (offnum = FirstOffsetNumber; offnum <= maxoff; offnum = OffsetNumberNext(offnum))
 		{
 			ItemId		lp;
-			HeapTupleData loctup;
+			StorageTuple loctup;
 			bool		valid;
 
 			lp = PageGetItemId(dp, offnum);
 			if (!ItemIdIsNormal(lp))
 				continue;
-			loctup.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lp);
-			loctup.t_len = ItemIdGetLength(lp);
-			loctup.t_tableOid = scan->rs_rd->rd_id;
-			ItemPointerSet(&loctup.t_self, page, offnum);
-			valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine, &loctup, snapshot, buffer);
+			loctup = storage_fetch_tuple_from_offset(scan, page, offnum);
+			valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine, loctup, snapshot, buffer);
 			if (valid)
 			{
-				scan->rs_vistuples[ntup++] = offnum;
-				PredicateLockTuple(scan->rs_rd, &loctup, snapshot);
+				pagescan->rs_vistuples[ntup++] = offnum;
+				PredicateLockTuple(scan->rs_rd, loctup, snapshot);
 			}
-			CheckForSerializableConflictOut(valid, scan->rs_rd, &loctup,
+			CheckForSerializableConflictOut(valid, scan->rs_rd, loctup,
 											buffer, snapshot);
 		}
 	}
@@ -476,7 +467,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
 
 	Assert(ntup <= MaxHeapTuplesPerPage);
-	scan->rs_ntuples = ntup;
+	pagescan->rs_ntuples = ntup;
 }
 
 /*
@@ -602,7 +593,7 @@ BitmapAdjustPrefetchTarget(BitmapHeapScanState *node)
  * BitmapPrefetch - Prefetch, if prefetch_pages are behind prefetch_target
  */
 static inline void
-BitmapPrefetch(BitmapHeapScanState *node, HeapScanDesc scan)
+BitmapPrefetch(BitmapHeapScanState *node, StorageScanDesc scan)
 {
 #ifdef USE_PREFETCH
 	ParallelBitmapHeapState *pstate = node->pstate;
@@ -793,7 +784,7 @@ void
 ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 {
 	Relation	relation;
-	HeapScanDesc scanDesc;
+	StorageScanDesc scanDesc;
 
 	/*
 	 * extract information from the node
@@ -958,6 +949,8 @@ ExecInitBitmapHeapScan(BitmapHeapScan *node, EState *estate, int eflags)
 															0,
 															NULL);
 
+	scanstate->pagescan = storageam_get_heappagescandesc(scanstate->ss.ss_currentScanDesc);
+
 	/*
 	 * get the scan type from the relation descriptor.
 	 */
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index 7e990dc35e..064b655aea 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -31,9 +31,7 @@ static TupleTableSlot *SampleNext(SampleScanState *node);
 static void tablesample_init(SampleScanState *scanstate);
 static StorageTuple tablesample_getnext(SampleScanState *scanstate);
 static bool SampleTupleVisible(StorageTuple tuple, OffsetNumber tupoffset,
-				   HeapScanDesc scan);
-
-/* hari */
+				   SampleScanState *scanstate);
 
 /* ----------------------------------------------------------------
  *						Scan Support
@@ -357,6 +355,7 @@ tablesample_init(SampleScanState *scanstate)
 									   scanstate->use_bulkread,
 									   allow_sync,
 									   scanstate->use_pagemode);
+		scanstate->pagescan = storageam_get_heappagescandesc(scanstate->ss.ss_currentScanDesc);
 	}
 	else
 	{
@@ -382,10 +381,11 @@ static StorageTuple
 tablesample_getnext(SampleScanState *scanstate)
 {
 	TsmRoutine *tsm = scanstate->tsmroutine;
-	HeapScanDesc scan = scanstate->ss.ss_currentScanDesc;
-	HeapTuple	tuple = &(scan->rs_ctup);
+	StorageScanDesc scan = scanstate->ss.ss_currentScanDesc;
+	HeapPageScanDesc pagescan = scanstate->pagescan;
+	StorageTuple tuple;
 	Snapshot	snapshot = scan->rs_snapshot;
-	bool		pagemode = scan->rs_pageatatime;
+	bool		pagemode = pagescan->rs_pageatatime;
 	BlockNumber blockno;
 	Page		page;
 	bool		all_visible;
@@ -396,10 +396,9 @@ tablesample_getnext(SampleScanState *scanstate)
 		/*
 		 * return null immediately if relation is empty
 		 */
-		if (scan->rs_nblocks == 0)
+		if (pagescan->rs_nblocks == 0)
 		{
 			Assert(!BufferIsValid(scan->rs_cbuf));
-			tuple->t_data = NULL;
 			return NULL;
 		}
 		if (tsm->NextSampleBlock)
@@ -407,13 +406,12 @@ tablesample_getnext(SampleScanState *scanstate)
 			blockno = tsm->NextSampleBlock(scanstate);
 			if (!BlockNumberIsValid(blockno))
 			{
-				tuple->t_data = NULL;
 				return NULL;
 			}
 		}
 		else
-			blockno = scan->rs_startblock;
-		Assert(blockno < scan->rs_nblocks);
+			blockno = pagescan->rs_startblock;
+		Assert(blockno < pagescan->rs_nblocks);
 		heapgetpage(scan, blockno);
 		scan->rs_inited = true;
 	}
@@ -456,14 +454,12 @@ tablesample_getnext(SampleScanState *scanstate)
 			if (!ItemIdIsNormal(itemid))
 				continue;
 
-			tuple->t_data = (HeapTupleHeader) PageGetItem(page, itemid);
-			tuple->t_len = ItemIdGetLength(itemid);
-			ItemPointerSet(&(tuple->t_self), blockno, tupoffset);
+			tuple = storage_fetch_tuple_from_offset(scan, blockno, tupoffset);
 
 			if (all_visible)
 				visible = true;
 			else
-				visible = SampleTupleVisible(tuple, tupoffset, scan);
+				visible = SampleTupleVisible(tuple, tupoffset, scanstate);
 
 			/* in pagemode, heapgetpage did this for us */
 			if (!pagemode)
@@ -494,14 +490,14 @@ tablesample_getnext(SampleScanState *scanstate)
 		if (tsm->NextSampleBlock)
 		{
 			blockno = tsm->NextSampleBlock(scanstate);
-			Assert(!scan->rs_syncscan);
+			Assert(!pagescan->rs_syncscan);
 			finished = !BlockNumberIsValid(blockno);
 		}
 		else
 		{
 			/* Without NextSampleBlock, just do a plain forward seqscan. */
 			blockno++;
-			if (blockno >= scan->rs_nblocks)
+			if (blockno >= pagescan->rs_nblocks)
 				blockno = 0;
 
 			/*
@@ -514,10 +510,10 @@ tablesample_getnext(SampleScanState *scanstate)
 			 * a little bit backwards on every invocation, which is confusing.
 			 * We don't guarantee any specific ordering in general, though.
 			 */
-			if (scan->rs_syncscan)
+			if (pagescan->rs_syncscan)
 				ss_report_location(scan->rs_rd, blockno);
 
-			finished = (blockno == scan->rs_startblock);
+			finished = (blockno == pagescan->rs_startblock);
 		}
 
 		/*
@@ -529,12 +525,11 @@ tablesample_getnext(SampleScanState *scanstate)
 				ReleaseBuffer(scan->rs_cbuf);
 			scan->rs_cbuf = InvalidBuffer;
 			scan->rs_cblock = InvalidBlockNumber;
-			tuple->t_data = NULL;
 			scan->rs_inited = false;
 			return NULL;
 		}
 
-		Assert(blockno < scan->rs_nblocks);
+		Assert(blockno < pagescan->rs_nblocks);
 		heapgetpage(scan, blockno);
 
 		/* Re-establish state for new page */
@@ -549,16 +544,19 @@ tablesample_getnext(SampleScanState *scanstate)
 	/* Count successfully-fetched tuples as heap fetches */
 	pgstat_count_heap_getnext(scan->rs_rd);
 
-	return &(scan->rs_ctup);
+	return tuple;
 }
 
 /*
  * Check visibility of the tuple.
  */
 static bool
-SampleTupleVisible(StorageTuple tuple, OffsetNumber tupoffset, HeapScanDesc scan) //hari
+SampleTupleVisible(StorageTuple tuple, OffsetNumber tupoffset, SampleScanState *scanstate)
 {
-	if (scan->rs_pageatatime)
+	StorageScanDesc scan = scanstate->ss.ss_currentScanDesc;
+	HeapPageScanDesc pagescan = scanstate->pagescan;
+
+	if (pagescan->rs_pageatatime)
 	{
 		/*
 		 * In pageatatime mode, heapgetpage() already did visibility checks,
@@ -570,12 +568,12 @@ SampleTupleVisible(StorageTuple tuple, OffsetNumber tupoffset, HeapScanDesc scan
 		 * gain to justify the restriction.
 		 */
 		int			start = 0,
-					end = scan->rs_ntuples - 1;
+					end = pagescan->rs_ntuples - 1;
 
 		while (start <= end)
 		{
 			int			mid = (start + end) / 2;
-			OffsetNumber curoffset = scan->rs_vistuples[mid];
+			OffsetNumber curoffset = pagescan->rs_vistuples[mid];
 
 			if (tupoffset == curoffset)
 				return true;
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index 7cfc2107f6..32484ea84c 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -317,9 +317,10 @@ void
 ExecSeqScanReInitializeDSM(SeqScanState *node,
 						   ParallelContext *pcxt)
 {
-	HeapScanDesc scan = node->ss.ss_currentScanDesc;
+	ParallelHeapScanDesc pscan;
 
-	heap_parallelscan_reinitialize(scan->rs_parallel);
+	pscan = storageam_get_parallelheapscandesc(node->ss.ss_currentScanDesc);
+	heap_parallelscan_reinitialize(pscan);
 }
 
 /* ----------------------------------------------------------------
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index f7438714c4..1d8caf3c79 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -21,6 +21,7 @@
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/nbtree.h"
+#include "access/storageamapi.h"
 #include "access/sysattr.h"
 #include "access/transam.h"
 #include "access/xlog.h"
@@ -253,7 +254,8 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 			info->amsearchnulls = amroutine->amsearchnulls;
 			info->amcanparallel = amroutine->amcanparallel;
 			info->amhasgettuple = (amroutine->amgettuple != NULL);
-			info->amhasgetbitmap = (amroutine->amgetbitmap != NULL);
+			info->amhasgetbitmap = ((amroutine->amgetbitmap != NULL)
+									&& (relation->rd_stamroutine->scan_get_heappagescandesc != NULL));
 			info->amcostestimate = amroutine->amcostestimate;
 			Assert(info->amcostestimate != NULL);
 
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 0d2e1733bf..ee005d4168 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1865,7 +1865,7 @@ get_database_list(void)
 {
 	List	   *dblist = NIL;
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tup;
 	MemoryContext resultcxt;
 
@@ -1931,7 +1931,7 @@ do_autovacuum(void)
 {
 	Relation	classRel;
 	HeapTuple	tuple;
-	HeapScanDesc relScan;
+	StorageScanDesc relScan;
 	Form_pg_database dbForm;
 	List	   *table_oids = NIL;
 	List	   *orphan_oids = NIL;
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index a4cbedd54a..5b30fb281f 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -1207,7 +1207,7 @@ pgstat_collect_oids(Oid catalogid)
 	HTAB	   *htab;
 	HASHCTL		hash_ctl;
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tup;
 	Snapshot	snapshot;
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index bade3fefc5..a7a2ef7bf6 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -107,7 +107,7 @@ get_subscription_list(void)
 {
 	List	   *res = NIL;
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tup;
 	MemoryContext resultcxt;
 
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index c6791c758c..4140ff7603 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -420,7 +420,7 @@ DefineQueryRewrite(const char *rulename,
 		if (event_relation->rd_rel->relkind != RELKIND_VIEW &&
 			event_relation->rd_rel->relkind != RELKIND_MATVIEW)
 		{
-			HeapScanDesc scanDesc;
+			StorageScanDesc scanDesc;
 			Snapshot	snapshot;
 
 			if (event_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 03b7cc76d7..42a35824e5 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -1208,7 +1208,7 @@ static bool
 ThereIsAtLeastOneRole(void)
 {
 	Relation	pg_authid_rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	bool		result;
 
 	pg_authid_rel = heap_open(AuthIdRelationId, AccessShareLock);
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 4b16de02bd..0501bb7bb0 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -98,6 +98,8 @@ extern Relation heap_openrv_extended(const RangeVar *relation,
 #define heap_close(r,l)  relation_close(r,l)
 
 /* struct definitions appear in relscan.h */
+typedef struct HeapPageScanDescData *HeapPageScanDesc;
+typedef struct StorageScanDescData *StorageScanDesc;
 typedef struct HeapScanDescData *HeapScanDesc;
 typedef struct ParallelHeapScanDescData *ParallelHeapScanDesc;
 
@@ -107,25 +109,25 @@ typedef struct ParallelHeapScanDescData *ParallelHeapScanDesc;
  */
 #define HeapScanIsValid(scan) PointerIsValid(scan)
 
-extern HeapScanDesc heap_beginscan(Relation relation, Snapshot snapshot,
-			   int nkeys, ScanKey key,
-			   ParallelHeapScanDesc parallel_scan,
-			   bool allow_strat,
-			   bool allow_sync,
-			   bool allow_pagemode,
-			   bool is_bitmapscan,
-			   bool is_samplescan,
-			   bool temp_snap);
-extern void heap_setscanlimits(HeapScanDesc scan, BlockNumber startBlk,
+extern StorageScanDesc heap_beginscan(Relation relation, Snapshot snapshot,
+									  int nkeys, ScanKey key,
+									  ParallelHeapScanDesc parallel_scan,
+									  bool allow_strat,
+									  bool allow_sync,
+									  bool allow_pagemode,
+									  bool is_bitmapscan,
+									  bool is_samplescan,
+									  bool temp_snap);
+extern void heap_setscanlimits(StorageScanDesc scan, BlockNumber startBlk,
 				   BlockNumber endBlk);
-extern void heapgetpage(HeapScanDesc scan, BlockNumber page);
-extern void heap_rescan(HeapScanDesc scan, ScanKey key, bool set_params,
+extern void heapgetpage(StorageScanDesc scan, BlockNumber page);
+extern void heap_rescan(StorageScanDesc scan, ScanKey key, bool set_params,
 			bool allow_strat, bool allow_sync, bool allow_pagemode);
-extern void heap_rescan_set_params(HeapScanDesc scan, ScanKey key,
+extern void heap_rescan_set_params(StorageScanDesc scan, ScanKey key,
 					   bool allow_strat, bool allow_sync, bool allow_pagemode);
-extern void heap_endscan(HeapScanDesc scan);
-extern StorageTuple heap_getnext(HeapScanDesc scan, ScanDirection direction);
-extern TupleTableSlot *heap_getnextslot(HeapScanDesc sscan, ScanDirection direction,
+extern void heap_endscan(StorageScanDesc scan);
+extern StorageTuple heap_getnext(StorageScanDesc scan, ScanDirection direction);
+extern TupleTableSlot *heap_getnextslot(StorageScanDesc sscan, ScanDirection direction,
 				 TupleTableSlot *slot);
 extern Size heap_parallelscan_estimate(Snapshot snapshot);
 extern void heap_parallelscan_initialize(ParallelHeapScanDesc target,
@@ -181,7 +183,7 @@ extern void simple_heap_update(Relation relation, ItemPointer otid,
 				   HeapTuple tup);
 
 extern void heap_sync(Relation relation);
-extern void heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot);
+extern void heap_update_snapshot(StorageScanDesc scan, Snapshot snapshot);
 
 /* in heap/heapam_visibility.c */
 extern HTSU_Result HeapTupleSatisfiesUpdate(StorageTuple stup, CommandId curcid,
diff --git a/src/include/access/relscan.h b/src/include/access/relscan.h
index 147f862a2b..2651fd73d7 100644
--- a/src/include/access/relscan.h
+++ b/src/include/access/relscan.h
@@ -16,6 +16,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/storageam.h"
 #include "access/htup_details.h"
 #include "access/itup.h"
 #include "access/tupdesc.h"
@@ -42,40 +43,54 @@ typedef struct ParallelHeapScanDescData
 	char		phs_snapshot_data[FLEXIBLE_ARRAY_MEMBER];
 }			ParallelHeapScanDescData;
 
-typedef struct HeapScanDescData
+typedef struct StorageScanDescData
 {
 	/* scan parameters */
 	Relation	rs_rd;			/* heap relation descriptor */
 	Snapshot	rs_snapshot;	/* snapshot to see */
 	int			rs_nkeys;		/* number of scan keys */
 	ScanKey		rs_key;			/* array of scan key descriptors */
-	bool		rs_bitmapscan;	/* true if this is really a bitmap scan */
-	bool		rs_samplescan;	/* true if this is really a sample scan */
+
+	/* scan current state */
+	bool		rs_inited;		/* false = scan not init'd yet */
+	BlockNumber rs_cblock;		/* current block # in scan, if any */
+	Buffer		rs_cbuf;		/* current buffer in scan, if any */
+}			StorageScanDescData;
+
+typedef struct HeapPageScanDescData
+{
 	bool		rs_pageatatime; /* verify visibility page-at-a-time? */
-	bool		rs_allow_strat; /* allow or disallow use of access strategy */
-	bool		rs_allow_sync;	/* allow or disallow use of syncscan */
-	bool		rs_temp_snap;	/* unregister snapshot at scan end? */
 
 	/* state set up at initscan time */
 	BlockNumber rs_nblocks;		/* total number of blocks in rel */
 	BlockNumber rs_startblock;	/* block # to start at */
 	BlockNumber rs_numblocks;	/* max number of blocks to scan */
+
 	/* rs_numblocks is usually InvalidBlockNumber, meaning "scan whole rel" */
 	BufferAccessStrategy rs_strategy;	/* access strategy for reads */
 	bool		rs_syncscan;	/* report location to syncscan logic? */
 
-	/* scan current state */
-	bool		rs_inited;		/* false = scan not init'd yet */
-	HeapTupleData rs_ctup;		/* current tuple in scan, if any */
-	BlockNumber rs_cblock;		/* current block # in scan, if any */
-	Buffer		rs_cbuf;		/* current buffer in scan, if any */
-	/* NB: if rs_cbuf is not InvalidBuffer, we hold a pin on that buffer */
-	ParallelHeapScanDesc rs_parallel;	/* parallel scan information */
-
 	/* these fields only used in page-at-a-time mode and for bitmap scans */
 	int			rs_cindex;		/* current tuple's index in vistuples */
 	int			rs_ntuples;		/* number of visible tuples on page */
 	OffsetNumber rs_vistuples[MaxHeapTuplesPerPage];	/* their offsets */
+}			HeapPageScanDescData;
+
+typedef struct HeapScanDescData
+{
+	/* scan parameters */
+	StorageScanDescData rs_scan;	/* */
+	HeapPageScanDescData rs_pagescan;
+	bool		rs_bitmapscan;	/* true if this is really a bitmap scan */
+	bool		rs_samplescan;	/* true if this is really a sample scan */
+	bool		rs_allow_strat; /* allow or disallow use of access strategy */
+	bool		rs_allow_sync;	/* allow or disallow use of syncscan */
+	bool		rs_temp_snap;	/* unregister snapshot at scan end? */
+
+	HeapTupleData rs_ctup;		/* current tuple in scan, if any */
+
+	/* NB: if rs_cbuf is not InvalidBuffer, we hold a pin on that buffer */
+	ParallelHeapScanDesc rs_parallel;	/* parallel scan information */
 }			HeapScanDescData;
 
 /*
@@ -149,12 +164,12 @@ typedef struct ParallelIndexScanDescData
 	char		ps_snapshot_data[FLEXIBLE_ARRAY_MEMBER];
 }			ParallelIndexScanDescData;
 
-/* Struct for heap-or-index scans of system tables */
+/* Struct for storage-or-index scans of system tables */
 typedef struct SysScanDescData
 {
 	Relation	heap_rel;		/* catalog being scanned */
 	Relation	irel;			/* NULL if doing heap scan */
-	HeapScanDesc scan;			/* only valid in heap-scan case */
+	StorageScanDesc scan;		/* only valid in storage-scan case */
 	IndexScanDesc iscan;		/* only valid in index-scan case */
 	Snapshot	snapshot;		/* snapshot to unregister at end of scan */
 }			SysScanDescData;
diff --git a/src/include/access/storage_common.h b/src/include/access/storage_common.h
index f9a1075339..0c43ae4310 100644
--- a/src/include/access/storage_common.h
+++ b/src/include/access/storage_common.h
@@ -27,7 +27,6 @@
 
 /* A physical tuple coming from a storage AM scan */
 typedef void *StorageTuple;
-typedef void *StorageScanDesc;
 
 /*
  * slot storage routine functions
diff --git a/src/include/access/storageam.h b/src/include/access/storageam.h
index b08d7f401e..e1d01193d3 100644
--- a/src/include/access/storageam.h
+++ b/src/include/access/storageam.h
@@ -40,29 +40,31 @@ typedef List *(*InsertIndexTuples) (TupleTableSlot *slot, EState *estate, bool n
 									bool *specConflict, List *arbiterIndexes);
 
 
-extern HeapScanDesc storage_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan);
-
-extern void storage_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
-extern HeapScanDesc storage_beginscan(Relation relation, Snapshot snapshot,
-				  int nkeys, ScanKey key);
-extern HeapScanDesc storage_beginscan_catalog(Relation relation, int nkeys, ScanKey key);
-extern HeapScanDesc storage_beginscan_strat(Relation relation, Snapshot snapshot,
-						int nkeys, ScanKey key,
-						bool allow_strat, bool allow_sync);
-extern HeapScanDesc storage_beginscan_bm(Relation relation, Snapshot snapshot,
-					 int nkeys, ScanKey key);
-extern HeapScanDesc storage_beginscan_sampling(Relation relation, Snapshot snapshot,
-						   int nkeys, ScanKey key,
-						   bool allow_strat, bool allow_sync, bool allow_pagemode);
-
-extern void storage_endscan(HeapScanDesc scan);
-extern void storage_rescan(HeapScanDesc scan, ScanKey key);
-extern void storage_rescan_set_params(HeapScanDesc scan, ScanKey key,
+extern StorageScanDesc storage_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan);
+extern ParallelHeapScanDesc storageam_get_parallelheapscandesc(StorageScanDesc sscan);
+extern HeapPageScanDesc storageam_get_heappagescandesc(StorageScanDesc sscan);
+extern void storage_setscanlimits(StorageScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
+extern StorageScanDesc storage_beginscan(Relation relation, Snapshot snapshot,
+										 int nkeys, ScanKey key);
+extern StorageScanDesc storage_beginscan_catalog(Relation relation, int nkeys, ScanKey key);
+extern StorageScanDesc storage_beginscan_strat(Relation relation, Snapshot snapshot,
+											   int nkeys, ScanKey key,
+											   bool allow_strat, bool allow_sync);
+extern StorageScanDesc storage_beginscan_bm(Relation relation, Snapshot snapshot,
+											int nkeys, ScanKey key);
+extern StorageScanDesc storage_beginscan_sampling(Relation relation, Snapshot snapshot,
+												  int nkeys, ScanKey key,
+												  bool allow_strat, bool allow_sync, bool allow_pagemode);
+
+extern void storage_endscan(StorageScanDesc scan);
+extern void storage_rescan(StorageScanDesc scan, ScanKey key);
+extern void storage_rescan_set_params(StorageScanDesc scan, ScanKey key,
 						  bool allow_strat, bool allow_sync, bool allow_pagemode);
-extern void storage_update_snapshot(HeapScanDesc scan, Snapshot snapshot);
+extern void storage_update_snapshot(StorageScanDesc scan, Snapshot snapshot);
 
-extern StorageTuple storage_getnext(HeapScanDesc sscan, ScanDirection direction);
-extern TupleTableSlot *storage_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *slot);
+extern StorageTuple storage_getnext(StorageScanDesc sscan, ScanDirection direction);
+extern TupleTableSlot *storage_getnextslot(StorageScanDesc sscan, ScanDirection direction, TupleTableSlot *slot);
+extern StorageTuple storage_fetch_tuple_from_offset(StorageScanDesc sscan, BlockNumber blkno, OffsetNumber offset);
 
 extern void storage_get_latest_tid(Relation relation,
 					   Snapshot snapshot,
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
index 4ea2ac4388..d626115b51 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -81,32 +81,37 @@ typedef StorageTuple(*TupleFromDatum_function) (Datum data, Oid tableoid);
 typedef void (*RelationSync_function) (Relation relation);
 
 
-typedef HeapScanDesc (*ScanBegin_function) (Relation relation,
-											Snapshot snapshot,
-											int nkeys, ScanKey key,
-											ParallelHeapScanDesc parallel_scan,
-											bool allow_strat,
-											bool allow_sync,
-											bool allow_pagemode,
-											bool is_bitmapscan,
-											bool is_samplescan,
-											bool temp_snap);
-typedef void (*ScanSetlimits_function) (HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
+typedef StorageScanDesc(*ScanBegin_function) (Relation relation,
+											  Snapshot snapshot,
+											  int nkeys, ScanKey key,
+											  ParallelHeapScanDesc parallel_scan,
+											  bool allow_strat,
+											  bool allow_sync,
+											  bool allow_pagemode,
+											  bool is_bitmapscan,
+											  bool is_samplescan,
+											  bool temp_snap);
+typedef ParallelHeapScanDesc (*ScanGetParallelheapscandesc_function) (StorageScanDesc scan);
+typedef HeapPageScanDesc(*ScanGetHeappagescandesc_function) (StorageScanDesc scan);
+
+typedef void (*ScanSetlimits_function) (StorageScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
 
 /* must return a TupleTableSlot? */
-typedef StorageTuple(*ScanGetnext_function) (HeapScanDesc scan,
+typedef StorageTuple(*ScanGetnext_function) (StorageScanDesc scan,
 											 ScanDirection direction);
 
-typedef TupleTableSlot *(*ScanGetnextSlot_function) (HeapScanDesc scan,
+typedef TupleTableSlot *(*ScanGetnextSlot_function) (StorageScanDesc scan,
 													 ScanDirection direction, TupleTableSlot *slot);
+typedef StorageTuple(*ScanFetchTupleFromOffset_function) (StorageScanDesc scan,
+														  BlockNumber blkno, OffsetNumber offset);
 
-typedef void (*ScanEnd_function) (HeapScanDesc scan);
+typedef void (*ScanEnd_function) (StorageScanDesc scan);
 
 
-typedef void (*ScanGetpage_function) (HeapScanDesc scan, BlockNumber page);
-typedef void (*ScanRescan_function) (HeapScanDesc scan, ScanKey key, bool set_params,
+typedef void (*ScanGetpage_function) (StorageScanDesc scan, BlockNumber page);
+typedef void (*ScanRescan_function) (StorageScanDesc scan, ScanKey key, bool set_params,
 									 bool allow_strat, bool allow_sync, bool allow_pagemode);
-typedef void (*ScanUpdateSnapshot_function) (HeapScanDesc scan, Snapshot snapshot);
+typedef void (*ScanUpdateSnapshot_function) (StorageScanDesc scan, Snapshot snapshot);
 
 typedef bool (*HotSearchBuffer_function) (ItemPointer tid, Relation relation,
 										  Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
@@ -148,9 +153,12 @@ typedef struct StorageAmRoutine
 
 	/* Operations on relation scans */
 	ScanBegin_function scan_begin;
+	ScanGetParallelheapscandesc_function scan_get_parallelheapscandesc;
+	ScanGetHeappagescandesc_function scan_get_heappagescandesc;
 	ScanSetlimits_function scansetlimits;
 	ScanGetnext_function scan_getnext;
 	ScanGetnextSlot_function scan_getnextslot;
+	ScanFetchTupleFromOffset_function scan_fetch_tuple_from_offset;
 	ScanEnd_function scan_end;
 	ScanGetpage_function scan_getpage;
 	ScanRescan_function scan_rescan;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 75ca1543fc..62bb60c301 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1118,7 +1118,7 @@ typedef struct ScanState
 {
 	PlanState	ps;				/* its first field is NodeTag */
 	Relation	ss_currentRelation;
-	HeapScanDesc ss_currentScanDesc;
+	StorageScanDesc ss_currentScanDesc;
 	TupleTableSlot *ss_ScanTupleSlot;
 } ScanState;
 
@@ -1139,6 +1139,7 @@ typedef struct SeqScanState
 typedef struct SampleScanState
 {
 	ScanState	ss;
+	HeapPageScanDesc pagescan;
 	List	   *args;			/* expr states for TABLESAMPLE params */
 	ExprState  *repeatable;		/* expr state for REPEATABLE expr */
 	/* use struct pointer to avoid including tsmapi.h here */
@@ -1367,6 +1368,7 @@ typedef struct ParallelBitmapHeapState
 typedef struct BitmapHeapScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
+	HeapPageScanDesc pagescan;
 	ExprState  *bitmapqualorig;
 	TIDBitmap  *tbm;
 	TBMIterator *tbmiterator;
-- 
2.15.0.windows.1



  [application/octet-stream] 0009-BulkInsertState-is-moved-into-storage.patch (10.7K, ../../CAJrrPGc951F-R4Kfa4W47B5vHKeHsB2Y34zewp=b+AWSkF9RVA@mail.gmail.com/13-0009-BulkInsertState-is-moved-into-storage.patch)
  download | inline diff:
From ca3e363fdd064f7b2ea667aee79f038fa9143510 Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Wed, 20 Dec 2017 18:19:26 +1100
Subject: [PATCH 09/11] BulkInsertState is moved into storage

Added Get, free and release bulkinsertstate functions
to operate with bulkinsertstate that is provided by
the storage.
---
 src/backend/access/heap/heapam.c         |  2 +-
 src/backend/access/heap/heapam_storage.c |  4 ++++
 src/backend/access/storage/storageam.c   | 23 +++++++++++++++++++++++
 src/backend/commands/copy.c              |  8 ++++----
 src/backend/commands/createas.c          |  6 +++---
 src/backend/commands/matview.c           |  6 +++---
 src/backend/commands/tablecmds.c         |  6 +++---
 src/include/access/heapam.h              |  4 +---
 src/include/access/storage_common.h      |  3 +++
 src/include/access/storageam.h           |  4 ++++
 src/include/access/storageamapi.h        |  7 +++++++
 11 files changed, 56 insertions(+), 17 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 90bbed9900..04d9fabd9a 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2328,7 +2328,7 @@ GetBulkInsertState(void)
 	bistate = (BulkInsertState) palloc(sizeof(BulkInsertStateData));
 	bistate->strategy = GetAccessStrategy(BAS_BULKWRITE);
 	bistate->current_buf = InvalidBuffer;
-	return bistate;
+	return (void *)bistate;
 }
 
 /*
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index ea2b640693..b89069bf74 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -327,5 +327,9 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 	amroutine->tuple_get_latest_tid = heap_get_latest_tid;
 	amroutine->relation_sync = heap_sync;
 
+	amroutine->getbulkinsertstate = GetBulkInsertState;
+	amroutine->freebulkinsertstate = FreeBulkInsertState;
+	amroutine->releasebulkinsertstate = ReleaseBulkInsertStatePin;
+
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/storage/storageam.c b/src/backend/access/storage/storageam.c
index f14673e88e..d884b3633c 100644
--- a/src/backend/access/storage/storageam.c
+++ b/src/backend/access/storage/storageam.c
@@ -378,3 +378,26 @@ storage_sync(Relation rel)
 {
 	rel->rd_stamroutine->relation_sync(rel);
 }
+
+/*
+ * -------------------
+ * storage Bulk Insert functions
+ * -------------------
+ */
+BulkInsertState
+storage_getbulkinsertstate(Relation rel)
+{
+	return rel->rd_stamroutine->getbulkinsertstate();
+}
+
+void
+storage_freebulkinsertstate(Relation rel, BulkInsertState bistate)
+{
+	rel->rd_stamroutine->freebulkinsertstate(bistate);
+}
+
+void
+storage_releasebulkinsertstate(Relation rel, BulkInsertState bistate)
+{
+	rel->rd_stamroutine->releasebulkinsertstate(bistate);
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 42803c687b..a4a8f52284 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2297,7 +2297,7 @@ CopyFrom(CopyState cstate)
 	ErrorContextCallback errcallback;
 	CommandId	mycid = GetCurrentCommandId(true);
 	int			hi_options = 0; /* start with default heap_insert options */
-	BulkInsertState bistate;
+	void       *bistate;
 	uint64		processed = 0;
 	bool		useHeapMultiInsert;
 	int			nBufferedTuples = 0;
@@ -2553,7 +2553,7 @@ CopyFrom(CopyState cstate)
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	bistate = GetBulkInsertState();
+	bistate = storage_getbulkinsertstate(resultRelInfo->ri_RelationDesc);
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2633,7 +2633,7 @@ CopyFrom(CopyState cstate)
 			 */
 			if (prev_leaf_part_index != leaf_part_index)
 			{
-				ReleaseBulkInsertStatePin(bistate);
+				storage_releasebulkinsertstate(resultRelInfo->ri_RelationDesc, bistate);
 				prev_leaf_part_index = leaf_part_index;
 			}
 
@@ -2818,7 +2818,7 @@ CopyFrom(CopyState cstate)
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	FreeBulkInsertState(bistate);
+	storage_freebulkinsertstate(resultRelInfo->ri_RelationDesc, bistate);
 
 	MemoryContextSwitchTo(oldcontext);
 
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 9e6fb8740b..2ddebcec3d 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -60,7 +60,7 @@ typedef struct
 	ObjectAddress reladdr;		/* address of rel, for ExecCreateTableAs */
 	CommandId	output_cid;		/* cmin to insert in output tuples */
 	int			hi_options;		/* heap_insert performance options */
-	BulkInsertState bistate;	/* bulk insert state */
+	void       *bistate;		/* bulk insert state */
 } DR_intorel;
 
 /* utility functions for CTAS definition creation */
@@ -570,7 +570,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	 */
 	myState->hi_options = HEAP_INSERT_SKIP_FSM |
 		(XLogIsNeeded() ? 0 : HEAP_INSERT_SKIP_WAL);
-	myState->bistate = GetBulkInsertState();
+	myState->bistate = storage_getbulkinsertstate(intoRelationDesc);
 
 	/* Not using WAL requires smgr_targblock be initially invalid */
 	Assert(RelationGetTargetBlock(intoRelationDesc) == InvalidBlockNumber);
@@ -619,7 +619,7 @@ intorel_shutdown(DestReceiver *self)
 {
 	DR_intorel *myState = (DR_intorel *) self;
 
-	FreeBulkInsertState(myState->bistate);
+	storage_freebulkinsertstate(myState->rel, myState->bistate);
 
 	/* If we skipped using WAL, must heap_sync before commit */
 	if (myState->hi_options & HEAP_INSERT_SKIP_WAL)
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 936ea9b9e5..ab44049e3a 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -52,7 +52,7 @@ typedef struct
 	Relation	transientrel;	/* relation to write to */
 	CommandId	output_cid;		/* cmin to insert in output tuples */
 	int			hi_options;		/* heap_insert performance options */
-	BulkInsertState bistate;	/* bulk insert state */
+	void       *bistate;		/* bulk insert state */
 } DR_transientrel;
 
 static int	matview_maintenance_depth = 0;
@@ -479,7 +479,7 @@ transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	myState->hi_options = HEAP_INSERT_SKIP_FSM | HEAP_INSERT_FROZEN;
 	if (!XLogIsNeeded())
 		myState->hi_options |= HEAP_INSERT_SKIP_WAL;
-	myState->bistate = GetBulkInsertState();
+	myState->bistate = storage_getbulkinsertstate(transientrel);
 
 	/* Not using WAL requires smgr_targblock be initially invalid */
 	Assert(RelationGetTargetBlock(transientrel) == InvalidBlockNumber);
@@ -522,7 +522,7 @@ transientrel_shutdown(DestReceiver *self)
 {
 	DR_transientrel *myState = (DR_transientrel *) self;
 
-	FreeBulkInsertState(myState->bistate);
+	storage_freebulkinsertstate(myState->transientrel, myState->bistate);
 
 	/* If we skipped using WAL, must heap_sync before commit */
 	if (myState->hi_options & HEAP_INSERT_SKIP_WAL)
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 28874a27ae..90c41b4847 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4384,7 +4384,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	ListCell   *l;
 	EState	   *estate;
 	CommandId	mycid;
-	BulkInsertState bistate;
+	void       *bistate;
 	int			hi_options;
 	ExprState  *partqualstate = NULL;
 
@@ -4410,7 +4410,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	if (newrel)
 	{
 		mycid = GetCurrentCommandId(true);
-		bistate = GetBulkInsertState();
+		bistate = storage_getbulkinsertstate(newrel);
 
 		hi_options = HEAP_INSERT_SKIP_FSM;
 		if (!XLogIsNeeded())
@@ -4685,7 +4685,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	heap_close(oldrel, NoLock);
 	if (newrel)
 	{
-		FreeBulkInsertState(bistate);
+		storage_freebulkinsertstate(newrel, bistate);
 
 		/* If we skipped writing WAL, then we need to sync the heap. */
 		if (hi_options & HEAP_INSERT_SKIP_WAL)
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 0501bb7bb0..99b8cf67d8 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -31,8 +31,6 @@
 #define HEAP_INSERT_FROZEN		0x0004
 #define HEAP_INSERT_SPECULATIVE 0x0008
 
-typedef struct BulkInsertStateData *BulkInsertState;
-
 /*
  * Possible lock modes for a tuple.
  */
@@ -148,7 +146,7 @@ extern void heap_get_latest_tid(Relation relation, Snapshot snapshot,
 extern void setLastTid(const ItemPointer tid);
 
 extern BulkInsertState GetBulkInsertState(void);
-extern void FreeBulkInsertState(BulkInsertState);
+extern void FreeBulkInsertState(BulkInsertState bistate);
 extern void ReleaseBulkInsertStatePin(BulkInsertState bistate);
 
 extern Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid,
diff --git a/src/include/access/storage_common.h b/src/include/access/storage_common.h
index 0c43ae4310..2d0b1a3ccd 100644
--- a/src/include/access/storage_common.h
+++ b/src/include/access/storage_common.h
@@ -28,6 +28,9 @@
 /* A physical tuple coming from a storage AM scan */
 typedef void *StorageTuple;
 
+typedef struct BulkInsertStateData *BulkInsertState;
+
+
 /*
  * slot storage routine functions
  */
diff --git a/src/include/access/storageam.h b/src/include/access/storageam.h
index e1d01193d3..8ced6c6a1f 100644
--- a/src/include/access/storageam.h
+++ b/src/include/access/storageam.h
@@ -116,4 +116,8 @@ extern void storage_get_latest_tid(Relation relation,
 
 extern void storage_sync(Relation rel);
 
+extern BulkInsertState storage_getbulkinsertstate(Relation rel);
+extern void storage_freebulkinsertstate(Relation rel, BulkInsertState bistate);
+extern void storage_releasebulkinsertstate(Relation rel, BulkInsertState bistate);
+
 #endif
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
index d626115b51..d79aad71df 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -80,6 +80,9 @@ typedef StorageTuple(*TupleFromDatum_function) (Datum data, Oid tableoid);
 
 typedef void (*RelationSync_function) (Relation relation);
 
+typedef BulkInsertState (*GetBulkInsertState_function) (void);
+typedef void (*FreeBulkInsertState_function) (BulkInsertState bistate);
+typedef void (*ReleaseBulkInsertState_function) (BulkInsertState bistate);
 
 typedef StorageScanDesc(*ScanBegin_function) (Relation relation,
 											  Snapshot snapshot,
@@ -151,6 +154,10 @@ typedef struct StorageAmRoutine
 
 	RelationSync_function relation_sync;	/* heap_sync */
 
+	GetBulkInsertState_function getbulkinsertstate;
+	FreeBulkInsertState_function freebulkinsertstate;
+	ReleaseBulkInsertState_function releasebulkinsertstate;
+
 	/* Operations on relation scans */
 	ScanBegin_function scan_begin;
 	ScanGetParallelheapscandesc_function scan_get_parallelheapscandesc;
-- 
2.15.0.windows.1



^ permalink  raw  reply  [nested|flat] 21+ messages in thread

* Re: [HACKERS] Pluggable storage
  2017-12-12 04:06 Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
  2017-12-27 03:54 ` Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
@ 2017-12-27 12:33   ` Alexander Korotkov <[email protected]>
  2018-01-03 07:08     ` Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
  0 siblings, 1 reply; 21+ messages in thread

From: Alexander Korotkov @ 2017-12-27 12:33 UTC (permalink / raw)
  To: Haribabu Kommi <[email protected]>; +Cc: Michael Paquier <[email protected]>; Robert Haas <[email protected]>; Amit Kapila <[email protected]>; Thomas Munro <[email protected]>; Alvaro Herrera <[email protected]>; pgsql-hackers

Hi!

On Wed, Dec 27, 2017 at 6:54 AM, Haribabu Kommi <[email protected]>
wrote:

>
> On Tue, Dec 12, 2017 at 3:06 PM, Haribabu Kommi <[email protected]>
> wrote:
>
>>
>> I restructured that patch files to avoid showing unnecessary
>> modifications,
>> and also it will be easy for adding of new API's based on the all the
>> functions
>> that are exposed by heapam module easily compared earlier.
>>
>> Attached are the latest set of patches. I will work on the remaining
>> pending
>> items.
>>
>
> Apart from rebase to the latest master code, following are the additional
> changes,
>
> 1. Added API for bulk insert and rewrite functionality(Logical rewrite is
> not touched yet)
> 2. Tuple lock API interface redesign to remove the traversal logic from
> executor module.
>

Great, thank you!


> The tuple lock API interface changes are from "Alexander Korotkov" from
> "PostgresPro".
> Thanks Alexander. Currently we both are doing joint development for faster
> closure of
> open items that are pending to bring the "pluggable storage API" into a
> good shape.
>

Thank you for announcing this.  Yes, pluggable storage API requires a lot
of work to get into committable shape.  This is why I've decided to join
the development.

Let me explain the idea behind new tuple lock API and further patches I
plan to send.  As I noted upthread, I consider possibility of alternative
MVCC implementations as vital property of pluggable storage API.  These
include undo log option when tuple is updated in-place while old version of
tuple is displaced to some special area.  In this case, new version of
tuple would reside on same TID as old version of tuple.  This is an
important point, because TID is not really tuple identifier anymore.
Naturally, TID becomes a row identifier while tuple may be identified by
pair (tid, snapshot).  For current heap, snapshot is redundant and can be
used just for assert checking (tuple on given tid is really visible using
given snapshot).  For heap with undo log, appropriate tuple could be found
by snapshot in the undo chain associated with given tid.

One of consequences of above is that we cannot use fact that tid isn't
changed after update as sign that tuple was deleted.  This is why I've
introduced HTSU_Result  HeapTupleDeleted.  Another consequence is that our
tid traverse logic in the executor layer is not valid anymore.  For
instance, this traversal from older tuple to latter tuple doesn't make any
sense for heap with undo log where latter tuple is more easily accessible
than older tuple.  This is why I decided to hide this logic in storage
layer and provide TUPLE_LOCK_FLAG_FIND_LAST_VERSION flag which indicates
that lock_tuple() have to find latest updated version and lock it.  I've
also changed follow_updates bool to more explicit
TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS flag in order to not mess it with
previous flag which also kind of follow updates.  Third consequence is that
we have to pass snapshot to tuple_update() and tuple_delete() methods to
let them check if row was concurrently updated while residing on the same
TID.  I'm going to provide this change as separate patch.

Also, I appreciate that now tuple_insert() and tuple_update() methods are
responsible for inserting index tuples.  This unleash pluggable storages to
implement another way of interaction with indexes.  However, I didn't get
the point of passing InsertIndexTuples IndexFunc to them.  Now, we're
always passing ExecInsertIndexTuples() to this argument.  As I understood
storage is free to either call ExecInsertIndexTuples() or implement its own
logic of interaction with indexes.  But, I don't understand why do we need
a callback when tuple_insert() and tuple_update() can call
ExecInsertIndexTuples() directly if needed.  Another thing is that
tuple_delete() could also interact with indexes (especially when we will
enhance index access method API), and we need to pass meta-information
about indexes to tuple_delete() too.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company


^ permalink  raw  reply  [nested|flat] 21+ messages in thread

* Re: [HACKERS] Pluggable storage
  2017-12-12 04:06 Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
  2017-12-27 03:54 ` Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
  2017-12-27 12:33   ` Re: [HACKERS] Pluggable storage Alexander Korotkov <[email protected]>
@ 2018-01-03 07:08     ` Haribabu Kommi <[email protected]>
  2018-01-03 23:00       ` Re: [HACKERS] Pluggable storage Alexander Korotkov <[email protected]>
  2018-01-05 16:20       ` Re: [HACKERS] Pluggable storage Robert Haas <[email protected]>
  0 siblings, 2 replies; 21+ messages in thread

From: Haribabu Kommi @ 2018-01-03 07:08 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Michael Paquier <[email protected]>; Robert Haas <[email protected]>; Amit Kapila <[email protected]>; Thomas Munro <[email protected]>; Alvaro Herrera <[email protected]>; pgsql-hackers

On Wed, Dec 27, 2017 at 11:33 PM, Alexander Korotkov <
[email protected]> wrote:

>
> Also, I appreciate that now tuple_insert() and tuple_update() methods are
> responsible for inserting index tuples.  This unleash pluggable storages to
> implement another way of interaction with indexes.  However, I didn't get
> the point of passing InsertIndexTuples IndexFunc to them.  Now, we're
> always passing ExecInsertIndexTuples() to this argument.  As I understood
> storage is free to either call ExecInsertIndexTuples() or implement its own
> logic of interaction with indexes.  But, I don't understand why do we need
> a callback when tuple_insert() and tuple_update() can call
> ExecInsertIndexTuples() directly if needed.  Another thing is that
> tuple_delete() could also interact with indexes (especially when we will
> enhance index access method API), and we need to pass meta-information
> about indexes to tuple_delete() too.
>

The main reason for which I added the callback function to not to introduce
the
dependency of storage on executor functions. This way storage can call the
function that is passed to it without any knowledge. I added the function
pointer
for tuple_delete also in the new patches, currently it is passed as NULL
for heap.
These API's can be enhanced later.

Apart from rebase, Added storage shared memory API, currently this API is
used
only by the syncscan. And also all the exposed functions of syncscan usage
is
removed outside the heap.

Regards,
Hari Babu
Fujitsu Australia


Attachments:

  [application/octet-stream] 0012-Storage-shared-memory-API.patch (7.4K, ../../CAJrrPGeoihBWs+BUP06q9=rCNm75m56J-EEStf3ygikNcpreYw@mail.gmail.com/3-0012-Storage-shared-memory-API.patch)
  download | inline diff:
From 245c40a2ac2e44e039d8657590873515d25793db Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Wed, 3 Jan 2018 15:31:42 +1100
Subject: [PATCH 12/12] Storage shared memory API

Added API to provide needed shared memory for
storage. As of now only the syncscan infrastructure
uses the shared memory, it can enhanced for other
storages.

And also all the sync scan exposed API usage is
removed outside heap.
---
 src/backend/access/heap/heapam_storage.c | 17 +++++++++++++++++
 src/backend/access/storage/storageam.c   | 24 ++++++++++++++++++++++++
 src/backend/executor/nodeSamplescan.c    |  2 +-
 src/backend/storage/ipc/ipci.c           |  5 +++--
 src/include/access/storageam.h           |  4 ++++
 src/include/access/storageamapi.h        |  4 ++++
 6 files changed, 53 insertions(+), 3 deletions(-)

diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index f8846519d6..78e5d9de8e 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -30,6 +30,11 @@
 #include "utils/rel.h"
 #include "utils/tqual.h"
 
+static Size heapam_storage_shmem_size(void);
+static void heapam_storage_shmem_init(void);
+
+StorageShmemSize_hook_type StorageShmemSize_hook = heapam_storage_shmem_size;
+StorageShmemInit_hook_type StorageShmemInit_hook = heapam_storage_shmem_init;
 
 /* ----------------------------------------------------------------
  *				storage AM support routines for heapam
@@ -501,6 +506,17 @@ retry:
 	return result;
 }
 
+static Size
+heapam_storage_shmem_size()
+{
+	return SyncScanShmemSize();
+}
+
+static void
+heapam_storage_shmem_init()
+{
+	return SyncScanShmemInit();
+}
 
 Datum
 heapam_storage_handler(PG_FUNCTION_ARGS)
@@ -535,6 +551,7 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 	 * BitmapHeap and Sample Scans
 	 */
 	amroutine->scan_get_heappagescandesc = heapam_get_heappagescandesc;
+	amroutine->sync_scan_report_location = ss_report_location;
 
 	amroutine->tuple_fetch = heapam_fetch;
 	amroutine->tuple_insert = heapam_heap_insert;
diff --git a/src/backend/access/storage/storageam.c b/src/backend/access/storage/storageam.c
index 39aa55f80c..a848d04698 100644
--- a/src/backend/access/storage/storageam.c
+++ b/src/backend/access/storage/storageam.c
@@ -20,6 +20,9 @@
 #include "utils/rel.h"
 #include "utils/tqual.h"
 
+extern StorageShmemSize_hook_type StorageShmemSize_hook;
+extern StorageShmemInit_hook_type StorageShmemInit_hook;
+
 /*
  *	storage_fetch		- retrieve tuple with given tid
  */
@@ -89,6 +92,12 @@ storageam_get_heappagescandesc(StorageScanDesc sscan)
 	return sscan->rs_rd->rd_stamroutine->scan_get_heappagescandesc(sscan);
 }
 
+void
+storage_syncscan_report_location(Relation rel, BlockNumber location)
+{
+	return rel->rd_stamroutine->sync_scan_report_location(rel, location);
+}
+
 /*
  * heap_setscanlimits - restrict range of a heapscan
  *
@@ -435,3 +444,18 @@ storage_rewrite_dead_tuple(Relation rel, RewriteState state, HeapTuple oldTuple)
 {
 	return rel->rd_stamroutine->rewrite_heap_dead_tuple(state, oldTuple);
 }
+
+Size
+storage_shmem_size()
+{
+	if (StorageShmemSize_hook)
+		return (*StorageShmemSize_hook)();
+	return 0;
+}
+
+void
+storage_shmem_init()
+{
+	if (StorageShmemInit_hook)
+		(*StorageShmemInit_hook) ();
+}
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index e85abd1555..88b2cef715 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -511,7 +511,7 @@ tablesample_getnext(SampleScanState *scanstate)
 			 * We don't guarantee any specific ordering in general, though.
 			 */
 			if (pagescan->rs_syncscan)
-				ss_report_location(scan->rs_rd, blockno);
+				storage_syncscan_report_location(scan->rs_rd, blockno);
 
 			finished = (blockno == pagescan->rs_startblock);
 		}
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 0c86a581c0..50157d4175 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -19,6 +19,7 @@
 #include "access/heapam.h"
 #include "access/multixact.h"
 #include "access/nbtree.h"
+#include "access/storageam.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
 #include "commands/async.h"
@@ -147,7 +148,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port)
 		size = add_size(size, ApplyLauncherShmemSize());
 		size = add_size(size, SnapMgrShmemSize());
 		size = add_size(size, BTreeShmemSize());
-		size = add_size(size, SyncScanShmemSize());
+		size = add_size(size, storage_shmem_size());
 		size = add_size(size, AsyncShmemSize());
 		size = add_size(size, BackendRandomShmemSize());
 #ifdef EXEC_BACKEND
@@ -267,7 +268,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port)
 	 */
 	SnapMgrInit();
 	BTreeShmemInit();
-	SyncScanShmemInit();
+	storage_shmem_init();
 	AsyncShmemInit();
 	BackendRandomShmemInit();
 
diff --git a/src/include/access/storageam.h b/src/include/access/storageam.h
index 3e151a70ca..996cbda63c 100644
--- a/src/include/access/storageam.h
+++ b/src/include/access/storageam.h
@@ -45,6 +45,7 @@ typedef void (*DeleteIndexTuples) (Relation rel, ItemPointer tid, TransactionId
 extern StorageScanDesc storage_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan);
 extern ParallelHeapScanDesc storageam_get_parallelheapscandesc(StorageScanDesc sscan);
 extern HeapPageScanDesc storageam_get_heappagescandesc(StorageScanDesc sscan);
+extern void storage_syncscan_report_location(Relation rel, BlockNumber location);
 extern void storage_setscanlimits(StorageScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
 extern StorageScanDesc storage_beginscan(Relation relation, Snapshot snapshot,
 										 int nkeys, ScanKey key);
@@ -130,4 +131,7 @@ extern void storage_rewrite_tuple(Relation rel, RewriteState state, HeapTuple ol
 				   HeapTuple newTuple);
 extern bool storage_rewrite_dead_tuple(Relation rel, RewriteState state, HeapTuple oldTuple);
 
+extern Size storage_shmem_size(void);
+extern void storage_shmem_init(void);
+
 #endif
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
index 4c39de6f31..2e0733e35c 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -106,6 +106,7 @@ typedef StorageScanDesc(*ScanBegin_function) (Relation relation,
 											  bool temp_snap);
 typedef ParallelHeapScanDesc (*ScanGetParallelheapscandesc_function) (StorageScanDesc scan);
 typedef HeapPageScanDesc(*ScanGetHeappagescandesc_function) (StorageScanDesc scan);
+typedef void (*SyncScanReportLocation_function) (Relation rel, BlockNumber location);
 
 typedef void (*ScanSetlimits_function) (StorageScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
 
@@ -130,6 +131,8 @@ typedef bool (*HotSearchBuffer_function) (ItemPointer tid, Relation relation,
 										  Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
 										  bool *all_dead, bool first_call);
 
+typedef Size (*StorageShmemSize_hook_type) (void);
+typedef void (*StorageShmemInit_hook_type) (void);
 
 /*
  * API struct for a storage AM.  Note this must be stored in a single palloc'd
@@ -177,6 +180,7 @@ typedef struct StorageAmRoutine
 	ScanBegin_function scan_begin;
 	ScanGetParallelheapscandesc_function scan_get_parallelheapscandesc;
 	ScanGetHeappagescandesc_function scan_get_heappagescandesc;
+	SyncScanReportLocation_function sync_scan_report_location;
 	ScanSetlimits_function scansetlimits;
 	ScanGetnext_function scan_getnext;
 	ScanGetnextSlot_function scan_getnextslot;
-- 
2.15.0.windows.1



  [application/octet-stream] 0001-Change-Create-Access-method-to-include-storage-handl.patch (9.3K, ../../CAJrrPGeoihBWs+BUP06q9=rCNm75m56J-EEStf3ygikNcpreYw@mail.gmail.com/4-0001-Change-Create-Access-method-to-include-storage-handl.patch)
  download | inline diff:
From 55947e29755bc3b6fad0fa7cb1e0001d32c95d49 Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Tue, 29 Aug 2017 19:45:30 +1000
Subject: [PATCH 01/12] Change Create Access method to include storage handler

Add the support of storage handler as an access method
---
 src/backend/commands/amcmds.c            | 17 ++++++++++++++---
 src/backend/parser/gram.y                | 11 +++++++++--
 src/backend/utils/adt/pseudotypes.c      |  1 +
 src/include/catalog/pg_am.h              |  1 +
 src/include/catalog/pg_proc.h            |  4 ++++
 src/include/catalog/pg_type.h            |  2 ++
 src/test/regress/expected/opr_sanity.out | 19 ++++++++++++++++---
 src/test/regress/sql/opr_sanity.sql      | 16 +++++++++++++---
 8 files changed, 60 insertions(+), 11 deletions(-)

diff --git a/src/backend/commands/amcmds.c b/src/backend/commands/amcmds.c
index f2173450ad..d4afe4bf0b 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -29,7 +29,7 @@
 #include "utils/syscache.h"
 
 
-static Oid	lookup_index_am_handler_func(List *handler_name, char amtype);
+static Oid	lookup_am_handler_func(List *handler_name, char amtype);
 static const char *get_am_type_string(char amtype);
 
 
@@ -72,7 +72,7 @@ CreateAccessMethod(CreateAmStmt *stmt)
 	/*
 	 * Get the handler function oid, verifying the AM type while at it.
 	 */
-	amhandler = lookup_index_am_handler_func(stmt->handler_name, stmt->amtype);
+	amhandler = lookup_am_handler_func(stmt->handler_name, stmt->amtype);
 
 	/*
 	 * Insert tuple into pg_am.
@@ -225,6 +225,8 @@ get_am_type_string(char amtype)
 	{
 		case AMTYPE_INDEX:
 			return "INDEX";
+		case AMTYPE_STORAGE:
+			return "STORAGE";
 		default:
 			/* shouldn't happen */
 			elog(ERROR, "invalid access method type '%c'", amtype);
@@ -239,7 +241,7 @@ get_am_type_string(char amtype)
  * This function either return valid function Oid or throw an error.
  */
 static Oid
-lookup_index_am_handler_func(List *handler_name, char amtype)
+lookup_am_handler_func(List *handler_name, char amtype)
 {
 	Oid			handlerOid;
 	static const Oid funcargtypes[1] = {INTERNALOID};
@@ -263,6 +265,15 @@ lookup_index_am_handler_func(List *handler_name, char amtype)
 								NameListToString(handler_name),
 								"index_am_handler")));
 			break;
+			/* XXX refactor duplicate error */
+		case AMTYPE_STORAGE:
+			if (get_func_rettype(handlerOid) != STORAGE_AM_HANDLEROID)
+				ereport(ERROR,
+						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+						 errmsg("function %s must return type %s",
+								NameListToString(handler_name),
+								"storage_am_handler")));
+			break;
 		default:
 			elog(ERROR, "unrecognized access method type \"%c\"", amtype);
 	}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 16923e853a..2ddcdaac9c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -321,6 +321,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		OptSchemaName
 %type <list>	OptSchemaEltList
 
+%type <chr>		am_type
+
 %type <boolean> TriggerForSpec TriggerForType
 %type <ival>	TriggerActionTime
 %type <list>	TriggerEvents TriggerOneEvent
@@ -5279,16 +5281,21 @@ row_security_cmd:
  *
  *****************************************************************************/
 
-CreateAmStmt: CREATE ACCESS METHOD name TYPE_P INDEX HANDLER handler_name
+CreateAmStmt: CREATE ACCESS METHOD name TYPE_P am_type HANDLER handler_name
 				{
 					CreateAmStmt *n = makeNode(CreateAmStmt);
 					n->amname = $4;
 					n->handler_name = $8;
-					n->amtype = AMTYPE_INDEX;
+					n->amtype = $6;
 					$$ = (Node *) n;
 				}
 		;
 
+am_type:
+			INDEX			{ $$ = AMTYPE_INDEX; }
+		|	STORAGE			{ $$ = AMTYPE_STORAGE; }
+		;
+
 /*****************************************************************************
  *
  *		QUERIES :
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index dbe67cdb4c..3605ab35b5 100644
--- a/src/backend/utils/adt/pseudotypes.c
+++ b/src/backend/utils/adt/pseudotypes.c
@@ -418,3 +418,4 @@ PSEUDOTYPE_DUMMY_IO_FUNCS(internal);
 PSEUDOTYPE_DUMMY_IO_FUNCS(opaque);
 PSEUDOTYPE_DUMMY_IO_FUNCS(anyelement);
 PSEUDOTYPE_DUMMY_IO_FUNCS(anynonarray);
+PSEUDOTYPE_DUMMY_IO_FUNCS(storage_am_handler);
diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h
index 2e785c4cec..b3532e4058 100644
--- a/src/include/catalog/pg_am.h
+++ b/src/include/catalog/pg_am.h
@@ -59,6 +59,7 @@ typedef FormData_pg_am *Form_pg_am;
  * ----------------
  */
 #define AMTYPE_INDEX					'i' /* index access method */
+#define AMTYPE_STORAGE                  's' /* storage access method */
 
 /* ----------------
  *		initial contents of pg_am
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 298e0ae2f0..f3b471a0b6 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3877,6 +3877,10 @@ DATA(insert OID = 326  (  index_am_handler_in	PGNSP PGUID 12 1 0 0 0 f f f f f f
 DESCR("I/O");
 DATA(insert OID = 327  (  index_am_handler_out	PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2275 "325" _null_ _null_ _null_ _null_ _null_ index_am_handler_out _null_ _null_ _null_ ));
 DESCR("I/O");
+DATA(insert OID = 3425  (  storage_am_handler_in	PGNSP PGUID 12 1 0 0 0 f f f f f f i s 1 0 3998 "2275" _null_ _null_ _null_ _null_ _null_ storage_am_handler_in _null_ _null_ _null_ ));
+DESCR("I/O");
+DATA(insert OID = 3426  (  storage_am_handler_out	PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2275 "3998" _null_ _null_ _null_ _null_ _null_ storage_am_handler_out _null_ _null_ _null_ ));
+DESCR("I/O");
 DATA(insert OID = 3311 (  tsm_handler_in	PGNSP PGUID 12 1 0 0 0 f f f f f f i s 1 0 3310 "2275" _null_ _null_ _null_ _null_ _null_ tsm_handler_in _null_ _null_ _null_ ));
 DESCR("I/O");
 DATA(insert OID = 3312 (  tsm_handler_out	PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2275 "3310" _null_ _null_ _null_ _null_ _null_ tsm_handler_out _null_ _null_ _null_ ));
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 5b5b1218de..a88f6bd8bf 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -708,6 +708,8 @@ DATA(insert OID = 3115 ( fdw_handler	PGNSP PGUID  4 t p P f t \054 0 0 0 fdw_han
 #define FDW_HANDLEROID	3115
 DATA(insert OID = 325 ( index_am_handler	PGNSP PGUID  4 t p P f t \054 0 0 0 index_am_handler_in index_am_handler_out - - - - - i p f 0 -1 0 0 _null_ _null_ _null_ ));
 #define INDEX_AM_HANDLEROID 325
+DATA(insert OID = 3998 ( storage_am_handler	PGNSP PGUID 4 t p P f t \054 0 0 0 storage_am_handler_in storage_am_handler_out - - - - - i p f 0 -1 0 0 _null_ _null_ _null_ ));
+#define STORAGE_AM_HANDLEROID	3998
 DATA(insert OID = 3310 ( tsm_handler	PGNSP PGUID  4 t p P f t \054 0 0 0 tsm_handler_in tsm_handler_out - - - - - i p f 0 -1 0 0 _null_ _null_ _null_ ));
 #define TSM_HANDLEROID	3310
 DATA(insert OID = 3831 ( anyrange		PGNSP PGUID  -1 f p P f t \054 0 0 0 anyrange_in anyrange_out - - - - - d x f 0 -1 0 0 _null_ _null_ _null_ ));
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 684f7f20a8..3113966415 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1713,11 +1713,24 @@ WHERE p1.amhandler = 0;
 -----+--------
 (0 rows)
 
--- Check for amhandler functions with the wrong signature
+-- Check for index amhandler functions with the wrong signature
 SELECT p1.oid, p1.amname, p2.oid, p2.proname
 FROM pg_am AS p1, pg_proc AS p2
-WHERE p2.oid = p1.amhandler AND
-    (p2.prorettype != 'index_am_handler'::regtype OR p2.proretset
+WHERE p2.oid = p1.amhandler AND p1.amtype = 'i' AND
+    (p2.prorettype != 'index_am_handler'::regtype
+     OR p2.proretset
+     OR p2.pronargs != 1
+     OR p2.proargtypes[0] != 'internal'::regtype);
+ oid | amname | oid | proname 
+-----+--------+-----+---------
+(0 rows)
+
+-- Check for storage amhandler functions with the wrong signature
+SELECT p1.oid, p1.amname, p2.oid, p2.proname
+FROM pg_am AS p1, pg_proc AS p2
+WHERE p2.oid = p1.amhandler AND p1.amtype = 's' AND
+    (p2.prorettype != 'storage_am_handler'::regtype
+     OR p2.proretset
      OR p2.pronargs != 1
      OR p2.proargtypes[0] != 'internal'::regtype);
  oid | amname | oid | proname 
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index e8fdf8454d..bb1570c94f 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -1155,15 +1155,25 @@ SELECT p1.oid, p1.amname
 FROM pg_am AS p1
 WHERE p1.amhandler = 0;
 
--- Check for amhandler functions with the wrong signature
+-- Check for index amhandler functions with the wrong signature
 
 SELECT p1.oid, p1.amname, p2.oid, p2.proname
 FROM pg_am AS p1, pg_proc AS p2
-WHERE p2.oid = p1.amhandler AND
-    (p2.prorettype != 'index_am_handler'::regtype OR p2.proretset
+WHERE p2.oid = p1.amhandler AND p1.amtype = 'i' AND
+    (p2.prorettype != 'index_am_handler'::regtype
+     OR p2.proretset
      OR p2.pronargs != 1
      OR p2.proargtypes[0] != 'internal'::regtype);
 
+-- Check for storage amhandler functions with the wrong signature
+
+SELECT p1.oid, p1.amname, p2.oid, p2.proname
+FROM pg_am AS p1, pg_proc AS p2
+WHERE p2.oid = p1.amhandler AND p1.amtype = 's' AND
+    (p2.prorettype != 'storage_am_handler'::regtype
+     OR p2.proretset
+     OR p2.pronargs != 1
+     OR p2.proargtypes[0] != 'internal'::regtype);
 
 -- **************** pg_amop ****************
 
-- 
2.15.0.windows.1



  [application/octet-stream] 0002-Storage-AM-folder-and-init-functions.patch (11.0K, ../../CAJrrPGeoihBWs+BUP06q9=rCNm75m56J-EEStf3ygikNcpreYw@mail.gmail.com/5-0002-Storage-AM-folder-and-init-functions.patch)
  download | inline diff:
From 1ca8f8f2f92a3d7b4605e4b47e50e245d3f10265 Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Wed, 30 Aug 2017 12:41:15 +1000
Subject: [PATCH 02/12] Storage AM folder and init functions

---
 src/backend/access/Makefile               |   2 +-
 src/backend/access/heap/Makefile          |   3 +-
 src/backend/access/heap/heapam_storage.c  |  33 ++++++++++
 src/backend/access/storage/Makefile       |  17 +++++
 src/backend/access/storage/storageam.c    |  15 +++++
 src/backend/access/storage/storageamapi.c | 103 ++++++++++++++++++++++++++++++
 src/include/access/storageamapi.h         |  39 +++++++++++
 src/include/catalog/pg_am.h               |   3 +
 src/include/catalog/pg_proc.h             |   5 ++
 src/include/nodes/nodes.h                 |   1 +
 10 files changed, 219 insertions(+), 2 deletions(-)
 create mode 100644 src/backend/access/heap/heapam_storage.c
 create mode 100644 src/backend/access/storage/Makefile
 create mode 100644 src/backend/access/storage/storageam.c
 create mode 100644 src/backend/access/storage/storageamapi.c
 create mode 100644 src/include/access/storageamapi.h

diff --git a/src/backend/access/Makefile b/src/backend/access/Makefile
index bd93a6a8d1..e72ad6c86c 100644
--- a/src/backend/access/Makefile
+++ b/src/backend/access/Makefile
@@ -9,6 +9,6 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 SUBDIRS	    = brin common gin gist hash heap index nbtree rmgrdesc spgist \
-			  tablesample transam
+			  storage tablesample transam
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/heap/Makefile b/src/backend/access/heap/Makefile
index b83d496bcd..816f03a86f 100644
--- a/src/backend/access/heap/Makefile
+++ b/src/backend/access/heap/Makefile
@@ -12,6 +12,7 @@ subdir = src/backend/access/heap
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = heapam.o hio.o pruneheap.o rewriteheap.o syncscan.o tuptoaster.o visibilitymap.o
+OBJS = heapam.o hio.o heapam_storage.o pruneheap.o rewriteheap.o \
+	syncscan.o tuptoaster.o visibilitymap.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
new file mode 100644
index 0000000000..792e9cb436
--- /dev/null
+++ b/src/backend/access/heap/heapam_storage.c
@@ -0,0 +1,33 @@
+/*-------------------------------------------------------------------------
+ *
+ * heapam_storage.c
+ *	  heap storage access method code
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/heap/heapam_storage.c
+ *
+ *
+ * NOTES
+ *	  This file contains the heap_ routines which implement
+ *	  the POSTGRES heap access method used for all POSTGRES
+ *	  relations.
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/storageamapi.h"
+#include "utils/builtins.h"
+
+
+Datum
+heapam_storage_handler(PG_FUNCTION_ARGS)
+{
+	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
+
+	PG_RETURN_POINTER(amroutine);
+}
diff --git a/src/backend/access/storage/Makefile b/src/backend/access/storage/Makefile
new file mode 100644
index 0000000000..2a05c7ce66
--- /dev/null
+++ b/src/backend/access/storage/Makefile
@@ -0,0 +1,17 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+#    Makefile for access/storage
+#
+# IDENTIFICATION
+#    src/backend/access/storage/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/access/storage
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = storageam.o storageamapi.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/storage/storageam.c b/src/backend/access/storage/storageam.c
new file mode 100644
index 0000000000..8541c75782
--- /dev/null
+++ b/src/backend/access/storage/storageam.c
@@ -0,0 +1,15 @@
+/*-------------------------------------------------------------------------
+ *
+ * storageam.c
+ *	  storage access method code
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/storage/storageam.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
diff --git a/src/backend/access/storage/storageamapi.c b/src/backend/access/storage/storageamapi.c
new file mode 100644
index 0000000000..bcbe14588b
--- /dev/null
+++ b/src/backend/access/storage/storageamapi.c
@@ -0,0 +1,103 @@
+/*----------------------------------------------------------------------
+ *
+ * storageamapi.c
+ *		Support routines for API for Postgres storage access methods
+ *
+ * FIXME: looks like this should be in amapi.c.
+ *
+ * Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * src/backend/access/heap/storageamapi.c
+ *----------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/storageamapi.h"
+#include "catalog/pg_am.h"
+#include "catalog/pg_proc.h"
+#include "utils/syscache.h"
+#include "utils/memutils.h"
+
+
+/*
+ * GetStorageAmRoutine
+ *		Call the specified access method handler routine to get its
+ *		StorageAmRoutine struct, which will be palloc'd in the caller's
+ *		memory context.
+ */
+StorageAmRoutine *
+GetStorageAmRoutine(Oid amhandler)
+{
+	Datum		datum;
+	StorageAmRoutine *routine;
+
+	datum = OidFunctionCall0(amhandler);
+	routine = (StorageAmRoutine *) DatumGetPointer(datum);
+
+	if (routine == NULL || !IsA(routine, StorageAmRoutine))
+		elog(ERROR, "storage access method handler %u did not return a StorageAmRoutine struct",
+			 amhandler);
+
+	return routine;
+}
+
+/* A crock */
+StorageAmRoutine *
+GetHeapamStorageAmRoutine(void)
+{
+	Datum		datum;
+	static StorageAmRoutine * HeapamStorageAmRoutine = NULL;
+
+	if (HeapamStorageAmRoutine == NULL)
+	{
+		MemoryContext oldcxt;
+
+		oldcxt = MemoryContextSwitchTo(TopMemoryContext);
+		datum = OidFunctionCall0(HEAPAM_STORAGE_AM_HANDLER_OID);
+		HeapamStorageAmRoutine = (StorageAmRoutine *) DatumGetPointer(datum);
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	return HeapamStorageAmRoutine;
+}
+
+/*
+ * GetStorageAmRoutineByAmId - look up the handler of the storage access
+ * method with the given OID, and get its StorageAmRoutine struct.
+ */
+StorageAmRoutine *
+GetStorageAmRoutineByAmId(Oid amoid)
+{
+	regproc		amhandler;
+	HeapTuple	tuple;
+	Form_pg_am	amform;
+
+	/* Get handler function OID for the access method */
+	tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(amoid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for access method %u",
+			 amoid);
+	amform = (Form_pg_am) GETSTRUCT(tuple);
+
+	/* Check that it is a storage access method */
+	if (amform->amtype != AMTYPE_STORAGE)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("access method \"%s\" is not of type %s",
+						NameStr(amform->amname), "STORAGE")));
+
+	amhandler = amform->amhandler;
+
+	/* Complain if handler OID is invalid */
+	if (!RegProcedureIsValid(amhandler))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("storage access method \"%s\" does not have a handler",
+						NameStr(amform->amname))));
+
+	ReleaseSysCache(tuple);
+
+	/* And finally, call the handler function to get the API struct. */
+	return GetStorageAmRoutine(amhandler);
+}
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
new file mode 100644
index 0000000000..6fae4eea5c
--- /dev/null
+++ b/src/include/access/storageamapi.h
@@ -0,0 +1,39 @@
+/*---------------------------------------------------------------------
+ *
+ * storageamapi.h
+ *		API for Postgres storage access methods
+ *
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ *
+ * src/include/access/storageamapi.h
+ *---------------------------------------------------------------------
+ */
+#ifndef STORAGEAMAPI_H
+#define STORAGEAMAPI_H
+
+#include "nodes/nodes.h"
+#include "fmgr.h"
+
+/* A physical tuple coming from a storage AM scan */
+typedef void *StorageTuple;
+
+/*
+ * API struct for a storage AM.  Note this must be stored in a single palloc'd
+ * chunk of memory.
+ *
+ * XXX currently all functions are together in a single struct.  Would it be
+ * worthwhile to split the slot-accessor functions to a different struct?
+ * That way, MinimalTuple could be handled without a complete StorageAmRoutine
+ * for them -- it'd only have a few functions in TupleTableSlotAmRoutine or so.
+ */
+typedef struct StorageAmRoutine
+{
+	NodeTag		type;
+
+}			StorageAmRoutine;
+
+extern StorageAmRoutine * GetStorageAmRoutine(Oid amhandler);
+extern StorageAmRoutine * GetStorageAmRoutineByAmId(Oid amoid);
+extern StorageAmRoutine * GetHeapamStorageAmRoutine(void);
+
+#endif							/* STORAGEAMAPI_H */
diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h
index b3532e4058..e946f6ad3b 100644
--- a/src/include/catalog/pg_am.h
+++ b/src/include/catalog/pg_am.h
@@ -84,5 +84,8 @@ DESCR("SP-GiST index access method");
 DATA(insert OID = 3580 (  brin		brinhandler i ));
 DESCR("block range index (BRIN) access method");
 #define BRIN_AM_OID 3580
+DATA(insert OID = 4001 (  heapam         heapam_storage_handler s ));
+DESCR("heapam storage access method");
+#define HEAPAM_STORAGE_AM_OID 4001
 
 #endif							/* PG_AM_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index f3b471a0b6..f0d1b9311a 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -558,6 +558,11 @@ DESCR("convert int4 to float4");
 DATA(insert OID = 319 (  int4			   PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1  0 23 "700" _null_ _null_ _null_ _null_ _null_	ftoi4 _null_ _null_ _null_ ));
 DESCR("convert float4 to int4");
 
+/* Storage access method handlers */
+DATA(insert OID = 4002 (  heapam_storage_handler		PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 3998 "2281" _null_ _null_ _null_ _null_ _null_	heapam_storage_handler _null_ _null_ _null_ ));
+DESCR("row-oriented storage access method handler");
+#define HEAPAM_STORAGE_AM_HANDLER_OID	4002
+
 /* Index access method handlers */
 DATA(insert OID = 330 (  bthandler		PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 325 "2281" _null_ _null_ _null_ _null_ _null_	bthandler _null_ _null_ _null_ ));
 DESCR("btree index access method handler");
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 2eb3d6d371..6477664b30 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -499,6 +499,7 @@ typedef enum NodeTag
 	T_InlineCodeBlock,			/* in nodes/parsenodes.h */
 	T_FdwRoutine,				/* in foreign/fdwapi.h */
 	T_IndexAmRoutine,			/* in access/amapi.h */
+	T_StorageAmRoutine,			/* in access/storageamapi.h */
 	T_TsmRoutine,				/* in access/tsmapi.h */
 	T_ForeignKeyCacheInfo		/* in utils/rel.h */
 } NodeTag;
-- 
2.15.0.windows.1



  [application/octet-stream] 0003-Adding-storageam-hanlder-to-relation-structure.patch (7.0K, ../../CAJrrPGeoihBWs+BUP06q9=rCNm75m56J-EEStf3ygikNcpreYw@mail.gmail.com/6-0003-Adding-storageam-hanlder-to-relation-structure.patch)
  download | inline diff:
From 70e05d3117240a1df2dbc12b0ede9bd9aa2d8b46 Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Wed, 30 Aug 2017 12:49:46 +1000
Subject: [PATCH 03/12] Adding storageam hanlder to relation structure

And also the necessary functions to initialize
the storageam handler
---
 src/backend/utils/cache/relcache.c | 119 ++++++++++++++++++++++++++++++++++++-
 src/include/utils/rel.h            |  12 ++++
 src/include/utils/relcache.h       |   2 +
 3 files changed, 130 insertions(+), 3 deletions(-)

diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 28a4483434..095b61b604 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -35,6 +35,7 @@
 #include "access/multixact.h"
 #include "access/nbtree.h"
 #include "access/reloptions.h"
+#include "access/storageamapi.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "access/xlog.h"
@@ -1325,10 +1326,27 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	}
 
 	/*
-	 * if it's an index, initialize index-related information
+	 * initialize access method information
 	 */
-	if (OidIsValid(relation->rd_rel->relam))
-		RelationInitIndexAccessInfo(relation);
+	switch (relation->rd_rel->relkind)
+	{
+		case RELKIND_INDEX:
+			Assert(relation->rd_rel->relkind != InvalidOid);
+			RelationInitIndexAccessInfo(relation);
+			break;
+		case RELKIND_RELATION:
+		case RELKIND_SEQUENCE:
+		case RELKIND_TOASTVALUE:
+		case RELKIND_VIEW:		/* Not exactly the storage, but underlying
+								 * tuple access, it is required */
+		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
+			RelationInitStorageAccessInfo(relation);
+			break;
+		default:
+			/* nothing to do in other cases */
+			break;
+	}
 
 	/* extract reloptions if any */
 	RelationParseRelOptions(relation, pg_class_tuple);
@@ -1826,6 +1844,71 @@ LookupOpclassInfo(Oid operatorClassOid,
 	return opcentry;
 }
 
+/*
+ * Fill in the StorageAmRoutine for a relation
+ *
+ * relation's rd_amhandler and rd_indexcxt (XXX?) must be valid already.
+ */
+static void
+InitStorageAmRoutine(Relation relation)
+{
+	StorageAmRoutine *cached,
+			   *tmp;
+
+	/*
+	 * Call the amhandler in current, short-lived memory context, just in case
+	 * it leaks anything (it probably won't, but let's be paranoid).
+	 */
+	tmp = GetStorageAmRoutine(relation->rd_amhandler);
+
+	/* XXX do we need a separate memory context for this? */
+	/* OK, now transfer the data into cache context */
+	cached = (StorageAmRoutine *) MemoryContextAlloc(CacheMemoryContext,
+													 sizeof(StorageAmRoutine));
+	memcpy(cached, tmp, sizeof(StorageAmRoutine));
+	relation->rd_stamroutine = cached;
+
+	pfree(tmp);
+}
+
+/*
+ * Initialize storage-access-method support data for a heap relation
+ */
+void
+RelationInitStorageAccessInfo(Relation relation)
+{
+	HeapTuple	tuple;
+	Form_pg_am	aform;
+
+	/*
+	 * Relations that don't have a catalogued storage access method use the
+	 * standard heapam module; otherwise a catalog lookup is in order.
+	 */
+	if (!OidIsValid(relation->rd_rel->relam))
+	{
+		relation->rd_amhandler = HEAPAM_STORAGE_AM_HANDLER_OID;
+	}
+	else
+	{
+		/*
+		 * Look up the storage access method, save the OID of its handler
+		 * function.
+		 */
+		tuple = SearchSysCache1(AMOID,
+								ObjectIdGetDatum(relation->rd_rel->relam));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for access method %u",
+				 relation->rd_rel->relam);
+		aform = (Form_pg_am) GETSTRUCT(tuple);
+		relation->rd_amhandler = aform->amhandler;
+		ReleaseSysCache(tuple);
+	}
+
+	/*
+	 * Now we can fetch the storage AM's API struct
+	 */
+	InitStorageAmRoutine(relation);
+}
 
 /*
  *		formrdesc
@@ -1984,6 +2067,11 @@ formrdesc(const char *relationName, Oid relationReltype,
 	 */
 	RelationInitPhysicalAddr(relation);
 
+	/*
+	 * initialize the storage am handler
+	 */
+	relation->rd_stamroutine = GetHeapamStorageAmRoutine();
+
 	/*
 	 * initialize the rel-has-index flag, using hardwired knowledge
 	 */
@@ -2312,6 +2400,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		pfree(relation->rd_pubactions);
 	if (relation->rd_options)
 		pfree(relation->rd_options);
+	if (relation->rd_stamroutine)
+		pfree(relation->rd_stamroutine);
 	if (relation->rd_indextuple)
 		pfree(relation->rd_indextuple);
 	if (relation->rd_indexcxt)
@@ -3326,6 +3416,14 @@ RelationBuildLocalRelation(const char *relname,
 
 	RelationInitPhysicalAddr(rel);
 
+	if (relkind == RELKIND_RELATION ||
+		relkind == RELKIND_MATVIEW ||
+		relkind == RELKIND_VIEW ||	/* Not exactly the storage, but underlying
+									 * tuple access, it is required */
+		relkind == RELKIND_PARTITIONED_TABLE ||
+		relkind == RELKIND_TOASTVALUE)
+		RelationInitStorageAccessInfo(rel);
+
 	/*
 	 * Okay to insert into the relcache hash table.
 	 *
@@ -3847,6 +3945,18 @@ RelationCacheInitializePhase3(void)
 			restart = true;
 		}
 
+		if (relation->rd_stamroutine == NULL &&
+			(relation->rd_rel->relkind == RELKIND_RELATION ||
+			 relation->rd_rel->relkind == RELKIND_MATVIEW ||
+			 relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+			 relation->rd_rel->relkind == RELKIND_TOASTVALUE))
+		{
+			RelationInitStorageAccessInfo(relation);
+			Assert(relation->rd_stamroutine != NULL);
+
+			restart = true;
+		}
+
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -5567,6 +5677,9 @@ load_relcache_init_file(bool shared)
 			if (rel->rd_isnailed)
 				nailed_rels++;
 
+			/* Load storage AM stuff */
+			RelationInitStorageAccessInfo(rel);
+
 			Assert(rel->rd_index == NULL);
 			Assert(rel->rd_indextuple == NULL);
 			Assert(rel->rd_indexcxt == NULL);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index aa8add544a..55db1fcbea 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -160,6 +160,12 @@ typedef struct RelationData
 	/* use "struct" here to avoid needing to include htup.h: */
 	struct HeapTupleData *rd_indextuple;	/* all of pg_index tuple */
 
+	/*
+	 * Underlying storage support
+	 */
+	Oid			rd_storageam;	/* OID of storage AM handler function */
+	struct StorageAmRoutine *rd_stamroutine;	/* storage AM's API struct */
+
 	/*
 	 * index access support info (used only for an index relation)
 	 *
@@ -436,6 +442,12 @@ typedef struct ViewOptions
  */
 #define RelationGetDescr(relation) ((relation)->rd_att)
 
+/*
+ * RelationGetStorageRoutine
+ *		Returns the storage AM routine for a relation.
+ */
+#define RelationGetStorageRoutine(relation) ((relation)->rd_stamroutine)
+
 /*
  * RelationGetRelationName
  *		Returns the rel's name.
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 8a546aba28..4effd56a7d 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -76,6 +76,8 @@ extern void RelationInitIndexAccessInfo(Relation relation);
 struct PublicationActions;
 extern struct PublicationActions *GetRelationPublicationActions(Relation relation);
 
+extern void RelationInitStorageAccessInfo(Relation relation);
+
 /*
  * Routines to support ereport() reports of relation-related errors
  */
-- 
2.15.0.windows.1



  [application/octet-stream] 0004-Adding-tuple-visibility-function-to-storage-AM.patch (54.0K, ../../CAJrrPGeoihBWs+BUP06q9=rCNm75m56J-EEStf3ygikNcpreYw@mail.gmail.com/7-0004-Adding-tuple-visibility-function-to-storage-AM.patch)
  download | inline diff:
From 6abc4434300f239594abb4105819701cdcc792e3 Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Wed, 3 Jan 2018 17:09:40 +1100
Subject: [PATCH 04/12] Adding tuple visibility function to storage AM

Tuple visibility functions are now part of the
heap storage AM routine. The visibilty execution
procedure is changed accoridngly.

The snapshot satifies function is changed to an
enum to represent what type of snapshot is it
and this enum value is used to call the corresponding
visibilty function from the storage AM when the
visibilty of the tuple is required.

The common code is that is part of both server
and pluggable storages is now moved into storage_common.c
and storage_common.h files.
---
 contrib/pg_visibility/pg_visibility.c              |  11 +-
 contrib/pgrowlocks/pgrowlocks.c                    |   7 +-
 contrib/pgstattuple/pgstatapprox.c                 |   7 +-
 contrib/pgstattuple/pgstattuple.c                  |   3 +-
 src/backend/access/heap/Makefile                   |   2 +-
 src/backend/access/heap/heapam.c                   |  61 ++++--
 src/backend/access/heap/heapam_storage.c           |   6 +
 .../tqual.c => access/heap/heapam_visibility.c}    | 244 ++++++++++++---------
 src/backend/access/heap/pruneheap.c                |   4 +-
 src/backend/access/index/genam.c                   |   4 +-
 src/backend/access/storage/Makefile                |   2 +-
 src/backend/access/storage/storage_common.c        |  26 +++
 src/backend/catalog/index.c                        |   6 +-
 src/backend/commands/analyze.c                     |   6 +-
 src/backend/commands/cluster.c                     |   3 +-
 src/backend/commands/vacuumlazy.c                  |   4 +-
 src/backend/executor/nodeBitmapHeapscan.c          |   2 +-
 src/backend/executor/nodeModifyTable.c             |   7 +-
 src/backend/executor/nodeSamplescan.c              |   3 +-
 src/backend/replication/logical/snapbuild.c        |   6 +-
 src/backend/storage/lmgr/predicate.c               |   2 +-
 src/backend/utils/adt/ri_triggers.c                |   2 +-
 src/backend/utils/time/Makefile                    |   2 +-
 src/backend/utils/time/snapmgr.c                   |  10 +-
 src/include/access/heapam.h                        |  13 ++
 src/include/access/storage_common.h                |  41 ++++
 src/include/access/storageamapi.h                  |  16 +-
 src/include/storage/bufmgr.h                       |   5 +-
 src/include/utils/snapshot.h                       |  14 +-
 src/include/utils/tqual.h                          |  54 +----
 30 files changed, 354 insertions(+), 219 deletions(-)
 rename src/backend/{utils/time/tqual.c => access/heap/heapam_visibility.c} (95%)
 create mode 100644 src/backend/access/storage/storage_common.c
 create mode 100644 src/include/access/storage_common.h

diff --git a/contrib/pg_visibility/pg_visibility.c b/contrib/pg_visibility/pg_visibility.c
index 944dea66fc..fcc3e36a33 100644
--- a/contrib/pg_visibility/pg_visibility.c
+++ b/contrib/pg_visibility/pg_visibility.c
@@ -11,6 +11,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/storageamapi.h"
 #include "access/visibilitymap.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage_xlog.h"
@@ -51,7 +52,7 @@ static vbits *collect_visibility_data(Oid relid, bool include_pd);
 static corrupt_items *collect_corrupt_items(Oid relid, bool all_visible,
 					  bool all_frozen);
 static void record_corrupt_item(corrupt_items *items, ItemPointer tid);
-static bool tuple_all_visible(HeapTuple tup, TransactionId OldestXmin,
+static bool tuple_all_visible(Relation rel, HeapTuple tup, TransactionId OldestXmin,
 				  Buffer buffer);
 static void check_relation_relkind(Relation rel);
 
@@ -656,7 +657,7 @@ collect_corrupt_items(Oid relid, bool all_visible, bool all_frozen)
 			 * the tuple to be all-visible.
 			 */
 			if (check_visible &&
-				!tuple_all_visible(&tuple, OldestXmin, buffer))
+				!tuple_all_visible(rel, &tuple, OldestXmin, buffer))
 			{
 				TransactionId RecomputedOldestXmin;
 
@@ -681,7 +682,7 @@ collect_corrupt_items(Oid relid, bool all_visible, bool all_frozen)
 				else
 				{
 					OldestXmin = RecomputedOldestXmin;
-					if (!tuple_all_visible(&tuple, OldestXmin, buffer))
+					if (!tuple_all_visible(rel, &tuple, OldestXmin, buffer))
 						record_corrupt_item(items, &tuple.t_self);
 				}
 			}
@@ -739,12 +740,12 @@ record_corrupt_item(corrupt_items *items, ItemPointer tid)
  * The buffer should contain the tuple and should be locked and pinned.
  */
 static bool
-tuple_all_visible(HeapTuple tup, TransactionId OldestXmin, Buffer buffer)
+tuple_all_visible(Relation rel, HeapTuple tup, TransactionId OldestXmin, Buffer buffer)
 {
 	HTSV_Result state;
 	TransactionId xmin;
 
-	state = HeapTupleSatisfiesVacuum(tup, OldestXmin, buffer);
+	state = rel->rd_stamroutine->snapshot_satisfiesVacuum(tup, OldestXmin, buffer);
 	if (state != HEAPTUPLE_LIVE)
 		return false;			/* all-visible implies live */
 
diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index eabca65bd2..830e74fd07 100644
--- a/contrib/pgrowlocks/pgrowlocks.c
+++ b/contrib/pgrowlocks/pgrowlocks.c
@@ -26,6 +26,7 @@
 
 #include "access/multixact.h"
 #include "access/relscan.h"
+#include "access/storageamapi.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
@@ -149,9 +150,9 @@ pgrowlocks(PG_FUNCTION_ARGS)
 		/* must hold a buffer lock to call HeapTupleSatisfiesUpdate */
 		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 
-		htsu = HeapTupleSatisfiesUpdate(tuple,
-										GetCurrentCommandId(false),
-										scan->rs_cbuf);
+		htsu = rel->rd_stamroutine->snapshot_satisfiesUpdate(tuple,
+															 GetCurrentCommandId(false),
+															 scan->rs_cbuf);
 		xmax = HeapTupleHeaderGetRawXmax(tuple->t_data);
 		infomask = tuple->t_data->t_infomask;
 
diff --git a/contrib/pgstattuple/pgstatapprox.c b/contrib/pgstattuple/pgstatapprox.c
index 3cfbc08649..dc0c3753a9 100644
--- a/contrib/pgstattuple/pgstatapprox.c
+++ b/contrib/pgstattuple/pgstatapprox.c
@@ -12,12 +12,13 @@
  */
 #include "postgres.h"
 
-#include "access/visibilitymap.h"
 #include "access/transam.h"
+#include "access/visibilitymap.h"
 #include "access/xact.h"
 #include "access/multixact.h"
 #include "access/htup_details.h"
 #include "catalog/namespace.h"
+#include "commands/vacuum.h"
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
@@ -26,7 +27,7 @@
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
 #include "utils/tqual.h"
-#include "commands/vacuum.h"
+
 
 PG_FUNCTION_INFO_V1(pgstattuple_approx);
 PG_FUNCTION_INFO_V1(pgstattuple_approx_v1_5);
@@ -156,7 +157,7 @@ statapprox_heap(Relation rel, output_type *stat)
 			 * We count live and dead tuples, but we also need to add up
 			 * others in order to feed vac_estimate_reltuples.
 			 */
-			switch (HeapTupleSatisfiesVacuum(&tuple, OldestXmin, buf))
+			switch (rel->rd_stamroutine->snapshot_satisfiesVacuum(&tuple, OldestXmin, buf))
 			{
 				case HEAPTUPLE_RECENTLY_DEAD:
 					misc_count++;
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index 7ca1bb24d2..e098202f84 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -322,6 +322,7 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 	Buffer		buffer;
 	pgstattuple_type stat = {0};
 	SnapshotData SnapshotDirty;
+	StorageAmRoutine *method = rel->rd_stamroutine;
 
 	/* Disable syncscan because we assume we scan from block zero upwards */
 	scan = heap_beginscan_strat(rel, SnapshotAny, 0, NULL, true, false);
@@ -337,7 +338,7 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 		/* must hold a buffer lock to call HeapTupleSatisfiesVisibility */
 		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 
-		if (HeapTupleSatisfiesVisibility(tuple, &SnapshotDirty, scan->rs_cbuf))
+		if (HeapTupleSatisfiesVisibility(method, tuple, &SnapshotDirty, scan->rs_cbuf))
 		{
 			stat.tuple_len += tuple->t_len;
 			stat.tuple_count++;
diff --git a/src/backend/access/heap/Makefile b/src/backend/access/heap/Makefile
index 816f03a86f..f5c628395b 100644
--- a/src/backend/access/heap/Makefile
+++ b/src/backend/access/heap/Makefile
@@ -12,7 +12,7 @@ subdir = src/backend/access/heap
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = heapam.o hio.o heapam_storage.o pruneheap.o rewriteheap.o \
+OBJS = heapam.o hio.o heapam_storage.o heapam_visibility.o pruneheap.o rewriteheap.o \
 	syncscan.o tuptoaster.o visibilitymap.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index dbc8f2d6c7..bc092056f8 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -45,6 +45,7 @@
 #include "access/multixact.h"
 #include "access/parallel.h"
 #include "access/relscan.h"
+#include "access/storageamapi.h"
 #include "access/sysattr.h"
 #include "access/transam.h"
 #include "access/tuptoaster.h"
@@ -438,7 +439,7 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 			if (all_visible)
 				valid = true;
 			else
-				valid = HeapTupleSatisfiesVisibility(&loctup, snapshot, buffer);
+				valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine, &loctup, snapshot, buffer);
 
 			CheckForSerializableConflictOut(valid, scan->rs_rd, &loctup,
 											buffer, snapshot);
@@ -653,7 +654,8 @@ heapgettup(HeapScanDesc scan,
 				/*
 				 * if current tuple qualifies, return it.
 				 */
-				valid = HeapTupleSatisfiesVisibility(tuple,
+				valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine,
+													 tuple,
 													 snapshot,
 													 scan->rs_cbuf);
 
@@ -841,6 +843,7 @@ heapgettup_pagemode(HeapScanDesc scan,
 			lineindex = scan->rs_cindex + 1;
 		}
 
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 		dp = BufferGetPage(scan->rs_cbuf);
 		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
 		lines = scan->rs_ntuples;
@@ -885,6 +888,7 @@ heapgettup_pagemode(HeapScanDesc scan,
 			page = scan->rs_cblock; /* current page */
 		}
 
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 		dp = BufferGetPage(scan->rs_cbuf);
 		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
 		lines = scan->rs_ntuples;
@@ -954,23 +958,31 @@ heapgettup_pagemode(HeapScanDesc scan,
 			/*
 			 * if current tuple qualifies, return it.
 			 */
-			if (key != NULL)
+			if (HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine, tuple, scan->rs_snapshot, scan->rs_cbuf))
 			{
-				bool		valid;
+				/*
+				 * if current tuple qualifies, return it.
+				 */
+				if (key != NULL)
+				{
+					bool		valid;
 
-				HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
-							nkeys, key, valid);
-				if (valid)
+					HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
+								nkeys, key, valid);
+					if (valid)
+					{
+						scan->rs_cindex = lineindex;
+						LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+						return;
+					}
+				}
+				else
 				{
 					scan->rs_cindex = lineindex;
+					LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
 					return;
 				}
 			}
-			else
-			{
-				scan->rs_cindex = lineindex;
-				return;
-			}
 
 			/*
 			 * otherwise move to the next item on the page
@@ -982,6 +994,12 @@ heapgettup_pagemode(HeapScanDesc scan,
 				++lineindex;
 		}
 
+		/*
+		 * if we get here, it means we've exhausted the items on this page and
+		 * it's time to move to the next.
+		 */
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+
 		/*
 		 * if we get here, it means we've exhausted the items on this page and
 		 * it's time to move to the next.
@@ -1039,6 +1057,7 @@ heapgettup_pagemode(HeapScanDesc scan,
 
 		heapgetpage(scan, page);
 
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 		dp = BufferGetPage(scan->rs_cbuf);
 		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
 		lines = scan->rs_ntuples;
@@ -1831,7 +1850,7 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 
 	pgstat_count_heap_getnext(scan->rs_rd);
 
-	return &(scan->rs_ctup);
+	return heap_copytuple(&(scan->rs_ctup));
 }
 
 /*
@@ -1950,7 +1969,7 @@ heap_fetch(Relation relation,
 	/*
 	 * check time qualification of tuple, then release lock
 	 */
-	valid = HeapTupleSatisfiesVisibility(tuple, snapshot, buffer);
+	valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, tuple, snapshot, buffer);
 
 	if (valid)
 		PredicateLockTuple(relation, tuple, snapshot);
@@ -2097,7 +2116,7 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
 			ItemPointerSet(&(heapTuple->t_self), BufferGetBlockNumber(buffer), offnum);
 
 			/* If it's visible per the snapshot, we must return it */
-			valid = HeapTupleSatisfiesVisibility(heapTuple, snapshot, buffer);
+			valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, heapTuple, snapshot, buffer);
 			CheckForSerializableConflictOut(valid, relation, heapTuple,
 											buffer, snapshot);
 			/* reset to original, non-redirected, tid */
@@ -2271,7 +2290,7 @@ heap_get_latest_tid(Relation relation,
 		 * Check time qualification of tuple; if visible, set it as the new
 		 * result candidate.
 		 */
-		valid = HeapTupleSatisfiesVisibility(&tp, snapshot, buffer);
+		valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, &tp, snapshot, buffer);
 		CheckForSerializableConflictOut(valid, relation, &tp, buffer, snapshot);
 		if (valid)
 			*tid = ctid;
@@ -3097,7 +3116,7 @@ heap_delete(Relation relation, ItemPointer tid,
 	tp.t_self = *tid;
 
 l1:
-	result = HeapTupleSatisfiesUpdate(&tp, cid, buffer);
+	result = relation->rd_stamroutine->snapshot_satisfiesUpdate(&tp, cid, buffer);
 
 	if (result == HeapTupleInvisible)
 	{
@@ -3208,7 +3227,7 @@ l1:
 	if (crosscheck != InvalidSnapshot && result == HeapTupleMayBeUpdated)
 	{
 		/* Perform additional check for transaction-snapshot mode RI updates */
-		if (!HeapTupleSatisfiesVisibility(&tp, crosscheck, buffer))
+		if (!HeapTupleSatisfiesVisibility(relation->rd_stamroutine, &tp, crosscheck, buffer))
 			result = HeapTupleUpdated;
 	}
 
@@ -3668,7 +3687,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 l2:
 	checked_lockers = false;
 	locker_remains = false;
-	result = HeapTupleSatisfiesUpdate(&oldtup, cid, buffer);
+	result = relation->rd_stamroutine->snapshot_satisfiesUpdate(&oldtup, cid, buffer);
 
 	/* see below about the "no wait" case */
 	Assert(result != HeapTupleBeingUpdated || wait);
@@ -3849,7 +3868,7 @@ l2:
 	if (crosscheck != InvalidSnapshot && result == HeapTupleMayBeUpdated)
 	{
 		/* Perform additional check for transaction-snapshot mode RI updates */
-		if (!HeapTupleSatisfiesVisibility(&oldtup, crosscheck, buffer))
+		if (!HeapTupleSatisfiesVisibility(relation->rd_stamroutine, &oldtup, crosscheck, buffer))
 			result = HeapTupleUpdated;
 	}
 
@@ -4600,7 +4619,7 @@ heap_lock_tuple(Relation relation, HeapTuple tuple,
 	tuple->t_tableOid = RelationGetRelid(relation);
 
 l3:
-	result = HeapTupleSatisfiesUpdate(tuple, cid, *buffer);
+	result = relation->rd_stamroutine->snapshot_satisfiesUpdate(tuple, cid, *buffer);
 
 	if (result == HeapTupleInvisible)
 	{
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index 792e9cb436..a340c46a80 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -20,6 +20,7 @@
  */
 #include "postgres.h"
 
+#include "access/heapam.h"
 #include "access/storageamapi.h"
 #include "utils/builtins.h"
 
@@ -29,5 +30,10 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 {
 	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
 
+	amroutine->snapshot_satisfies = HeapTupleSatisfies;
+
+	amroutine->snapshot_satisfiesUpdate = HeapTupleSatisfiesUpdate;
+	amroutine->snapshot_satisfiesVacuum = HeapTupleSatisfiesVacuum;
+
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/utils/time/tqual.c b/src/backend/access/heap/heapam_visibility.c
similarity index 95%
rename from src/backend/utils/time/tqual.c
rename to src/backend/access/heap/heapam_visibility.c
index f7c4c9188c..daeb9bdb29 100644
--- a/src/backend/utils/time/tqual.c
+++ b/src/backend/access/heap/heapam_visibility.c
@@ -1,7 +1,6 @@
 /*-------------------------------------------------------------------------
  *
- * tqual.c
- *	  POSTGRES "time qualification" code, ie, tuple visibility rules.
+ * POSTGRES "time qualification" code, ie, tuple visibility rules.
  *
  * NOTE: all the HeapTupleSatisfies routines will update the tuple's
  * "hint" status bits if we see that the inserting or deleting transaction
@@ -56,13 +55,14 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  src/backend/utils/time/tqual.c
+ *	  src/backend/access/heap/heapam_visibilty.c
  *
  *-------------------------------------------------------------------------
  */
 
 #include "postgres.h"
 
+#include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/multixact.h"
 #include "access/subtrans.h"
@@ -76,11 +76,9 @@
 #include "utils/snapmgr.h"
 #include "utils/tqual.h"
 
-
 /* Static variables representing various special snapshot semantics */
-SnapshotData SnapshotSelfData = {HeapTupleSatisfiesSelf};
-SnapshotData SnapshotAnyData = {HeapTupleSatisfiesAny};
-
+SnapshotData SnapshotSelfData = {SELF_VISIBILITY};
+SnapshotData SnapshotAnyData = {ANY_VISIBILITY};
 
 /*
  * SetHintBits()
@@ -172,9 +170,10 @@ HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
  *			(Xmax != my-transaction &&			the row was deleted by another transaction
  *			 Xmax is not committed)))			that has not been committed
  */
-bool
-HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
+static bool
+HeapTupleSatisfiesSelf(StorageTuple stup, Snapshot snapshot, Buffer buffer)
 {
+	HeapTuple	htup = (HeapTuple) stup;
 	HeapTupleHeader tuple = htup->t_data;
 
 	Assert(ItemPointerIsValid(&htup->t_self));
@@ -342,8 +341,8 @@ HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
  * HeapTupleSatisfiesAny
  *		Dummy "satisfies" routine: any tuple satisfies SnapshotAny.
  */
-bool
-HeapTupleSatisfiesAny(HeapTuple htup, Snapshot snapshot, Buffer buffer)
+static bool
+HeapTupleSatisfiesAny(StorageTuple stup, Snapshot snapshot, Buffer buffer)
 {
 	return true;
 }
@@ -362,10 +361,11 @@ HeapTupleSatisfiesAny(HeapTuple htup, Snapshot snapshot, Buffer buffer)
  * Among other things, this means you can't do UPDATEs of rows in a TOAST
  * table.
  */
-bool
-HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot,
+static bool
+HeapTupleSatisfiesToast(StorageTuple stup, Snapshot snapshot,
 						Buffer buffer)
 {
+	HeapTuple	htup = (HeapTuple) stup;
 	HeapTupleHeader tuple = htup->t_data;
 
 	Assert(ItemPointerIsValid(&htup->t_self));
@@ -457,9 +457,10 @@ HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot,
  *	distinguish that case must test for it themselves.)
  */
 HTSU_Result
-HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
+HeapTupleSatisfiesUpdate(StorageTuple stup, CommandId curcid,
 						 Buffer buffer)
 {
+	HeapTuple	htup = (HeapTuple) stup;
 	HeapTupleHeader tuple = htup->t_data;
 
 	Assert(ItemPointerIsValid(&htup->t_self));
@@ -735,10 +736,11 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
  * on the insertion without aborting the whole transaction, the associated
  * token is also returned in snapshot->speculativeToken.
  */
-bool
-HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
+static bool
+HeapTupleSatisfiesDirty(StorageTuple stup, Snapshot snapshot,
 						Buffer buffer)
 {
+	HeapTuple	htup = (HeapTuple) stup;
 	HeapTupleHeader tuple = htup->t_data;
 
 	Assert(ItemPointerIsValid(&htup->t_self));
@@ -959,10 +961,11 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
  * inserting/deleting transaction was still running --- which was more cycles
  * and more contention on the PGXACT array.
  */
-bool
-HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
+static bool
+HeapTupleSatisfiesMVCC(StorageTuple stup, Snapshot snapshot,
 					   Buffer buffer)
 {
+	HeapTuple	htup = (HeapTuple) stup;
 	HeapTupleHeader tuple = htup->t_data;
 
 	Assert(ItemPointerIsValid(&htup->t_self));
@@ -1161,9 +1164,10 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
  * even if we see that the deleting transaction has committed.
  */
 HTSV_Result
-HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
+HeapTupleSatisfiesVacuum(StorageTuple stup, TransactionId OldestXmin,
 						 Buffer buffer)
 {
+	HeapTuple	htup = (HeapTuple) stup;
 	HeapTupleHeader tuple = htup->t_data;
 
 	Assert(ItemPointerIsValid(&htup->t_self));
@@ -1383,84 +1387,77 @@ HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
 	return HEAPTUPLE_DEAD;
 }
 
-
 /*
  * HeapTupleSatisfiesNonVacuumable
  *
- *	True if tuple might be visible to some transaction; false if it's
- *	surely dead to everyone, ie, vacuumable.
+ *     True if tuple might be visible to some transaction; false if it's
+ *     surely dead to everyone, ie, vacuumable.
  *
- *	This is an interface to HeapTupleSatisfiesVacuum that meets the
- *	SnapshotSatisfiesFunc API, so it can be used through a Snapshot.
- *	snapshot->xmin must have been set up with the xmin horizon to use.
+ *     This is an interface to HeapTupleSatisfiesVacuum that meets the
+ *     SnapshotSatisfiesFunc API, so it can be used through a Snapshot.
+ *     snapshot->xmin must have been set up with the xmin horizon to use.
  */
-bool
-HeapTupleSatisfiesNonVacuumable(HeapTuple htup, Snapshot snapshot,
+static bool
+HeapTupleSatisfiesNonVacuumable(StorageTuple htup, Snapshot snapshot,
 								Buffer buffer)
 {
 	return HeapTupleSatisfiesVacuum(htup, snapshot->xmin, buffer)
 		!= HEAPTUPLE_DEAD;
 }
 
-
 /*
- * HeapTupleIsSurelyDead
+ * Is the tuple really only locked?  That is, is it not updated?
  *
- *	Cheaply determine whether a tuple is surely dead to all onlookers.
- *	We sometimes use this in lieu of HeapTupleSatisfiesVacuum when the
- *	tuple has just been tested by another visibility routine (usually
- *	HeapTupleSatisfiesMVCC) and, therefore, any hint bits that can be set
- *	should already be set.  We assume that if no hint bits are set, the xmin
- *	or xmax transaction is still running.  This is therefore faster than
- *	HeapTupleSatisfiesVacuum, because we don't consult PGXACT nor CLOG.
- *	It's okay to return false when in doubt, but we must return true only
- *	if the tuple is removable.
+ * It's easy to check just infomask bits if the locker is not a multi; but
+ * otherwise we need to verify that the updating transaction has not aborted.
+ *
+ * This function is here because it follows the same time qualification rules
+ * laid out at the top of this file.
  */
 bool
-HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin)
+HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple)
 {
-	HeapTupleHeader tuple = htup->t_data;
+	TransactionId xmax;
 
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
+	/* if there's no valid Xmax, then there's obviously no update either */
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)
+		return true;
 
-	/*
-	 * If the inserting transaction is marked invalid, then it aborted, and
-	 * the tuple is definitely dead.  If it's marked neither committed nor
-	 * invalid, then we assume it's still alive (since the presumption is that
-	 * all relevant hint bits were just set moments ago).
-	 */
-	if (!HeapTupleHeaderXminCommitted(tuple))
-		return HeapTupleHeaderXminInvalid(tuple) ? true : false;
+	if (tuple->t_infomask & HEAP_XMAX_LOCK_ONLY)
+		return true;
 
-	/*
-	 * If the inserting transaction committed, but any deleting transaction
-	 * aborted, the tuple is still alive.
-	 */
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)
-		return false;
+	/* invalid xmax means no update */
+	if (!TransactionIdIsValid(HeapTupleHeaderGetRawXmax(tuple)))
+		return true;
 
 	/*
-	 * If the XMAX is just a lock, the tuple is still alive.
+	 * if HEAP_XMAX_LOCK_ONLY is not set and not a multi, then this must
+	 * necessarily have been updated
 	 */
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+	if (!(tuple->t_infomask & HEAP_XMAX_IS_MULTI))
 		return false;
 
-	/*
-	 * If the Xmax is a MultiXact, it might be dead or alive, but we cannot
-	 * know without checking pg_multixact.
-	 */
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-		return false;
+	/* ... but if it's a multi, then perhaps the updating Xid aborted. */
+	xmax = HeapTupleGetUpdateXid(tuple);
 
-	/* If deleter isn't known to have committed, assume it's still running. */
-	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
+	/* not LOCKED_ONLY, so it has to have an xmax */
+	Assert(TransactionIdIsValid(xmax));
+
+	if (TransactionIdIsCurrentTransactionId(xmax))
+		return false;
+	if (TransactionIdIsInProgress(xmax))
+		return false;
+	if (TransactionIdDidCommit(xmax))
 		return false;
 
-	/* Deleter committed, so tuple is dead if the XID is old enough. */
-	return TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin);
+	/*
+	 * not current, not in progress, not committed -- must have aborted or
+	 * crashed
+	 */
+	return true;
 }
 
+
 /*
  * XidInMVCCSnapshot
  *		Is the given XID still-in-progress according to the snapshot?
@@ -1584,55 +1581,61 @@ XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot)
 }
 
 /*
- * Is the tuple really only locked?  That is, is it not updated?
- *
- * It's easy to check just infomask bits if the locker is not a multi; but
- * otherwise we need to verify that the updating transaction has not aborted.
+ * HeapTupleIsSurelyDead
  *
- * This function is here because it follows the same time qualification rules
- * laid out at the top of this file.
+ *	Cheaply determine whether a tuple is surely dead to all onlookers.
+ *	We sometimes use this in lieu of HeapTupleSatisfiesVacuum when the
+ *	tuple has just been tested by another visibility routine (usually
+ *	HeapTupleSatisfiesMVCC) and, therefore, any hint bits that can be set
+ *	should already be set.  We assume that if no hint bits are set, the xmin
+ *	or xmax transaction is still running.  This is therefore faster than
+ *	HeapTupleSatisfiesVacuum, because we don't consult PGXACT nor CLOG.
+ *	It's okay to return false when in doubt, but we must return TRUE only
+ *	if the tuple is removable.
  */
 bool
-HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple)
+HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin)
 {
-	TransactionId xmax;
-
-	/* if there's no valid Xmax, then there's obviously no update either */
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)
-		return true;
+	HeapTupleHeader tuple = htup->t_data;
 
-	if (tuple->t_infomask & HEAP_XMAX_LOCK_ONLY)
-		return true;
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
 
-	/* invalid xmax means no update */
-	if (!TransactionIdIsValid(HeapTupleHeaderGetRawXmax(tuple)))
-		return true;
+	/*
+	 * If the inserting transaction is marked invalid, then it aborted, and
+	 * the tuple is definitely dead.  If it's marked neither committed nor
+	 * invalid, then we assume it's still alive (since the presumption is that
+	 * all relevant hint bits were just set moments ago).
+	 */
+	if (!HeapTupleHeaderXminCommitted(tuple))
+		return HeapTupleHeaderXminInvalid(tuple) ? true : false;
 
 	/*
-	 * if HEAP_XMAX_LOCK_ONLY is not set and not a multi, then this must
-	 * necessarily have been updated
+	 * If the inserting transaction committed, but any deleting transaction
+	 * aborted, the tuple is still alive.
 	 */
-	if (!(tuple->t_infomask & HEAP_XMAX_IS_MULTI))
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)
 		return false;
 
-	/* ... but if it's a multi, then perhaps the updating Xid aborted. */
-	xmax = HeapTupleGetUpdateXid(tuple);
-
-	/* not LOCKED_ONLY, so it has to have an xmax */
-	Assert(TransactionIdIsValid(xmax));
-
-	if (TransactionIdIsCurrentTransactionId(xmax))
-		return false;
-	if (TransactionIdIsInProgress(xmax))
-		return false;
-	if (TransactionIdDidCommit(xmax))
+	/*
+	 * If the XMAX is just a lock, the tuple is still alive.
+	 */
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
 		return false;
 
 	/*
-	 * not current, not in progress, not committed -- must have aborted or
-	 * crashed
+	 * If the Xmax is a MultiXact, it might be dead or alive, but we cannot
+	 * know without checking pg_multixact.
 	 */
-	return true;
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+		return false;
+
+	/* If deleter isn't known to have committed, assume it's still running. */
+	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
+		return false;
+
+	/* Deleter committed, so tuple is dead if the XID is old enough. */
+	return TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin);
 }
 
 /*
@@ -1659,10 +1662,11 @@ TransactionIdInArray(TransactionId xid, TransactionId *xip, Size num)
  * dangerous to do so as the semantics of doing so during timetravel are more
  * complicated than when dealing "only" with the present.
  */
-bool
-HeapTupleSatisfiesHistoricMVCC(HeapTuple htup, Snapshot snapshot,
+static bool
+HeapTupleSatisfiesHistoricMVCC(StorageTuple stup, Snapshot snapshot,
 							   Buffer buffer)
 {
+	HeapTuple	htup = (HeapTuple) stup;
 	HeapTupleHeader tuple = htup->t_data;
 	TransactionId xmin = HeapTupleHeaderGetXmin(tuple);
 	TransactionId xmax = HeapTupleHeaderGetRawXmax(tuple);
@@ -1796,3 +1800,35 @@ HeapTupleSatisfiesHistoricMVCC(HeapTuple htup, Snapshot snapshot,
 	else
 		return true;
 }
+
+bool
+HeapTupleSatisfies(StorageTuple stup, Snapshot snapshot, Buffer buffer)
+{
+	switch (snapshot->visibility_type)
+	{
+		case MVCC_VISIBILITY:
+			return HeapTupleSatisfiesMVCC(stup, snapshot, buffer);
+			break;
+		case SELF_VISIBILITY:
+			return HeapTupleSatisfiesSelf(stup, snapshot, buffer);
+			break;
+		case ANY_VISIBILITY:
+			return HeapTupleSatisfiesAny(stup, snapshot, buffer);
+			break;
+		case TOAST_VISIBILITY:
+			return HeapTupleSatisfiesToast(stup, snapshot, buffer);
+			break;
+		case DIRTY_VISIBILITY:
+			return HeapTupleSatisfiesDirty(stup, snapshot, buffer);
+			break;
+		case HISTORIC_MVCC_VISIBILITY:
+			return HeapTupleSatisfiesHistoricMVCC(stup, snapshot, buffer);
+			break;
+		case NON_VACUUMABLE_VISIBILTY:
+			return HeapTupleSatisfiesNonVacuumable(stup, snapshot, buffer);
+			break;
+		default:
+			Assert(0);
+			break;
+	}
+}
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index f67d7d15df..f19814059e 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -402,7 +402,7 @@ heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum,
 			 * either here or while following a chain below.  Whichever path
 			 * gets there first will mark the tuple unused.
 			 */
-			if (HeapTupleSatisfiesVacuum(&tup, OldestXmin, buffer)
+			if (relation->rd_stamroutine->snapshot_satisfiesVacuum(&tup, OldestXmin, buffer)
 				== HEAPTUPLE_DEAD && !HeapTupleHeaderIsHotUpdated(htup))
 			{
 				heap_prune_record_unused(prstate, rootoffnum);
@@ -486,7 +486,7 @@ heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum,
 		 */
 		tupdead = recent_dead = false;
 
-		switch (HeapTupleSatisfiesVacuum(&tup, OldestXmin, buffer))
+		switch (relation->rd_stamroutine->snapshot_satisfiesVacuum(&tup, OldestXmin, buffer))
 		{
 			case HEAPTUPLE_DEAD:
 				tupdead = true;
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 214825114e..8f0a827b5d 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -472,7 +472,7 @@ systable_recheck_tuple(SysScanDesc sysscan, HeapTuple tup)
 		Assert(BufferIsValid(scan->xs_cbuf));
 		/* must hold a buffer lock to call HeapTupleSatisfiesVisibility */
 		LockBuffer(scan->xs_cbuf, BUFFER_LOCK_SHARE);
-		result = HeapTupleSatisfiesVisibility(tup, freshsnap, scan->xs_cbuf);
+		result = HeapTupleSatisfiesVisibility(sysscan->heap_rel->rd_stamroutine, tup, freshsnap, scan->xs_cbuf);
 		LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK);
 	}
 	else
@@ -484,7 +484,7 @@ systable_recheck_tuple(SysScanDesc sysscan, HeapTuple tup)
 		Assert(BufferIsValid(scan->rs_cbuf));
 		/* must hold a buffer lock to call HeapTupleSatisfiesVisibility */
 		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-		result = HeapTupleSatisfiesVisibility(tup, freshsnap, scan->rs_cbuf);
+		result = HeapTupleSatisfiesVisibility(sysscan->heap_rel->rd_stamroutine, tup, freshsnap, scan->rs_cbuf);
 		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
 	}
 	return result;
diff --git a/src/backend/access/storage/Makefile b/src/backend/access/storage/Makefile
index 2a05c7ce66..321676820f 100644
--- a/src/backend/access/storage/Makefile
+++ b/src/backend/access/storage/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/storage
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = storageam.o storageamapi.o
+OBJS = storageam.o storageamapi.o storage_common.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/storage/storage_common.c b/src/backend/access/storage/storage_common.c
new file mode 100644
index 0000000000..e83da822ea
--- /dev/null
+++ b/src/backend/access/storage/storage_common.c
@@ -0,0 +1,26 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_common.c
+ *	  storage access method code that is common across all pluggable
+ *	  storage modules
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/storage/storage_common.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/storage_common.h"
+#include "access/subtrans.h"
+#include "access/transam.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "storage/bufmgr.h"
+#include "storage/procarray.h"
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 330488b96f..16819abb68 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2225,6 +2225,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	TransactionId OldestXmin;
 	BlockNumber root_blkno = InvalidBlockNumber;
 	OffsetNumber root_offsets[MaxHeapTuplesPerPage];
+	StorageAmRoutine *method;
 
 	/*
 	 * sanity checks
@@ -2280,6 +2281,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 		OldestXmin = GetOldestXmin(heapRelation, PROCARRAY_FLAGS_VACUUM);
 	}
 
+	method = heapRelation->rd_stamroutine;
 	scan = heap_beginscan_strat(heapRelation,	/* relation */
 								snapshot,	/* snapshot */
 								0,	/* number of keys */
@@ -2360,8 +2362,8 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 			 */
 			LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 
-			switch (HeapTupleSatisfiesVacuum(heapTuple, OldestXmin,
-											 scan->rs_cbuf))
+			switch (method->snapshot_satisfiesVacuum(heapTuple, OldestXmin,
+													 scan->rs_cbuf))
 			{
 				case HEAPTUPLE_DEAD:
 					/* Definitely dead, we can ignore it */
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 5f21fcb5f4..e672529dce 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -1119,9 +1119,9 @@ acquire_sample_rows(Relation onerel, int elevel,
 			targtuple.t_data = (HeapTupleHeader) PageGetItem(targpage, itemid);
 			targtuple.t_len = ItemIdGetLength(itemid);
 
-			switch (HeapTupleSatisfiesVacuum(&targtuple,
-											 OldestXmin,
-											 targbuffer))
+			switch (onerel->rd_stamroutine->snapshot_satisfiesVacuum(&targtuple,
+																	 OldestXmin,
+																	 targbuffer))
 			{
 				case HEAPTUPLE_LIVE:
 					sample_it = true;
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index eb73299199..27362cbbbf 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -21,6 +21,7 @@
 #include "access/multixact.h"
 #include "access/relscan.h"
 #include "access/rewriteheap.h"
+#include "access/storageamapi.h"
 #include "access/transam.h"
 #include "access/tuptoaster.h"
 #include "access/xact.h"
@@ -971,7 +972,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 
 		LockBuffer(buf, BUFFER_LOCK_SHARE);
 
-		switch (HeapTupleSatisfiesVacuum(tuple, OldestXmin, buf))
+		switch (OldHeap->rd_stamroutine->snapshot_satisfiesVacuum(tuple, OldestXmin, buf))
 		{
 			case HEAPTUPLE_DEAD:
 				/* Definitely dead */
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index cf7f5e1162..2b36f680c5 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -988,7 +988,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
 
 			tupgone = false;
 
-			switch (HeapTupleSatisfiesVacuum(&tuple, OldestXmin, buf))
+			switch (onerel->rd_stamroutine->snapshot_satisfiesVacuum(&tuple, OldestXmin, buf))
 			{
 				case HEAPTUPLE_DEAD:
 
@@ -2162,7 +2162,7 @@ heap_page_is_all_visible(Relation rel, Buffer buf,
 		tuple.t_len = ItemIdGetLength(itemid);
 		tuple.t_tableOid = RelationGetRelid(rel);
 
-		switch (HeapTupleSatisfiesVacuum(&tuple, OldestXmin, buf))
+		switch (rel->rd_stamroutine->snapshot_satisfiesVacuum(&tuple, OldestXmin, buf))
 		{
 			case HEAPTUPLE_LIVE:
 				{
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index 7ba1db7d7e..5f11c94713 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -461,7 +461,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 			loctup.t_len = ItemIdGetLength(lp);
 			loctup.t_tableOid = scan->rs_rd->rd_id;
 			ItemPointerSet(&loctup.t_self, page, offnum);
-			valid = HeapTupleSatisfiesVisibility(&loctup, snapshot, buffer);
+			valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine, &loctup, snapshot, buffer);
 			if (valid)
 			{
 				scan->rs_vistuples[ntup++] = offnum;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index e52a3bb95e..b44fc7cda8 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -191,6 +191,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
  */
 static void
 ExecCheckHeapTupleVisible(EState *estate,
+						  Relation rel,
 						  HeapTuple tuple,
 						  Buffer buffer)
 {
@@ -202,7 +203,7 @@ ExecCheckHeapTupleVisible(EState *estate,
 	 * Caller should be holding pin, but not lock.
 	 */
 	LockBuffer(buffer, BUFFER_LOCK_SHARE);
-	if (!HeapTupleSatisfiesVisibility(tuple, estate->es_snapshot, buffer))
+	if (!HeapTupleSatisfiesVisibility(rel->rd_stamroutine, tuple, estate->es_snapshot, buffer))
 	{
 		/*
 		 * We should not raise a serialization failure if the conflict is
@@ -237,7 +238,7 @@ ExecCheckTIDVisible(EState *estate,
 	tuple.t_self = *tid;
 	if (!heap_fetch(rel, SnapshotAny, &tuple, &buffer, false, NULL))
 		elog(ERROR, "failed to fetch conflicting tuple for ON CONFLICT");
-	ExecCheckHeapTupleVisible(estate, &tuple, buffer);
+	ExecCheckHeapTupleVisible(estate, rel, &tuple, buffer);
 	ReleaseBuffer(buffer);
 }
 
@@ -1313,7 +1314,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	 * snapshot.  This is in line with the way UPDATE deals with newer tuple
 	 * versions.
 	 */
-	ExecCheckHeapTupleVisible(estate, &tuple, buffer);
+	ExecCheckHeapTupleVisible(estate, relation, &tuple, buffer);
 
 	/* Store target's existing tuple in the state's dedicated slot */
 	ExecStoreTuple(&tuple, mtstate->mt_existing, buffer, false);
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index e88cd18737..e105f6758e 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -588,7 +588,8 @@ SampleTupleVisible(HeapTuple tuple, OffsetNumber tupoffset, HeapScanDesc scan)
 	else
 	{
 		/* Otherwise, we have to check the tuple individually. */
-		return HeapTupleSatisfiesVisibility(tuple,
+		return HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine,
+											tuple,
 											scan->rs_snapshot,
 											scan->rs_cbuf);
 	}
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index 5b35f22a32..9c5329812d 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -376,7 +376,7 @@ static void
 SnapBuildFreeSnapshot(Snapshot snap)
 {
 	/* make sure we don't get passed an external snapshot */
-	Assert(snap->satisfies == HeapTupleSatisfiesHistoricMVCC);
+	Assert(snap->visibility_type == HISTORIC_MVCC_VISIBILITY);
 
 	/* make sure nobody modified our snapshot */
 	Assert(snap->curcid == FirstCommandId);
@@ -434,7 +434,7 @@ void
 SnapBuildSnapDecRefcount(Snapshot snap)
 {
 	/* make sure we don't get passed an external snapshot */
-	Assert(snap->satisfies == HeapTupleSatisfiesHistoricMVCC);
+	Assert(snap->visibility_type == HISTORIC_MVCC_VISIBILITY);
 
 	/* make sure nobody modified our snapshot */
 	Assert(snap->curcid == FirstCommandId);
@@ -476,7 +476,7 @@ SnapBuildBuildSnapshot(SnapBuild *builder)
 
 	snapshot = MemoryContextAllocZero(builder->context, ssize);
 
-	snapshot->satisfies = HeapTupleSatisfiesHistoricMVCC;
+	snapshot->visibility_type = HISTORIC_MVCC_VISIBILITY;
 
 	/*
 	 * We misuse the original meaning of SnapshotData's xip and subxip fields
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index d1ff2b1edc..f2f6e31b9d 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -3972,7 +3972,7 @@ CheckForSerializableConflictOut(bool visible, Relation relation,
 	 * tuple is visible to us, while HeapTupleSatisfiesVacuum checks what else
 	 * is going on with it.
 	 */
-	htsvResult = HeapTupleSatisfiesVacuum(tuple, TransactionXmin, buffer);
+	htsvResult = relation->rd_stamroutine->snapshot_satisfiesVacuum(tuple, TransactionXmin, buffer);
 	switch (htsvResult)
 	{
 		case HEAPTUPLE_LIVE:
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 8faae1d069..8da65d1d2e 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -287,7 +287,7 @@ RI_FKey_check(TriggerData *trigdata)
 	 * should be holding pin, but not lock.
 	 */
 	LockBuffer(new_row_buf, BUFFER_LOCK_SHARE);
-	if (!HeapTupleSatisfiesVisibility(new_row, SnapshotSelf, new_row_buf))
+	if (!HeapTupleSatisfiesVisibility(trigdata->tg_relation->rd_stamroutine, new_row, SnapshotSelf, new_row_buf))
 	{
 		LockBuffer(new_row_buf, BUFFER_LOCK_UNLOCK);
 		return PointerGetDatum(NULL);
diff --git a/src/backend/utils/time/Makefile b/src/backend/utils/time/Makefile
index 5a6e6fa4c8..f17b1c5324 100644
--- a/src/backend/utils/time/Makefile
+++ b/src/backend/utils/time/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/utils/time
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = combocid.o tqual.o snapmgr.o
+OBJS = combocid.o snapmgr.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c
index e58c69dbd7..dd486fc6a3 100644
--- a/src/backend/utils/time/snapmgr.c
+++ b/src/backend/utils/time/snapmgr.c
@@ -141,9 +141,9 @@ static volatile OldSnapshotControlData *oldSnapshotControl;
  * These SnapshotData structs are static to simplify memory allocation
  * (see the hack in GetSnapshotData to avoid repeated malloc/free).
  */
-static SnapshotData CurrentSnapshotData = {HeapTupleSatisfiesMVCC};
-static SnapshotData SecondarySnapshotData = {HeapTupleSatisfiesMVCC};
-SnapshotData CatalogSnapshotData = {HeapTupleSatisfiesMVCC};
+static SnapshotData CurrentSnapshotData = {MVCC_VISIBILITY};
+static SnapshotData SecondarySnapshotData = {MVCC_VISIBILITY};
+SnapshotData CatalogSnapshotData = {MVCC_VISIBILITY};
 
 /* Pointers to valid snapshots */
 static Snapshot CurrentSnapshot = NULL;
@@ -2046,7 +2046,7 @@ EstimateSnapshotSpace(Snapshot snap)
 	Size		size;
 
 	Assert(snap != InvalidSnapshot);
-	Assert(snap->satisfies == HeapTupleSatisfiesMVCC);
+	Assert(snap->visibility_type == MVCC_VISIBILITY);
 
 	/* We allocate any XID arrays needed in the same palloc block. */
 	size = add_size(sizeof(SerializedSnapshotData),
@@ -2143,7 +2143,7 @@ RestoreSnapshot(char *start_address)
 
 	/* Copy all required fields */
 	snapshot = (Snapshot) MemoryContextAlloc(TopTransactionContext, size);
-	snapshot->satisfies = HeapTupleSatisfiesMVCC;
+	snapshot->visibility_type = MVCC_VISIBILITY;
 	snapshot->xmin = serialized_snapshot.xmin;
 	snapshot->xmax = serialized_snapshot.xmax;
 	snapshot->xip = NULL;
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 4c0256b18a..5bbe55f18b 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -16,6 +16,7 @@
 
 #include "access/sdir.h"
 #include "access/skey.h"
+#include "access/storage_common.h"
 #include "nodes/lockoptions.h"
 #include "nodes/primnodes.h"
 #include "storage/bufpage.h"
@@ -200,4 +201,16 @@ extern BlockNumber ss_get_location(Relation rel, BlockNumber relnblocks);
 extern void SyncScanShmemInit(void);
 extern Size SyncScanShmemSize(void);
 
+/* in heap/heapam_visibility.c */
+extern bool HeapTupleSatisfies(StorageTuple stup, Snapshot snapshot, Buffer buffer);
+extern HTSU_Result HeapTupleSatisfiesUpdate(StorageTuple stup, CommandId curcid,
+						 Buffer buffer);
+extern HTSV_Result HeapTupleSatisfiesVacuum(StorageTuple stup, TransactionId OldestXmin,
+						 Buffer buffer);
+extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
+					 uint16 infomask, TransactionId xid);
+extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
+extern bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
+extern bool HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin);
+
 #endif							/* HEAPAM_H */
diff --git a/src/include/access/storage_common.h b/src/include/access/storage_common.h
new file mode 100644
index 0000000000..ca140c1745
--- /dev/null
+++ b/src/include/access/storage_common.h
@@ -0,0 +1,41 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_common.h
+ *	  POSTGRES storage access method definitions shared across
+ *	  all pluggable storage methods and server.
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/storage_common.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_COMMON_H
+#define STORAGE_COMMON_H
+
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/transam.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "storage/bufpage.h"
+#include "storage/bufmgr.h"
+
+
+/* A physical tuple coming from a storage AM scan */
+typedef void *StorageTuple;
+
+/* Result codes for HeapTupleSatisfiesVacuum */
+typedef enum
+{
+	HEAPTUPLE_DEAD,				/* tuple is dead and deletable */
+	HEAPTUPLE_LIVE,				/* tuple is live (committed, no deleter) */
+	HEAPTUPLE_RECENTLY_DEAD,	/* tuple is dead, but not deletable yet */
+	HEAPTUPLE_INSERT_IN_PROGRESS,	/* inserting xact is still in progress */
+	HEAPTUPLE_DELETE_IN_PROGRESS	/* deleting xact is still in progress */
+} HTSV_Result;
+
+#endif							/* STORAGE_COMMON_H */
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
index 6fae4eea5c..56a791d0c6 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -11,11 +11,19 @@
 #ifndef STORAGEAMAPI_H
 #define STORAGEAMAPI_H
 
+#include "access/storage_common.h"
 #include "nodes/nodes.h"
+#include "utils/snapshot.h"
 #include "fmgr.h"
 
-/* A physical tuple coming from a storage AM scan */
-typedef void *StorageTuple;
+
+/*
+ * Storage routine function hooks
+ */
+typedef bool (*SnapshotSatisfies_function) (StorageTuple htup, Snapshot snapshot, Buffer buffer);
+typedef HTSU_Result (*SnapshotSatisfiesUpdate_function) (StorageTuple htup, CommandId curcid, Buffer buffer);
+typedef HTSV_Result (*SnapshotSatisfiesVacuum_function) (StorageTuple htup, TransactionId OldestXmin, Buffer buffer);
+
 
 /*
  * API struct for a storage AM.  Note this must be stored in a single palloc'd
@@ -30,6 +38,10 @@ typedef struct StorageAmRoutine
 {
 	NodeTag		type;
 
+	SnapshotSatisfies_function snapshot_satisfies;
+	SnapshotSatisfiesUpdate_function snapshot_satisfiesUpdate;	/* HeapTupleSatisfiesUpdate */
+	SnapshotSatisfiesVacuum_function snapshot_satisfiesVacuum;	/* HeapTupleSatisfiesVacuum */
+
 }			StorageAmRoutine;
 
 extern StorageAmRoutine * GetStorageAmRoutine(Oid amhandler);
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 3cce3906a0..95915bdc92 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -20,7 +20,6 @@
 #include "storage/relfilenode.h"
 #include "utils/relcache.h"
 #include "utils/snapmgr.h"
-#include "utils/tqual.h"
 
 typedef void *Block;
 
@@ -268,8 +267,8 @@ TestForOldSnapshot(Snapshot snapshot, Relation relation, Page page)
 
 	if (old_snapshot_threshold >= 0
 		&& (snapshot) != NULL
-		&& ((snapshot)->satisfies == HeapTupleSatisfiesMVCC
-			|| (snapshot)->satisfies == HeapTupleSatisfiesToast)
+		&& ((snapshot)->visibility_type == MVCC_VISIBILITY
+			|| (snapshot)->visibility_type == TOAST_VISIBILITY)
 		&& !XLogRecPtrIsInvalid((snapshot)->lsn)
 		&& PageGetLSN(page) > (snapshot)->lsn)
 		TestForOldSnapshot_impl(snapshot, relation);
diff --git a/src/include/utils/snapshot.h b/src/include/utils/snapshot.h
index a8a5a8f4c0..ca96fd00fa 100644
--- a/src/include/utils/snapshot.h
+++ b/src/include/utils/snapshot.h
@@ -19,6 +19,18 @@
 #include "lib/pairingheap.h"
 #include "storage/buf.h"
 
+typedef enum tuple_visibility_type
+{
+	MVCC_VISIBILITY = 0,		/* HeapTupleSatisfiesMVCC */
+	SELF_VISIBILITY,			/* HeapTupleSatisfiesSelf */
+	ANY_VISIBILITY,				/* HeapTupleSatisfiesAny */
+	TOAST_VISIBILITY,			/* HeapTupleSatisfiesToast */
+	DIRTY_VISIBILITY,			/* HeapTupleSatisfiesDirty */
+	HISTORIC_MVCC_VISIBILITY,	/* HeapTupleSatisfiesHistoricMVCC */
+	NON_VACUUMABLE_VISIBILTY,	/* HeapTupleSatisfiesNonVacuumable */
+
+	END_OF_VISIBILITY
+}			tuple_visibility_type;
 
 typedef struct SnapshotData *Snapshot;
 
@@ -52,7 +64,7 @@ typedef bool (*SnapshotSatisfiesFunc) (HeapTuple htup,
  */
 typedef struct SnapshotData
 {
-	SnapshotSatisfiesFunc satisfies;	/* tuple test function */
+	tuple_visibility_type visibility_type;	/* tuple visibility test type */
 
 	/*
 	 * The remaining fields are used only for MVCC snapshots, and are normally
diff --git a/src/include/utils/tqual.h b/src/include/utils/tqual.h
index d3b6e99bb4..d62124b016 100644
--- a/src/include/utils/tqual.h
+++ b/src/include/utils/tqual.h
@@ -16,6 +16,7 @@
 #define TQUAL_H
 
 #include "utils/snapshot.h"
+#include "access/storageamapi.h"
 #include "access/xlogdefs.h"
 
 
@@ -29,8 +30,8 @@ extern PGDLLIMPORT SnapshotData CatalogSnapshotData;
 
 /* This macro encodes the knowledge of which snapshots are MVCC-safe */
 #define IsMVCCSnapshot(snapshot)  \
-	((snapshot)->satisfies == HeapTupleSatisfiesMVCC || \
-	 (snapshot)->satisfies == HeapTupleSatisfiesHistoricMVCC)
+	((snapshot)->visibility_type == MVCC_VISIBILITY || \
+	 (snapshot)->visibility_type == HISTORIC_MVCC_VISIBILITY)
 
 /*
  * HeapTupleSatisfiesVisibility
@@ -42,47 +43,8 @@ extern PGDLLIMPORT SnapshotData CatalogSnapshotData;
  *	Hint bits in the HeapTuple's t_infomask may be updated as a side effect;
  *	if so, the indicated buffer is marked dirty.
  */
-#define HeapTupleSatisfiesVisibility(tuple, snapshot, buffer) \
-	((*(snapshot)->satisfies) (tuple, snapshot, buffer))
-
-/* Result codes for HeapTupleSatisfiesVacuum */
-typedef enum
-{
-	HEAPTUPLE_DEAD,				/* tuple is dead and deletable */
-	HEAPTUPLE_LIVE,				/* tuple is live (committed, no deleter) */
-	HEAPTUPLE_RECENTLY_DEAD,	/* tuple is dead, but not deletable yet */
-	HEAPTUPLE_INSERT_IN_PROGRESS,	/* inserting xact is still in progress */
-	HEAPTUPLE_DELETE_IN_PROGRESS	/* deleting xact is still in progress */
-} HTSV_Result;
-
-/* These are the "satisfies" test routines for the various snapshot types */
-extern bool HeapTupleSatisfiesMVCC(HeapTuple htup,
-					   Snapshot snapshot, Buffer buffer);
-extern bool HeapTupleSatisfiesSelf(HeapTuple htup,
-					   Snapshot snapshot, Buffer buffer);
-extern bool HeapTupleSatisfiesAny(HeapTuple htup,
-					  Snapshot snapshot, Buffer buffer);
-extern bool HeapTupleSatisfiesToast(HeapTuple htup,
-						Snapshot snapshot, Buffer buffer);
-extern bool HeapTupleSatisfiesDirty(HeapTuple htup,
-						Snapshot snapshot, Buffer buffer);
-extern bool HeapTupleSatisfiesNonVacuumable(HeapTuple htup,
-								Snapshot snapshot, Buffer buffer);
-extern bool HeapTupleSatisfiesHistoricMVCC(HeapTuple htup,
-							   Snapshot snapshot, Buffer buffer);
-
-/* Special "satisfies" routines with different APIs */
-extern HTSU_Result HeapTupleSatisfiesUpdate(HeapTuple htup,
-						 CommandId curcid, Buffer buffer);
-extern HTSV_Result HeapTupleSatisfiesVacuum(HeapTuple htup,
-						 TransactionId OldestXmin, Buffer buffer);
-extern bool HeapTupleIsSurelyDead(HeapTuple htup,
-					  TransactionId OldestXmin);
-extern bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
-
-extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
-					 uint16 infomask, TransactionId xid);
-extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
+#define HeapTupleSatisfiesVisibility(method, tuple, snapshot, buffer) \
+	(((method)->snapshot_satisfies) (tuple, snapshot, buffer))
 
 /*
  * To avoid leaking too much knowledge about reorderbuffer implementation
@@ -101,14 +63,14 @@ extern bool ResolveCminCmaxDuringDecoding(struct HTAB *tuplecid_data,
  * local variable of type SnapshotData, and initialize it with this macro.
  */
 #define InitDirtySnapshot(snapshotdata)  \
-	((snapshotdata).satisfies = HeapTupleSatisfiesDirty)
+	((snapshotdata).visibility_type = DIRTY_VISIBILITY)
 
 /*
  * Similarly, some initialization is required for a NonVacuumable snapshot.
  * The caller must supply the xmin horizon to use (e.g., RecentGlobalXmin).
  */
 #define InitNonVacuumableSnapshot(snapshotdata, xmin_horizon)  \
-	((snapshotdata).satisfies = HeapTupleSatisfiesNonVacuumable, \
+	((snapshotdata).visibility_type = NON_VACUUMABLE_VISIBILTY, \
 	 (snapshotdata).xmin = (xmin_horizon))
 
 /*
@@ -116,7 +78,7 @@ extern bool ResolveCminCmaxDuringDecoding(struct HTAB *tuplecid_data,
  * to set lsn and whenTaken correctly to support snapshot_too_old.
  */
 #define InitToastSnapshot(snapshotdata, l, w)  \
-	((snapshotdata).satisfies = HeapTupleSatisfiesToast, \
+	((snapshotdata).visibility_type = TOAST_VISIBILITY, \
 	 (snapshotdata).lsn = (l),					\
 	 (snapshotdata).whenTaken = (w))
 
-- 
2.15.0.windows.1



  [application/octet-stream] 0005-slot-hooks-are-added-to-storage-AM.patch (62.0K, ../../CAJrrPGeoihBWs+BUP06q9=rCNm75m56J-EEStf3ygikNcpreYw@mail.gmail.com/8-0005-slot-hooks-are-added-to-storage-AM.patch)
  download | inline diff:
From bec00da92fd32c42e99f5166b71ff7d93ac3bd5a Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Wed, 3 Jan 2018 17:13:58 +1100
Subject: [PATCH 05/12] slot hooks are added to storage AM

The tuple is removed as part of the slot and added
an void pointer to store the tuple data that can
understand only by the storage AM routine.

The slot utility functions are reorganized to use
two storageAM routines to satify the current
functionality.

Currently the slot supports minimum tuple also.
---
 src/backend/access/common/heaptuple.c       | 302 +--------------------
 src/backend/access/heap/heapam_storage.c    |   2 +
 src/backend/access/storage/storage_common.c | 407 ++++++++++++++++++++++++++++
 src/backend/commands/copy.c                 |   2 +-
 src/backend/commands/createas.c             |   2 +-
 src/backend/commands/matview.c              |   2 +-
 src/backend/commands/trigger.c              |  15 +-
 src/backend/executor/execExprInterp.c       |  35 ++-
 src/backend/executor/execReplication.c      |  90 ++----
 src/backend/executor/execTuples.c           | 268 ++++++++----------
 src/backend/executor/nodeForeignscan.c      |   2 +-
 src/backend/executor/nodeModifyTable.c      |  24 +-
 src/backend/executor/tqueue.c               |   2 +-
 src/backend/replication/logical/worker.c    |   5 +-
 src/include/access/htup_details.h           |  15 +-
 src/include/access/storage_common.h         |  38 ++-
 src/include/access/storageamapi.h           |   2 +
 src/include/executor/tuptable.h             |  54 ++--
 18 files changed, 691 insertions(+), 576 deletions(-)

diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 0a13251067..a50a76dc76 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -57,6 +57,7 @@
 
 #include "postgres.h"
 
+#include "access/storageamapi.h"
 #include "access/sysattr.h"
 #include "access/tuptoaster.h"
 #include "executor/tuptable.h"
@@ -1021,111 +1022,6 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
 	}
 }
 
-/*
- * slot_deform_tuple
- *		Given a TupleTableSlot, extract data from the slot's physical tuple
- *		into its Datum/isnull arrays.  Data is extracted up through the
- *		natts'th column (caller must ensure this is a legal column number).
- *
- *		This is essentially an incremental version of heap_deform_tuple:
- *		on each call we extract attributes up to the one needed, without
- *		re-computing information about previously extracted attributes.
- *		slot->tts_nvalid is the number of attributes already extracted.
- */
-static void
-slot_deform_tuple(TupleTableSlot *slot, int natts)
-{
-	HeapTuple	tuple = slot->tts_tuple;
-	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
-	Datum	   *values = slot->tts_values;
-	bool	   *isnull = slot->tts_isnull;
-	HeapTupleHeader tup = tuple->t_data;
-	bool		hasnulls = HeapTupleHasNulls(tuple);
-	int			attnum;
-	char	   *tp;				/* ptr to tuple data */
-	long		off;			/* offset in tuple data */
-	bits8	   *bp = tup->t_bits;	/* ptr to null bitmap in tuple */
-	bool		slow;			/* can we use/set attcacheoff? */
-
-	/*
-	 * Check whether the first call for this tuple, and initialize or restore
-	 * loop state.
-	 */
-	attnum = slot->tts_nvalid;
-	if (attnum == 0)
-	{
-		/* Start from the first attribute */
-		off = 0;
-		slow = false;
-	}
-	else
-	{
-		/* Restore state from previous execution */
-		off = slot->tts_off;
-		slow = slot->tts_slow;
-	}
-
-	tp = (char *) tup + tup->t_hoff;
-
-	for (; attnum < natts; attnum++)
-	{
-		Form_pg_attribute thisatt = TupleDescAttr(tupleDesc, attnum);
-
-		if (hasnulls && att_isnull(attnum, bp))
-		{
-			values[attnum] = (Datum) 0;
-			isnull[attnum] = true;
-			slow = true;		/* can't use attcacheoff anymore */
-			continue;
-		}
-
-		isnull[attnum] = false;
-
-		if (!slow && thisatt->attcacheoff >= 0)
-			off = thisatt->attcacheoff;
-		else if (thisatt->attlen == -1)
-		{
-			/*
-			 * We can only cache the offset for a varlena attribute if the
-			 * offset is already suitably aligned, so that there would be no
-			 * pad bytes in any case: then the offset will be valid for either
-			 * an aligned or unaligned value.
-			 */
-			if (!slow &&
-				off == att_align_nominal(off, thisatt->attalign))
-				thisatt->attcacheoff = off;
-			else
-			{
-				off = att_align_pointer(off, thisatt->attalign, -1,
-										tp + off);
-				slow = true;
-			}
-		}
-		else
-		{
-			/* not varlena, so safe to use att_align_nominal */
-			off = att_align_nominal(off, thisatt->attalign);
-
-			if (!slow)
-				thisatt->attcacheoff = off;
-		}
-
-		values[attnum] = fetchatt(thisatt, tp + off);
-
-		off = att_addlength_pointer(off, thisatt->attlen, tp + off);
-
-		if (thisatt->attlen <= 0)
-			slow = true;		/* can't use attcacheoff anymore */
-	}
-
-	/*
-	 * Save state for next execution
-	 */
-	slot->tts_nvalid = attnum;
-	slot->tts_off = off;
-	slot->tts_slow = slow;
-}
-
 /*
  * slot_getattr
  *		This function fetches an attribute of the slot's current tuple.
@@ -1141,91 +1037,7 @@ slot_deform_tuple(TupleTableSlot *slot, int natts)
 Datum
 slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
 {
-	HeapTuple	tuple = slot->tts_tuple;
-	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
-	HeapTupleHeader tup;
-
-	/*
-	 * system attributes are handled by heap_getsysattr
-	 */
-	if (attnum <= 0)
-	{
-		if (tuple == NULL)		/* internal error */
-			elog(ERROR, "cannot extract system attribute from virtual tuple");
-		if (tuple == &(slot->tts_minhdr))	/* internal error */
-			elog(ERROR, "cannot extract system attribute from minimal tuple");
-		return heap_getsysattr(tuple, attnum, tupleDesc, isnull);
-	}
-
-	/*
-	 * fast path if desired attribute already cached
-	 */
-	if (attnum <= slot->tts_nvalid)
-	{
-		*isnull = slot->tts_isnull[attnum - 1];
-		return slot->tts_values[attnum - 1];
-	}
-
-	/*
-	 * return NULL if attnum is out of range according to the tupdesc
-	 */
-	if (attnum > tupleDesc->natts)
-	{
-		*isnull = true;
-		return (Datum) 0;
-	}
-
-	/*
-	 * otherwise we had better have a physical tuple (tts_nvalid should equal
-	 * natts in all virtual-tuple cases)
-	 */
-	if (tuple == NULL)			/* internal error */
-		elog(ERROR, "cannot extract attribute from empty tuple slot");
-
-	/*
-	 * return NULL if attnum is out of range according to the tuple
-	 *
-	 * (We have to check this separately because of various inheritance and
-	 * table-alteration scenarios: the tuple could be either longer or shorter
-	 * than the tupdesc.)
-	 */
-	tup = tuple->t_data;
-	if (attnum > HeapTupleHeaderGetNatts(tup))
-	{
-		*isnull = true;
-		return (Datum) 0;
-	}
-
-	/*
-	 * check if target attribute is null: no point in groveling through tuple
-	 */
-	if (HeapTupleHasNulls(tuple) && att_isnull(attnum - 1, tup->t_bits))
-	{
-		*isnull = true;
-		return (Datum) 0;
-	}
-
-	/*
-	 * If the attribute's column has been dropped, we force a NULL result.
-	 * This case should not happen in normal use, but it could happen if we
-	 * are executing a plan cached before the column was dropped.
-	 */
-	if (TupleDescAttr(tupleDesc, attnum - 1)->attisdropped)
-	{
-		*isnull = true;
-		return (Datum) 0;
-	}
-
-	/*
-	 * Extract the attribute, along with any preceding attributes.
-	 */
-	slot_deform_tuple(slot, attnum);
-
-	/*
-	 * The result is acquired from tts_values array.
-	 */
-	*isnull = slot->tts_isnull[attnum - 1];
-	return slot->tts_values[attnum - 1];
+	return slot->tts_storageslotam->slot_getattr(slot, attnum, isnull);
 }
 
 /*
@@ -1237,40 +1049,7 @@ slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
 void
 slot_getallattrs(TupleTableSlot *slot)
 {
-	int			tdesc_natts = slot->tts_tupleDescriptor->natts;
-	int			attnum;
-	HeapTuple	tuple;
-
-	/* Quick out if we have 'em all already */
-	if (slot->tts_nvalid == tdesc_natts)
-		return;
-
-	/*
-	 * otherwise we had better have a physical tuple (tts_nvalid should equal
-	 * natts in all virtual-tuple cases)
-	 */
-	tuple = slot->tts_tuple;
-	if (tuple == NULL)			/* internal error */
-		elog(ERROR, "cannot extract attribute from empty tuple slot");
-
-	/*
-	 * load up any slots available from physical tuple
-	 */
-	attnum = HeapTupleHeaderGetNatts(tuple->t_data);
-	attnum = Min(attnum, tdesc_natts);
-
-	slot_deform_tuple(slot, attnum);
-
-	/*
-	 * If tuple doesn't have all the atts indicated by tupleDesc, read the
-	 * rest as null
-	 */
-	for (; attnum < tdesc_natts; attnum++)
-	{
-		slot->tts_values[attnum] = (Datum) 0;
-		slot->tts_isnull[attnum] = true;
-	}
-	slot->tts_nvalid = tdesc_natts;
+	slot->tts_storageslotam->slot_virtualize_tuple(slot, slot->tts_tupleDescriptor->natts);
 }
 
 /*
@@ -1281,43 +1060,7 @@ slot_getallattrs(TupleTableSlot *slot)
 void
 slot_getsomeattrs(TupleTableSlot *slot, int attnum)
 {
-	HeapTuple	tuple;
-	int			attno;
-
-	/* Quick out if we have 'em all already */
-	if (slot->tts_nvalid >= attnum)
-		return;
-
-	/* Check for caller error */
-	if (attnum <= 0 || attnum > slot->tts_tupleDescriptor->natts)
-		elog(ERROR, "invalid attribute number %d", attnum);
-
-	/*
-	 * otherwise we had better have a physical tuple (tts_nvalid should equal
-	 * natts in all virtual-tuple cases)
-	 */
-	tuple = slot->tts_tuple;
-	if (tuple == NULL)			/* internal error */
-		elog(ERROR, "cannot extract attribute from empty tuple slot");
-
-	/*
-	 * load up any slots available from physical tuple
-	 */
-	attno = HeapTupleHeaderGetNatts(tuple->t_data);
-	attno = Min(attno, attnum);
-
-	slot_deform_tuple(slot, attno);
-
-	/*
-	 * If tuple doesn't have all the atts indicated by tupleDesc, read the
-	 * rest as null
-	 */
-	for (; attno < attnum; attno++)
-	{
-		slot->tts_values[attno] = (Datum) 0;
-		slot->tts_isnull[attno] = true;
-	}
-	slot->tts_nvalid = attnum;
+	slot->tts_storageslotam->slot_virtualize_tuple(slot, attnum);
 }
 
 /*
@@ -1328,42 +1071,11 @@ slot_getsomeattrs(TupleTableSlot *slot, int attnum)
 bool
 slot_attisnull(TupleTableSlot *slot, int attnum)
 {
-	HeapTuple	tuple = slot->tts_tuple;
-	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
-
-	/*
-	 * system attributes are handled by heap_attisnull
-	 */
-	if (attnum <= 0)
-	{
-		if (tuple == NULL)		/* internal error */
-			elog(ERROR, "cannot extract system attribute from virtual tuple");
-		if (tuple == &(slot->tts_minhdr))	/* internal error */
-			elog(ERROR, "cannot extract system attribute from minimal tuple");
-		return heap_attisnull(tuple, attnum);
-	}
-
-	/*
-	 * fast path if desired attribute already cached
-	 */
-	if (attnum <= slot->tts_nvalid)
-		return slot->tts_isnull[attnum - 1];
-
-	/*
-	 * return NULL if attnum is out of range according to the tupdesc
-	 */
-	if (attnum > tupleDesc->natts)
-		return true;
+	bool		isnull;
 
-	/*
-	 * otherwise we had better have a physical tuple (tts_nvalid should equal
-	 * natts in all virtual-tuple cases)
-	 */
-	if (tuple == NULL)			/* internal error */
-		elog(ERROR, "cannot extract attribute from empty tuple slot");
+	slot->tts_storageslotam->slot_getattr(slot, attnum, &isnull);
 
-	/* and let the tuple tell it */
-	return heap_attisnull(tuple, attnum);
+	return isnull;
 }
 
 /*
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index a340c46a80..a953a690b3 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -35,5 +35,7 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 	amroutine->snapshot_satisfiesUpdate = HeapTupleSatisfiesUpdate;
 	amroutine->snapshot_satisfiesVacuum = HeapTupleSatisfiesVacuum;
 
+	amroutine->slot_storageam = heapam_storage_slot_handler;
+
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/storage/storage_common.c b/src/backend/access/storage/storage_common.c
index e83da822ea..43d03cb8dc 100644
--- a/src/backend/access/storage/storage_common.c
+++ b/src/backend/access/storage/storage_common.c
@@ -24,3 +24,410 @@
 #include "access/xlog.h"
 #include "storage/bufmgr.h"
 #include "storage/procarray.h"
+
+/*-----------------------
+ *
+ * Slot storage handler API
+ * ----------------------
+ */
+
+static HeapTuple
+heapam_get_tuple(TupleTableSlot *slot, bool palloc_copy)
+{
+	HeapTuple	tup;
+	HeapamTuple *stuple = (HeapamTuple *) slot->tts_storage;
+
+	if (stuple)
+	{
+		if (stuple->hst_mintuple)
+		{
+			tup = heap_tuple_from_minimal_tuple(stuple->hst_mintuple);
+		}
+		else
+		{
+			if (!palloc_copy)
+				tup = stuple->hst_heaptuple;
+			else
+				tup = heap_copytuple(stuple->hst_heaptuple);
+		}
+	}
+	else
+	{
+		tup = heap_form_tuple(slot->tts_tupleDescriptor,
+							  slot->tts_values,
+							  slot->tts_isnull);
+	}
+
+	return tup;
+}
+
+static MinimalTuple
+heapam_get_min_tuple(TupleTableSlot *slot, bool palloc_copy)
+{
+	MinimalTuple tup;
+	HeapamTuple *stuple = (HeapamTuple *) slot->tts_storage;
+
+	if (stuple)
+	{
+		if (stuple->hst_mintuple)
+		{
+			if (!palloc_copy)
+				tup = stuple->hst_mintuple;
+			else
+				tup = heap_copy_minimal_tuple(stuple->hst_mintuple);
+		}
+		else
+		{
+			tup = minimal_tuple_from_heap_tuple(stuple->hst_heaptuple);
+		}
+	}
+	else
+	{
+		tup = heap_form_minimal_tuple(slot->tts_tupleDescriptor,
+									  slot->tts_values,
+									  slot->tts_isnull);
+	}
+
+	return tup;
+}
+
+
+/*
+ * slot_deform_tuple
+ *		Given a TupleTableSlot, extract data from the slot's physical tuple
+ *		into its Datum/isnull arrays.  Data is extracted up through the
+ *		natts'th column (caller must ensure this is a legal column number).
+ *
+ *		This is essentially an incremental version of heap_deform_tuple:
+ *		on each call we extract attributes up to the one needed, without
+ *		re-computing information about previously extracted attributes.
+ *		slot->tts_nvalid is the number of attributes already extracted.
+ */
+static void
+slot_deform_tuple(TupleTableSlot *slot, int natts)
+{
+	HeapamTuple *stuple = (HeapamTuple *) slot->tts_storage;
+	HeapTuple	tuple = stuple ? stuple->hst_heaptuple : NULL;
+	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
+	Datum	   *values = slot->tts_values;
+	bool	   *isnull = slot->tts_isnull;
+	HeapTupleHeader tup = tuple->t_data;
+	bool		hasnulls = HeapTupleHasNulls(tuple);
+	int			attnum;
+	char	   *tp;				/* ptr to tuple data */
+	long		off;			/* offset in tuple data */
+	bits8	   *bp = tup->t_bits;	/* ptr to null bitmap in tuple */
+	bool		slow;			/* can we use/set attcacheoff? */
+
+	/*
+	 * Check whether the first call for this tuple, and initialize or restore
+	 * loop state.
+	 */
+	attnum = slot->tts_nvalid;
+	if (attnum == 0)
+	{
+		/* Start from the first attribute */
+		off = 0;
+		slow = false;
+	}
+	else
+	{
+		/* Restore state from previous execution */
+		off = stuple->hst_off;
+		slow = stuple->hst_slow;
+	}
+
+	tp = (char *) tup + tup->t_hoff;
+
+	for (; attnum < natts; attnum++)
+	{
+		Form_pg_attribute thisatt = TupleDescAttr(tupleDesc, attnum);
+
+		if (hasnulls && att_isnull(attnum, bp))
+		{
+			values[attnum] = (Datum) 0;
+			isnull[attnum] = true;
+			slow = true;		/* can't use attcacheoff anymore */
+			continue;
+		}
+
+		isnull[attnum] = false;
+
+		if (!slow && thisatt->attcacheoff >= 0)
+			off = thisatt->attcacheoff;
+		else if (thisatt->attlen == -1)
+		{
+			/*
+			 * We can only cache the offset for a varlena attribute if the
+			 * offset is already suitably aligned, so that there would be no
+			 * pad bytes in any case: then the offset will be valid for either
+			 * an aligned or unaligned value.
+			 */
+			if (!slow &&
+				off == att_align_nominal(off, thisatt->attalign))
+				thisatt->attcacheoff = off;
+			else
+			{
+				off = att_align_pointer(off, thisatt->attalign, -1,
+										tp + off);
+				slow = true;
+			}
+		}
+		else
+		{
+			/* not varlena, so safe to use att_align_nominal */
+			off = att_align_nominal(off, thisatt->attalign);
+
+			if (!slow)
+				thisatt->attcacheoff = off;
+		}
+
+		values[attnum] = fetchatt(thisatt, tp + off);
+
+		off = att_addlength_pointer(off, thisatt->attlen, tp + off);
+
+		if (thisatt->attlen <= 0)
+			slow = true;		/* can't use attcacheoff anymore */
+	}
+
+	/*
+	 * Save state for next execution
+	 */
+	slot->tts_nvalid = attnum;
+	stuple->hst_off = off;
+	stuple->hst_slow = slow;
+}
+
+static void
+heapam_slot_virtualize_tuple(TupleTableSlot *slot, int16 upto)
+{
+	HeapamTuple *stuple;
+	HeapTuple	tuple;
+	int			attno;
+
+	/* Quick out if we have 'em all already */
+	if (slot->tts_nvalid >= upto)
+		return;
+
+	/* Check for caller error */
+	if (upto <= 0 || upto > slot->tts_tupleDescriptor->natts)
+		elog(ERROR, "invalid attribute number %d", upto);
+
+	/*
+	 * otherwise we had better have a physical tuple (tts_nvalid should equal
+	 * natts in all virtual-tuple cases)
+	 */
+	stuple = slot->tts_storage; /* XXX SlotGetTupleStorage(slot) ??? */
+	tuple = stuple->hst_heaptuple;
+	if (tuple == NULL)			/* internal error */
+		elog(ERROR, "cannot extract attribute from empty tuple slot");
+
+	/*
+	 * load up any slots available from physical tuple
+	 */
+	attno = HeapTupleHeaderGetNatts(tuple->t_data);
+	attno = Min(attno, upto);
+
+	slot_deform_tuple(slot, attno);
+
+	/*
+	 * If tuple doesn't have all the atts indicated by tupleDesc, read the
+	 * rest as null
+	 */
+	for (; attno < upto; attno++)
+	{
+		slot->tts_values[attno] = (Datum) 0;
+		slot->tts_isnull[attno] = true;
+	}
+	slot->tts_nvalid = upto;
+}
+
+static void
+heapam_slot_update_tuple_tableoid(TupleTableSlot *slot, Oid tableoid)
+{
+	HeapTuple	tuple;
+
+	tuple = heapam_get_tuple(slot, false);
+	tuple->t_tableOid = tableoid;
+}
+
+static void
+heapam_slot_store_tuple(TupleTableSlot *slot, StorageTuple tuple, bool shouldFree, bool minimum_tuple)
+{
+	HeapamTuple *stuple;
+	MemoryContext oldcontext;
+
+	oldcontext = MemoryContextSwitchTo(slot->tts_mcxt);
+
+	stuple = (HeapamTuple *) palloc0(sizeof(HeapamTuple));
+
+	if (!minimum_tuple)
+	{
+		stuple->hst_heaptuple = tuple;
+		stuple->hst_slow = false;
+		stuple->hst_off = 0;
+		stuple->hst_mintuple = NULL;
+		slot->tts_shouldFreeMin = false;
+		slot->tts_shouldFree = shouldFree;
+	}
+	else
+	{
+		stuple->hst_mintuple = tuple;
+		stuple->hst_minhdr.t_len = ((MinimalTuple) tuple)->t_len + MINIMAL_TUPLE_OFFSET;
+		stuple->hst_minhdr.t_data = (HeapTupleHeader) ((char *) tuple - MINIMAL_TUPLE_OFFSET);
+		stuple->hst_heaptuple = &stuple->hst_minhdr;
+		slot->tts_shouldFreeMin = shouldFree;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	slot->tts_tid = ((HeapTuple) tuple)->t_self;
+	if (slot->tts_tupleDescriptor->tdhasoid)
+		slot->tts_tupleOid = HeapTupleGetOid((HeapTuple) tuple);
+	slot->tts_storage = stuple;
+}
+
+static void
+heapam_slot_clear_tuple(TupleTableSlot *slot)
+{
+	HeapamTuple *stuple;
+
+	/* XXX should this be an Assert() instead? */
+	if (slot->tts_isempty)
+		return;
+
+	stuple = slot->tts_storage;
+	if (stuple == NULL)
+		return;
+
+	if (slot->tts_shouldFree)
+		heap_freetuple(stuple->hst_heaptuple);
+
+	if (slot->tts_shouldFreeMin)
+		heap_free_minimal_tuple(stuple->hst_mintuple);
+
+	slot->tts_shouldFree = false;
+	slot->tts_shouldFreeMin = false;
+
+	pfree(stuple);
+	slot->tts_storage = NULL;
+}
+
+/*
+ * slot_getattr
+ *		This function fetches an attribute of the slot's current tuple.
+ *		It is functionally equivalent to heap_getattr, but fetches of
+ *		multiple attributes of the same tuple will be optimized better,
+ *		because we avoid O(N^2) behavior from multiple calls of
+ *		nocachegetattr(), even when attcacheoff isn't usable.
+ *
+ *		A difference from raw heap_getattr is that attnums beyond the
+ *		slot's tupdesc's last attribute will be considered NULL even
+ *		when the physical tuple is longer than the tupdesc.
+ */
+static Datum
+heapam_slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
+{
+	HeapamTuple *stuple = slot->tts_storage;
+	HeapTuple	tuple = stuple ? stuple->hst_heaptuple : NULL;
+	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
+	HeapTupleHeader tup;
+
+	/*
+	 * system attributes are handled by heap_getsysattr
+	 */
+	if (attnum <= 0)
+	{
+		if (tuple == NULL)		/* internal error */
+			elog(ERROR, "cannot extract system attribute from virtual tuple");
+		if (tuple == &(stuple->hst_minhdr)) /* internal error */
+			elog(ERROR, "cannot extract system attribute from minimal tuple");
+		return heap_getsysattr(tuple, attnum, tupleDesc, isnull);
+	}
+
+	/*
+	 * fast path if desired attribute already cached
+	 */
+	if (attnum <= slot->tts_nvalid)
+	{
+		*isnull = slot->tts_isnull[attnum - 1];
+		return slot->tts_values[attnum - 1];
+	}
+
+	/*
+	 * return NULL if attnum is out of range according to the tupdesc
+	 */
+	if (attnum > tupleDesc->natts)
+	{
+		*isnull = true;
+		return (Datum) 0;
+	}
+
+	/*
+	 * otherwise we had better have a physical tuple (tts_nvalid should equal
+	 * natts in all virtual-tuple cases)
+	 */
+	if (tuple == NULL)			/* internal error */
+		elog(ERROR, "cannot extract attribute from empty tuple slot");
+
+	/*
+	 * return NULL if attnum is out of range according to the tuple
+	 *
+	 * (We have to check this separately because of various inheritance and
+	 * table-alteration scenarios: the tuple could be either longer or shorter
+	 * than the tupdesc.)
+	 */
+	tup = tuple->t_data;
+	if (attnum > HeapTupleHeaderGetNatts(tup))
+	{
+		*isnull = true;
+		return (Datum) 0;
+	}
+
+	/*
+	 * check if target attribute is null: no point in groveling through tuple
+	 */
+	if (HeapTupleHasNulls(tuple) && att_isnull(attnum - 1, tup->t_bits))
+	{
+		*isnull = true;
+		return (Datum) 0;
+	}
+
+	/*
+	 * If the attribute's column has been dropped, we force a NULL result.
+	 * This case should not happen in normal use, but it could happen if we
+	 * are executing a plan cached before the column was dropped.
+	 */
+	if (TupleDescAttr(tupleDesc, (attnum - 1))->attisdropped)
+	{
+		*isnull = true;
+		return (Datum) 0;
+	}
+
+	/*
+	 * Extract the attribute, along with any preceding attributes.
+	 */
+	slot_deform_tuple(slot, attnum);
+
+	/*
+	 * The result is acquired from tts_values array.
+	 */
+	*isnull = slot->tts_isnull[attnum - 1];
+	return slot->tts_values[attnum - 1];
+}
+
+StorageSlotAmRoutine *
+heapam_storage_slot_handler(void)
+{
+	StorageSlotAmRoutine *amroutine = palloc(sizeof(StorageSlotAmRoutine));
+
+	amroutine->slot_store_tuple = heapam_slot_store_tuple;
+	amroutine->slot_virtualize_tuple = heapam_slot_virtualize_tuple;
+	amroutine->slot_clear_tuple = heapam_slot_clear_tuple;
+	amroutine->slot_tuple = heapam_get_tuple;
+	amroutine->slot_min_tuple = heapam_get_min_tuple;
+	amroutine->slot_getattr = heapam_slot_getattr;
+	amroutine->slot_update_tableoid = heapam_slot_update_tuple_tableoid;
+
+	return amroutine;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 118115aa42..f44566591a 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2720,7 +2720,7 @@ CopyFrom(CopyState cstate)
 			if (slot == NULL)	/* "do nothing" */
 				skip_tuple = true;
 			else				/* trigger might have changed tuple */
-				tuple = ExecMaterializeSlot(slot);
+				tuple = ExecHeapifySlot(slot);
 		}
 
 		if (!skip_tuple)
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 3d82edbf58..ff2b7b75e9 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -588,7 +588,7 @@ intorel_receive(TupleTableSlot *slot, DestReceiver *self)
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
 	 */
-	tuple = ExecMaterializeSlot(slot);
+	tuple = ExecHeapifySlot(slot);
 
 	/*
 	 * force assignment of new OID (see comments in ExecInsert)
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index ab6a889b12..467695160a 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -497,7 +497,7 @@ transientrel_receive(TupleTableSlot *slot, DestReceiver *self)
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
 	 */
-	tuple = ExecMaterializeSlot(slot);
+	tuple = ExecHeapifySlot(slot);
 
 	heap_insert(myState->transientrel,
 				tuple,
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 1c488c338a..502f1dee1f 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2289,7 +2289,7 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 					 TupleTableSlot *slot)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
-	HeapTuple	slottuple = ExecMaterializeSlot(slot);
+	HeapTuple	slottuple = ExecHeapifySlot(slot);
 	HeapTuple	newtuple = slottuple;
 	HeapTuple	oldtuple;
 	TriggerData LocTriggerData;
@@ -2370,7 +2370,7 @@ ExecIRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 					 TupleTableSlot *slot)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
-	HeapTuple	slottuple = ExecMaterializeSlot(slot);
+	HeapTuple	slottuple = ExecHeapifySlot(slot);
 	HeapTuple	newtuple = slottuple;
 	HeapTuple	oldtuple;
 	TriggerData LocTriggerData;
@@ -2728,7 +2728,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 					 TupleTableSlot *slot)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
-	HeapTuple	slottuple = ExecMaterializeSlot(slot);
+	HeapTuple	slottuple = ExecHeapifySlot(slot);
 	HeapTuple	newtuple = slottuple;
 	TriggerData LocTriggerData;
 	HeapTuple	trigtuple;
@@ -2770,7 +2770,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 	if (newSlot != NULL)
 	{
 		slot = ExecFilterJunk(relinfo->ri_junkFilter, newSlot);
-		slottuple = ExecMaterializeSlot(slot);
+		slottuple = ExecHeapifySlot(slot);
 		newtuple = slottuple;
 	}
 
@@ -2879,7 +2879,7 @@ ExecIRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 					 HeapTuple trigtuple, TupleTableSlot *slot)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
-	HeapTuple	slottuple = ExecMaterializeSlot(slot);
+	HeapTuple	slottuple = ExecHeapifySlot(slot);
 	HeapTuple	newtuple = slottuple;
 	TriggerData LocTriggerData;
 	HeapTuple	oldtuple;
@@ -4006,14 +4006,13 @@ AfterTriggerExecute(AfterTriggerEvent event,
 			 * because we start with a minimal tuple that ExecFetchSlotTuple()
 			 * must materialize anyway.
 			 */
-			LocTriggerData.tg_trigtuple =
-				ExecMaterializeSlot(trig_tuple_slot1);
+			LocTriggerData.tg_trigtuple = ExecHeapifySlot(trig_tuple_slot1);
 			LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
 
 			LocTriggerData.tg_newtuple =
 				((evtshared->ats_event & TRIGGER_EVENT_OPMASK) ==
 				 TRIGGER_EVENT_UPDATE) ?
-				ExecMaterializeSlot(trig_tuple_slot2) : NULL;
+				ExecHeapifySlot(trig_tuple_slot2) : NULL;
 			LocTriggerData.tg_newtuplebuf = InvalidBuffer;
 
 			break;
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 2e88417265..f73e5a79fc 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -480,13 +480,15 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			Datum		d;
 
 			/* these asserts must match defenses in slot_getattr */
-			Assert(innerslot->tts_tuple != NULL);
-			Assert(innerslot->tts_tuple != &(innerslot->tts_minhdr));
+			Assert(innerslot->tts_storage != NULL);
+
+			/*
+			 * hari
+			 * Assert(innerslot->tts_storageslotam->slot_is_physical_tuple(innerslot));
+			 */
 
 			/* heap_getsysattr has sufficient defenses against bad attnums */
-			d = heap_getsysattr(innerslot->tts_tuple, attnum,
-								innerslot->tts_tupleDescriptor,
-								op->resnull);
+			d = slot_getattr(innerslot, attnum, op->resnull);
 			*op->resvalue = d;
 
 			EEO_NEXT();
@@ -498,13 +500,14 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			Datum		d;
 
 			/* these asserts must match defenses in slot_getattr */
-			Assert(outerslot->tts_tuple != NULL);
-			Assert(outerslot->tts_tuple != &(outerslot->tts_minhdr));
+			Assert(outerslot->tts_storage != NULL);
 
+			/*
+			 * hari
+			 * Assert(outerslot->tts_storageslotam->slot_is_physical_tuple(outerslot));
+			 */
 			/* heap_getsysattr has sufficient defenses against bad attnums */
-			d = heap_getsysattr(outerslot->tts_tuple, attnum,
-								outerslot->tts_tupleDescriptor,
-								op->resnull);
+			d = slot_getattr(outerslot, attnum, op->resnull);
 			*op->resvalue = d;
 
 			EEO_NEXT();
@@ -516,13 +519,15 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			Datum		d;
 
 			/* these asserts must match defenses in slot_getattr */
-			Assert(scanslot->tts_tuple != NULL);
-			Assert(scanslot->tts_tuple != &(scanslot->tts_minhdr));
+			Assert(scanslot->tts_storage != NULL);
+
+			/*
+			 * hari
+			 * Assert(scanslot->tts_storageslotam->slot_is_physical_tuple(scanslot));
+			 */
 
 			/* heap_getsysattr has sufficient defenses against bad attnums */
-			d = heap_getsysattr(scanslot->tts_tuple, attnum,
-								scanslot->tts_tupleDescriptor,
-								op->resnull);
+			d = slot_getattr(scanslot, attnum, op->resnull);
 			*op->resvalue = d;
 
 			EEO_NEXT();
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 732ed42fe5..864ca769e9 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -171,7 +171,7 @@ retry:
 		HTSU_Result res;
 		HeapTupleData locktup;
 
-		ItemPointerCopy(&outslot->tts_tuple->t_self, &locktup.t_self);
+		ItemPointerCopy(&outslot->tts_tid, &locktup.t_self);
 
 		PushActiveSnapshot(GetLatestSnapshot());
 
@@ -211,59 +211,6 @@ retry:
 	return found;
 }
 
-/*
- * Compare the tuple and slot and check if they have equal values.
- *
- * We use binary datum comparison which might return false negatives but
- * that's the best we can do here as there may be multiple notions of
- * equality for the data types and table columns don't specify which one
- * to use.
- */
-static bool
-tuple_equals_slot(TupleDesc desc, HeapTuple tup, TupleTableSlot *slot)
-{
-	Datum		values[MaxTupleAttributeNumber];
-	bool		isnull[MaxTupleAttributeNumber];
-	int			attrnum;
-
-	heap_deform_tuple(tup, desc, values, isnull);
-
-	/* Check equality of the attributes. */
-	for (attrnum = 0; attrnum < desc->natts; attrnum++)
-	{
-		Form_pg_attribute att;
-		TypeCacheEntry *typentry;
-
-		/*
-		 * If one value is NULL and other is not, then they are certainly not
-		 * equal
-		 */
-		if (isnull[attrnum] != slot->tts_isnull[attrnum])
-			return false;
-
-		/*
-		 * If both are NULL, they can be considered equal.
-		 */
-		if (isnull[attrnum])
-			continue;
-
-		att = TupleDescAttr(desc, attrnum);
-
-		typentry = lookup_type_cache(att->atttypid, TYPECACHE_EQ_OPR_FINFO);
-		if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
-			ereport(ERROR,
-					(errcode(ERRCODE_UNDEFINED_FUNCTION),
-					 errmsg("could not identify an equality operator for type %s",
-							format_type_be(att->atttypid))));
-
-		if (!DatumGetBool(FunctionCall2(&typentry->eq_opr_finfo,
-										values[attrnum],
-										slot->tts_values[attrnum])))
-			return false;
-	}
-
-	return true;
-}
 
 /*
  * Search the relation 'rel' for tuple using the sequential scan.
@@ -279,6 +226,7 @@ bool
 RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 						 TupleTableSlot *searchslot, TupleTableSlot *outslot)
 {
+	TupleTableSlot *scanslot;
 	HeapTuple	scantuple;
 	HeapScanDesc scan;
 	SnapshotData snap;
@@ -292,6 +240,8 @@ RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 	InitDirtySnapshot(snap);
 	scan = heap_beginscan(rel, &snap, 0, NULL);
 
+	scanslot = MakeSingleTupleTableSlot(desc);
+
 retry:
 	found = false;
 
@@ -300,12 +250,12 @@ retry:
 	/* Try to find the tuple */
 	while ((scantuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
 	{
-		if (!tuple_equals_slot(desc, scantuple, searchslot))
+		ExecStoreTuple(scantuple, scanslot, InvalidBuffer, false);
+		if (!ExecSlotCompare(scanslot, searchslot))
 			continue;
 
 		found = true;
-		ExecStoreTuple(scantuple, outslot, InvalidBuffer, false);
-		ExecMaterializeSlot(outslot);
+		ExecCopySlot(outslot, scanslot);
 
 		xwait = TransactionIdIsValid(snap.xmin) ?
 			snap.xmin : snap.xmax;
@@ -329,7 +279,7 @@ retry:
 		HTSU_Result res;
 		HeapTupleData locktup;
 
-		ItemPointerCopy(&outslot->tts_tuple->t_self, &locktup.t_self);
+		ItemPointerCopy(&outslot->tts_tid, &locktup.t_self);
 
 		PushActiveSnapshot(GetLatestSnapshot());
 
@@ -362,6 +312,7 @@ retry:
 	}
 
 	heap_endscan(scan);
+	ExecDropSingleTupleTableSlot(scanslot);
 
 	return found;
 }
@@ -404,7 +355,7 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		/* Store the slot into tuple that we can inspect. */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		/* OK, store the tuple and create index entries for it */
 		simple_heap_insert(rel, tuple);
@@ -442,6 +393,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
+	ItemPointer tid = &(searchslot->tts_tid);
 
 	/* For now we support only tables. */
 	Assert(rel->rd_rel->relkind == RELKIND_RELATION);
@@ -453,7 +405,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 		resultRelInfo->ri_TrigDesc->trig_update_before_row)
 	{
 		slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
-									&searchslot->tts_tuple->t_self,
+									tid,
 									NULL, slot);
 
 		if (slot == NULL)		/* "do nothing" */
@@ -469,21 +421,20 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		/* Store the slot into tuple that we can write. */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		/* OK, update the tuple and index entries for it */
-		simple_heap_update(rel, &searchslot->tts_tuple->t_self,
-						   slot->tts_tuple);
+		simple_heap_update(rel, tid, tuple);
 
 		if (resultRelInfo->ri_NumIndices > 0 &&
-			!HeapTupleIsHeapOnly(slot->tts_tuple))
-			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+			!HeapTupleIsHeapOnly(tuple))
+			recheckIndexes = ExecInsertIndexTuples(slot, tid,
 												   estate, false, NULL,
 												   NIL);
 
 		/* AFTER ROW UPDATE Triggers */
 		ExecARUpdateTriggers(estate, resultRelInfo,
-							 &searchslot->tts_tuple->t_self,
+							 tid,
 							 NULL, tuple, recheckIndexes, NULL);
 
 		list_free(recheckIndexes);
@@ -503,6 +454,7 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate,
 	bool		skip_tuple = false;
 	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
+	ItemPointer tid = &(searchslot->tts_tid);
 
 	/* For now we support only tables. */
 	Assert(rel->rd_rel->relkind == RELKIND_RELATION);
@@ -514,7 +466,7 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate,
 		resultRelInfo->ri_TrigDesc->trig_delete_before_row)
 	{
 		skip_tuple = !ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
-										   &searchslot->tts_tuple->t_self,
+										   tid,
 										   NULL);
 	}
 
@@ -523,11 +475,11 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate,
 		List	   *recheckIndexes = NIL;
 
 		/* OK, delete the tuple */
-		simple_heap_delete(rel, &searchslot->tts_tuple->t_self);
+		simple_heap_delete(rel, tid);
 
 		/* AFTER ROW DELETE Triggers */
 		ExecARDeleteTriggers(estate, resultRelInfo,
-							 &searchslot->tts_tuple->t_self, NULL, NULL);
+							 tid, NULL, NULL);
 
 		list_free(recheckIndexes);
 	}
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 5df89e419c..ba53eb85cf 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -82,6 +82,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/storage_common.h"
 #include "access/tuptoaster.h"
 #include "funcapi.h"
 #include "catalog/pg_type.h"
@@ -113,16 +114,15 @@ MakeTupleTableSlot(void)
 	TupleTableSlot *slot = makeNode(TupleTableSlot);
 
 	slot->tts_isempty = true;
-	slot->tts_shouldFree = false;
-	slot->tts_shouldFreeMin = false;
-	slot->tts_tuple = NULL;
 	slot->tts_tupleDescriptor = NULL;
 	slot->tts_mcxt = CurrentMemoryContext;
-	slot->tts_buffer = InvalidBuffer;
 	slot->tts_nvalid = 0;
 	slot->tts_values = NULL;
 	slot->tts_isnull = NULL;
-	slot->tts_mintuple = NULL;
+	slot->tts_tupleOid = InvalidOid;
+	slot->tts_tableOid = InvalidOid;
+	slot->tts_storageslotam = heapam_storage_slot_handler();
+	slot->tts_storage = NULL;
 
 	return slot;
 }
@@ -205,6 +205,54 @@ MakeSingleTupleTableSlot(TupleDesc tupdesc)
 	return slot;
 }
 
+/* --------------------------------
+ *		ExecSlotCompare
+ *
+ *		This is a slot comparision function to find out
+ *		whether both the slots are same or not?
+ * --------------------------------
+ */
+bool
+ExecSlotCompare(TupleTableSlot *slot1, TupleTableSlot *slot2)
+{
+	int			attrnum;
+
+	Assert(slot1->tts_tupleDescriptor->natts == slot2->tts_tupleDescriptor->natts);
+
+	slot_getallattrs(slot1);
+	slot_getallattrs(slot2);
+
+	/* Check equality of the attributes. */
+	for (attrnum = 0; attrnum < slot1->tts_tupleDescriptor->natts; attrnum++)
+	{
+		Form_pg_attribute att;
+		TypeCacheEntry *typentry;
+
+		/*
+		 * If one value is NULL and other is not, then they are certainly not
+		 * equal
+		 */
+		if (slot1->tts_isnull[attrnum] != slot2->tts_isnull[attrnum])
+			return false;
+
+		att = TupleDescAttr(slot1->tts_tupleDescriptor, attrnum);
+
+		typentry = lookup_type_cache(att->atttypid, TYPECACHE_EQ_OPR_FINFO);
+		if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_FUNCTION),
+					 errmsg("could not identify an equality operator for type %s",
+							format_type_be(att->atttypid))));
+
+		if (!DatumGetBool(FunctionCall2(&typentry->eq_opr_finfo,
+										slot1->tts_values[attrnum],
+										slot2->tts_values[attrnum])))
+			return false;
+	}
+
+	return true;
+}
+
 /* --------------------------------
  *		ExecDropSingleTupleTableSlot
  *
@@ -317,7 +365,7 @@ ExecSetSlotDescriptor(TupleTableSlot *slot, /* slot to change */
  * --------------------------------
  */
 TupleTableSlot *
-ExecStoreTuple(HeapTuple tuple,
+ExecStoreTuple(void *tuple,
 			   TupleTableSlot *slot,
 			   Buffer buffer,
 			   bool shouldFree)
@@ -328,47 +376,27 @@ ExecStoreTuple(HeapTuple tuple,
 	Assert(tuple != NULL);
 	Assert(slot != NULL);
 	Assert(slot->tts_tupleDescriptor != NULL);
+	Assert(slot->tts_storageslotam != NULL);
 	/* passing shouldFree=true for a tuple on a disk page is not sane */
 	Assert(BufferIsValid(buffer) ? (!shouldFree) : true);
 
 	/*
 	 * Free any old physical tuple belonging to the slot.
 	 */
-	if (slot->tts_shouldFree)
-		heap_freetuple(slot->tts_tuple);
-	if (slot->tts_shouldFreeMin)
-		heap_free_minimal_tuple(slot->tts_mintuple);
+	slot->tts_storageslotam->slot_clear_tuple(slot);	/* XXX ?? */
 
 	/*
-	 * Store the new tuple into the specified slot.
+	 * Store the new tuple into the specified slot, and mark the slot as no
+	 * longer empty.  This clears any previously stored physical tuple.
 	 */
+	/* XXX should we pass the buffer down to the storageAM perhaps? */
+	slot->tts_storageslotam->slot_store_tuple(slot, tuple, shouldFree, false);
+
 	slot->tts_isempty = false;
-	slot->tts_shouldFree = shouldFree;
-	slot->tts_shouldFreeMin = false;
-	slot->tts_tuple = tuple;
-	slot->tts_mintuple = NULL;
 
 	/* Mark extracted state invalid */
 	slot->tts_nvalid = 0;
 
-	/*
-	 * If tuple is on a disk page, keep the page pinned as long as we hold a
-	 * pointer into it.  We assume the caller already has such a pin.
-	 *
-	 * This is coded to optimize the case where the slot previously held a
-	 * tuple on the same disk page: in that case releasing and re-acquiring
-	 * the pin is a waste of cycles.  This is a common situation during
-	 * seqscans, so it's worth troubling over.
-	 */
-	if (slot->tts_buffer != buffer)
-	{
-		if (BufferIsValid(slot->tts_buffer))
-			ReleaseBuffer(slot->tts_buffer);
-		slot->tts_buffer = buffer;
-		if (BufferIsValid(buffer))
-			IncrBufferRefCount(buffer);
-	}
-
 	return slot;
 }
 
@@ -395,31 +423,19 @@ ExecStoreMinimalTuple(MinimalTuple mtup,
 	/*
 	 * Free any old physical tuple belonging to the slot.
 	 */
-	if (slot->tts_shouldFree)
-		heap_freetuple(slot->tts_tuple);
-	if (slot->tts_shouldFreeMin)
-		heap_free_minimal_tuple(slot->tts_mintuple);
-
-	/*
-	 * Drop the pin on the referenced buffer, if there is one.
-	 */
-	if (BufferIsValid(slot->tts_buffer))
-		ReleaseBuffer(slot->tts_buffer);
-
-	slot->tts_buffer = InvalidBuffer;
+	slot->tts_storageslotam->slot_clear_tuple(slot);	/* XXX ?? */
 
 	/*
 	 * Store the new tuple into the specified slot.
 	 */
 	slot->tts_isempty = false;
-	slot->tts_shouldFree = false;
-	slot->tts_shouldFreeMin = shouldFree;
-	slot->tts_tuple = &slot->tts_minhdr;
-	slot->tts_mintuple = mtup;
 
-	slot->tts_minhdr.t_len = mtup->t_len + MINIMAL_TUPLE_OFFSET;
-	slot->tts_minhdr.t_data = (HeapTupleHeader) ((char *) mtup - MINIMAL_TUPLE_OFFSET);
-	/* no need to set t_self or t_tableOid since we won't allow access */
+	/*
+	 * Store the new tuple into the specified slot, and mark the slot as no
+	 * longer empty.  This clears any previously stored physical tuple.
+	 */
+	/* XXX should we pass the buffer down to the storageAM perhaps? */
+	slot->tts_storageslotam->slot_store_tuple(slot, mtup, false, true);
 
 	/* Mark extracted state invalid */
 	slot->tts_nvalid = 0;
@@ -444,25 +460,9 @@ ExecClearTuple(TupleTableSlot *slot)	/* slot in which to store tuple */
 	Assert(slot != NULL);
 
 	/*
-	 * Free the old physical tuple if necessary.
+	 * Tell the storage AM to release any resource associated with the slot.
 	 */
-	if (slot->tts_shouldFree)
-		heap_freetuple(slot->tts_tuple);
-	if (slot->tts_shouldFreeMin)
-		heap_free_minimal_tuple(slot->tts_mintuple);
-
-	slot->tts_tuple = NULL;
-	slot->tts_mintuple = NULL;
-	slot->tts_shouldFree = false;
-	slot->tts_shouldFreeMin = false;
-
-	/*
-	 * Drop the pin on the referenced buffer, if there is one.
-	 */
-	if (BufferIsValid(slot->tts_buffer))
-		ReleaseBuffer(slot->tts_buffer);
-
-	slot->tts_buffer = InvalidBuffer;
+	slot->tts_storageslotam->slot_clear_tuple(slot);
 
 	/*
 	 * Mark it empty.
@@ -541,7 +541,7 @@ ExecStoreAllNullTuple(TupleTableSlot *slot)
  *		however the "system columns" of the result will not be meaningful.
  * --------------------------------
  */
-HeapTuple
+StorageTuple
 ExecCopySlotTuple(TupleTableSlot *slot)
 {
 	/*
@@ -550,20 +550,7 @@ ExecCopySlotTuple(TupleTableSlot *slot)
 	Assert(slot != NULL);
 	Assert(!slot->tts_isempty);
 
-	/*
-	 * If we have a physical tuple (either format) then just copy it.
-	 */
-	if (TTS_HAS_PHYSICAL_TUPLE(slot))
-		return heap_copytuple(slot->tts_tuple);
-	if (slot->tts_mintuple)
-		return heap_tuple_from_minimal_tuple(slot->tts_mintuple);
-
-	/*
-	 * Otherwise we need to build a tuple from the Datum array.
-	 */
-	return heap_form_tuple(slot->tts_tupleDescriptor,
-						   slot->tts_values,
-						   slot->tts_isnull);
+	return slot->tts_storageslotam->slot_tuple(slot, true);
 }
 
 /* --------------------------------
@@ -582,21 +569,19 @@ ExecCopySlotMinimalTuple(TupleTableSlot *slot)
 	Assert(slot != NULL);
 	Assert(!slot->tts_isempty);
 
-	/*
-	 * If we have a physical tuple then just copy it.  Prefer to copy
-	 * tts_mintuple since that's a tad cheaper.
-	 */
-	if (slot->tts_mintuple)
-		return heap_copy_minimal_tuple(slot->tts_mintuple);
-	if (slot->tts_tuple)
-		return minimal_tuple_from_heap_tuple(slot->tts_tuple);
+	return slot->tts_storageslotam->slot_min_tuple(slot, true);
+}
 
+void
+ExecSlotUpdateTupleTableoid(TupleTableSlot *slot, Oid tableoid)
+{
 	/*
-	 * Otherwise we need to build a tuple from the Datum array.
+	 * sanity checks
 	 */
-	return heap_form_minimal_tuple(slot->tts_tupleDescriptor,
-								   slot->tts_values,
-								   slot->tts_isnull);
+	Assert(slot != NULL);
+	Assert(!slot->tts_isempty);
+
+	slot->tts_storageslotam->slot_update_tableoid(slot, tableoid);
 }
 
 /* --------------------------------
@@ -614,25 +599,34 @@ ExecCopySlotMinimalTuple(TupleTableSlot *slot)
  * Hence, the result must be treated as read-only.
  * --------------------------------
  */
-HeapTuple
+StorageTuple
 ExecFetchSlotTuple(TupleTableSlot *slot)
 {
+	MemoryContext oldContext;
+	StorageTuple tup;
+
 	/*
 	 * sanity checks
 	 */
 	Assert(slot != NULL);
 	Assert(!slot->tts_isempty);
 
-	/*
-	 * If we have a regular physical tuple then just return it.
-	 */
-	if (TTS_HAS_PHYSICAL_TUPLE(slot))
-		return slot->tts_tuple;
+	if (slot->tts_shouldFree)
+		return slot->tts_storageslotam->slot_tuple(slot, false);
 
 	/*
-	 * Otherwise materialize the slot...
+	 * Otherwise, copy or build a tuple, and store it into the slot.
+	 *
+	 * We may be called in a context that is shorter-lived than the tuple
+	 * slot, but we have to ensure that the materialized tuple will survive
+	 * anyway.
 	 */
-	return ExecMaterializeSlot(slot);
+	oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
+	tup = ExecCopySlotTuple(slot);
+	ExecStoreTuple(tup, slot, InvalidBuffer, true);
+	MemoryContextSwitchTo(oldContext);
+
+	return tup;
 }
 
 /* --------------------------------
@@ -652,6 +646,7 @@ MinimalTuple
 ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 {
 	MemoryContext oldContext;
+	MinimalTuple tup;
 
 	/*
 	 * sanity checks
@@ -659,11 +654,8 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 	Assert(slot != NULL);
 	Assert(!slot->tts_isempty);
 
-	/*
-	 * If we have a minimal physical tuple (local or not) then just return it.
-	 */
-	if (slot->tts_mintuple)
-		return slot->tts_mintuple;
+	if (slot->tts_shouldFreeMin)
+		return slot->tts_storageslotam->slot_min_tuple(slot, false);
 
 	/*
 	 * Otherwise, copy or build a minimal tuple, and store it into the slot.
@@ -673,18 +665,11 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 	 * anyway.
 	 */
 	oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
-	slot->tts_mintuple = ExecCopySlotMinimalTuple(slot);
-	slot->tts_shouldFreeMin = true;
+	tup = ExecCopySlotMinimalTuple(slot);
+	ExecStoreMinimalTuple(tup, slot, true);
 	MemoryContextSwitchTo(oldContext);
 
-	/*
-	 * Note: we may now have a situation where we have a local minimal tuple
-	 * attached to a virtual or non-local physical tuple.  There seems no harm
-	 * in that at the moment, but if any materializes, we should change this
-	 * function to force the slot into minimal-tuple-only state.
-	 */
-
-	return slot->tts_mintuple;
+	return tup;
 }
 
 /* --------------------------------
@@ -713,18 +698,19 @@ ExecFetchSlotTupleDatum(TupleTableSlot *slot)
  *			Force a slot into the "materialized" state.
  *
  *		This causes the slot's tuple to be a local copy not dependent on
- *		any external storage.  A pointer to the contained tuple is returned.
+ *		any external storage.
  *
  *		A typical use for this operation is to prepare a computed tuple
  *		for being stored on disk.  The original data may or may not be
  *		virtual, but in any case we need a private copy for heap_insert
- *		to scribble on.
+ *		to scribble on.  XXX is this comment good?
  * --------------------------------
  */
-HeapTuple
+void
 ExecMaterializeSlot(TupleTableSlot *slot)
 {
 	MemoryContext oldContext;
+	HeapTuple	tup;
 
 	/*
 	 * sanity checks
@@ -732,12 +718,8 @@ ExecMaterializeSlot(TupleTableSlot *slot)
 	Assert(slot != NULL);
 	Assert(!slot->tts_isempty);
 
-	/*
-	 * If we have a regular physical tuple, and it's locally palloc'd, we have
-	 * nothing to do.
-	 */
-	if (slot->tts_tuple && slot->tts_shouldFree)
-		return slot->tts_tuple;
+	if (slot->tts_shouldFree)
+		return;
 
 	/*
 	 * Otherwise, copy or build a physical tuple, and store it into the slot.
@@ -747,18 +729,10 @@ ExecMaterializeSlot(TupleTableSlot *slot)
 	 * anyway.
 	 */
 	oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
-	slot->tts_tuple = ExecCopySlotTuple(slot);
-	slot->tts_shouldFree = true;
+	tup = ExecCopySlotTuple(slot);
+	ExecStoreTuple(tup, slot, InvalidBuffer, true);
 	MemoryContextSwitchTo(oldContext);
 
-	/*
-	 * Drop the pin on the referenced buffer, if there is one.
-	 */
-	if (BufferIsValid(slot->tts_buffer))
-		ReleaseBuffer(slot->tts_buffer);
-
-	slot->tts_buffer = InvalidBuffer;
-
 	/*
 	 * Mark extracted state invalid.  This is important because the slot is
 	 * not supposed to depend any more on the previous external data; we
@@ -768,17 +742,15 @@ ExecMaterializeSlot(TupleTableSlot *slot)
 	 * that we have not pfree'd tts_mintuple, if there is one.)
 	 */
 	slot->tts_nvalid = 0;
+}
 
-	/*
-	 * On the same principle of not depending on previous remote storage,
-	 * forget the mintuple if it's not local storage.  (If it is local
-	 * storage, we must not pfree it now, since callers might have already
-	 * fetched datum pointers referencing it.)
-	 */
-	if (!slot->tts_shouldFreeMin)
-		slot->tts_mintuple = NULL;
+StorageTuple
+ExecHeapifySlot(TupleTableSlot *slot)
+{
+	ExecMaterializeSlot(slot);
+	Assert(slot->tts_storage != NULL);
 
-	return slot->tts_tuple;
+	return slot->tts_storageslotam->slot_tuple(slot, false);
 }
 
 /* --------------------------------
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 59865f5cca..b9cf3037b1 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -62,7 +62,7 @@ ForeignNext(ForeignScanState *node)
 	 */
 	if (plan->fsSystemCol && !TupIsNull(slot))
 	{
-		HeapTuple	tup = ExecMaterializeSlot(slot);
+		HeapTuple	tup = ExecHeapifySlot(slot);
 
 		tup->t_tableOid = RelationGetRelid(node->ss.ss_currentRelation);
 	}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index b44fc7cda8..9b62ed0a3a 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -172,7 +172,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
 		 * initialize t_tableOid before evaluating them.
 		 */
 		Assert(!TupIsNull(econtext->ecxt_scantuple));
-		tuple = ExecMaterializeSlot(econtext->ecxt_scantuple);
+		tuple = ExecHeapifySlot(econtext->ecxt_scantuple);
 		tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
 	}
 	econtext->ecxt_outertuple = planSlot;
@@ -272,7 +272,7 @@ ExecInsert(ModifyTableState *mtstate,
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
 	 */
-	tuple = ExecMaterializeSlot(slot);
+	tuple = ExecHeapifySlot(slot);
 
 	/*
 	 * get information on the (current) result relation
@@ -407,7 +407,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW INSERT Triggers */
@@ -420,7 +420,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		newId = InvalidOid;
 	}
@@ -438,7 +438,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* FDW might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		/*
 		 * AFTER ROW Triggers or RETURNING expressions might reference the
@@ -747,7 +747,7 @@ ExecDelete(ModifyTableState *mtstate,
 		 */
 		if (slot->tts_isempty)
 			ExecStoreAllNullTuple(slot);
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
 	}
 	else
@@ -898,7 +898,7 @@ ldelete:;
 		 * Before releasing the target tuple again, make sure rslot has a
 		 * local copy of any pass-by-reference values.
 		 */
-		ExecMaterializeSlot(rslot);
+		ExecHeapifySlot(rslot);
 
 		ExecClearTuple(slot);
 		if (BufferIsValid(delbuffer))
@@ -959,7 +959,7 @@ ExecUpdate(ModifyTableState *mtstate,
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
 	 */
-	tuple = ExecMaterializeSlot(slot);
+	tuple = ExecHeapifySlot(slot);
 
 	/*
 	 * get information on the (current) result relation
@@ -978,7 +978,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW UPDATE Triggers */
@@ -992,7 +992,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
@@ -1008,7 +1008,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* FDW might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		/*
 		 * AFTER ROW Triggers or RETURNING expressions might reference the
@@ -1124,7 +1124,7 @@ lreplace:;
 					{
 						*tupleid = hufd.ctid;
 						slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
-						tuple = ExecMaterializeSlot(slot);
+						tuple = ExecHeapifySlot(slot);
 						goto lreplace;
 					}
 				}
diff --git a/src/backend/executor/tqueue.c b/src/backend/executor/tqueue.c
index ecdbe7f79f..12b9fef894 100644
--- a/src/backend/executor/tqueue.c
+++ b/src/backend/executor/tqueue.c
@@ -58,7 +58,7 @@ tqueueReceiveSlot(TupleTableSlot *slot, DestReceiver *self)
 	shm_mq_result result;
 
 	/* Send the tuple itself. */
-	tuple = ExecMaterializeSlot(slot);
+	tuple = ExecHeapifySlot(slot);
 	result = shm_mq_send(tqueue->queue, tuple->t_len, tuple->t_data, false);
 
 	/* Check for failure. */
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 83c69092ae..17c711d6bc 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -729,9 +729,12 @@ apply_handle_update(StringInfo s)
 	 */
 	if (found)
 	{
+		HeapTuple	tuple;
+
 		/* Process and store remote tuple in the slot */
 		oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
-		ExecStoreTuple(localslot->tts_tuple, remoteslot, InvalidBuffer, false);
+		tuple = ExecHeapifySlot(localslot);
+		ExecStoreTuple(tuple, remoteslot, InvalidBuffer, false);
 		slot_modify_cstrings(remoteslot, rel, newtup.values, newtup.changed);
 		MemoryContextSwitchTo(oldctx);
 
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index 2ab1815390..b1ceb854cd 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -20,6 +20,19 @@
 #include "access/transam.h"
 #include "storage/bufpage.h"
 
+/*
+ * Opaque tuple representation for executor's TupleTableSlot tts_storage
+ * (XXX This should probably live in a separate header)
+ */
+typedef struct HeapamTuple
+{
+	HeapTuple	hst_heaptuple;
+	bool		hst_slow;
+	long		hst_off;
+	MinimalTuple hst_mintuple;	/* minimal tuple, or NULL if none */
+	HeapTupleData hst_minhdr;	/* workspace for minimal-tuple-only case */
+}			HeapamTuple;
+
 /*
  * MaxTupleAttributeNumber limits the number of (user) columns in a tuple.
  * The key limit on this value is that the size of the fixed overhead for
@@ -658,7 +671,7 @@ struct MinimalTupleData
 /*
  * GETSTRUCT - given a HeapTuple pointer, return address of the user data
  */
-#define GETSTRUCT(TUP) ((char *) ((TUP)->t_data) + (TUP)->t_data->t_hoff)
+#define GETSTRUCT(TUP) ((char *) (((HeapTuple)(TUP))->t_data) + ((HeapTuple)(TUP))->t_data->t_hoff)
 
 /*
  * Accessor macros to be used with HeapTuple pointers.
diff --git a/src/include/access/storage_common.h b/src/include/access/storage_common.h
index ca140c1745..6128607313 100644
--- a/src/include/access/storage_common.h
+++ b/src/include/access/storage_common.h
@@ -21,10 +21,10 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "access/xlog.h"
+#include "executor/tuptable.h"
 #include "storage/bufpage.h"
 #include "storage/bufmgr.h"
 
-
 /* A physical tuple coming from a storage AM scan */
 typedef void *StorageTuple;
 
@@ -38,4 +38,40 @@ typedef enum
 	HEAPTUPLE_DELETE_IN_PROGRESS	/* deleting xact is still in progress */
 } HTSV_Result;
 
+/*
+ * slot storage routine functions
+ */
+typedef void (*SlotStoreTuple_function) (TupleTableSlot *slot,
+										 StorageTuple tuple,
+										 bool shouldFree,
+										 bool minumumtuple);
+typedef void (*SlotClearTuple_function) (TupleTableSlot *slot);
+typedef Datum (*SlotGetattr_function) (TupleTableSlot *slot,
+									   int attnum, bool *isnull);
+typedef void (*SlotVirtualizeTuple_function) (TupleTableSlot *slot, int16 upto);
+
+typedef HeapTuple (*SlotGetTuple_function) (TupleTableSlot *slot, bool palloc_copy);
+typedef MinimalTuple (*SlotGetMinTuple_function) (TupleTableSlot *slot, bool palloc_copy);
+
+typedef void (*SlotUpdateTableoid_function) (TupleTableSlot *slot, Oid tableoid);
+
+typedef void (*SpeculativeAbort_function) (Relation rel,
+										   TupleTableSlot *slot);
+
+typedef struct StorageSlotAmRoutine
+{
+	/* Operations on TupleTableSlot */
+	SlotStoreTuple_function slot_store_tuple;
+	SlotVirtualizeTuple_function slot_virtualize_tuple;
+	SlotClearTuple_function slot_clear_tuple;
+	SlotGetattr_function slot_getattr;
+	SlotGetTuple_function slot_tuple;
+	SlotGetMinTuple_function slot_min_tuple;
+	SlotUpdateTableoid_function slot_update_tableoid;
+}			StorageSlotAmRoutine;
+
+typedef StorageSlotAmRoutine * (*slot_storageam_hook) (void);
+
+extern StorageSlotAmRoutine * heapam_storage_slot_handler(void);
+
 #endif							/* STORAGE_COMMON_H */
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
index 56a791d0c6..7df51c4167 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -42,6 +42,8 @@ typedef struct StorageAmRoutine
 	SnapshotSatisfiesUpdate_function snapshot_satisfiesUpdate;	/* HeapTupleSatisfiesUpdate */
 	SnapshotSatisfiesVacuum_function snapshot_satisfiesVacuum;	/* HeapTupleSatisfiesVacuum */
 
+	slot_storageam_hook slot_storageam;
+
 }			StorageAmRoutine;
 
 extern StorageAmRoutine * GetStorageAmRoutine(Oid amhandler);
diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h
index 5b54834d33..df0fd0101c 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -18,9 +18,25 @@
 #include "access/tupdesc.h"
 #include "storage/buf.h"
 
+/*
+ * Forward declare StorageAmRoutine to avoid including storageamapi.h here
+ */
+struct StorageSlotAmRoutine;
+
+/*
+ * Forward declare StorageTuple to avoid including storageamapi.h here
+ */
+typedef void *StorageTuple;
+
 /*----------
  * The executor stores tuples in a "tuple table" which is a List of
- * independent TupleTableSlots.  There are several cases we need to handle:
+ * independent TupleTableSlots.
+ *
+ * XXX The "html-commented out" text below no longer reflects reality, as
+ * physical tuples are now responsibility of storage AMs.  But we have kept
+ * "minimal tuples".  Adjust this comment!
+ *
+ * <!-- There are several cases we need to handle:
  *		1. physical tuple in a disk buffer page
  *		2. physical tuple constructed in palloc'ed memory
  *		3. "minimal" physical tuple constructed in palloc'ed memory
@@ -56,6 +72,7 @@
  * had the fatal defect of invalidating any pass-by-reference Datums pointing
  * into the existing slot contents.)  Both copies must contain identical data
  * payloads when this is the case.
+ * -->
  *
  * The Datum/isnull arrays of a TupleTableSlot serve double duty.  When the
  * slot contains a virtual tuple, they are the authoritative data.  When the
@@ -82,11 +99,6 @@
  * When tts_shouldFree is true, the physical tuple is "owned" by the slot
  * and should be freed when the slot's reference to the tuple is dropped.
  *
- * If tts_buffer is not InvalidBuffer, then the slot is holding a pin
- * on the indicated buffer page; drop the pin when we release the
- * slot's reference to that buffer.  (tts_shouldFree should always be
- * false in such a case, since presumably tts_tuple is pointing at the
- * buffer page.)
  *
  * tts_nvalid indicates the number of valid columns in the tts_values/isnull
  * arrays.  When the slot is holding a "virtual" tuple this must be equal
@@ -114,24 +126,21 @@ typedef struct TupleTableSlot
 {
 	NodeTag		type;
 	bool		tts_isempty;	/* true = slot is empty */
-	bool		tts_shouldFree; /* should pfree tts_tuple? */
-	bool		tts_shouldFreeMin;	/* should pfree tts_mintuple? */
-	bool		tts_slow;		/* saved state for slot_deform_tuple */
-	HeapTuple	tts_tuple;		/* physical tuple, or NULL if virtual */
+	ItemPointerData tts_tid;	/* XXX describe */
 	TupleDesc	tts_tupleDescriptor;	/* slot's tuple descriptor */
 	MemoryContext tts_mcxt;		/* slot itself is in this context */
-	Buffer		tts_buffer;		/* tuple's buffer, or InvalidBuffer */
+	Oid			tts_tableOid;	/* XXX describe */
+	Oid			tts_tupleOid;	/* XXX describe */
 	int			tts_nvalid;		/* # of valid values in tts_values */
+	uint32		tts_speculativeToken;	/* XXX describe */
+	bool		tts_shouldFree;
+	bool		tts_shouldFreeMin;
 	Datum	   *tts_values;		/* current per-attribute values */
 	bool	   *tts_isnull;		/* current per-attribute isnull flags */
-	MinimalTuple tts_mintuple;	/* minimal tuple, or NULL if none */
-	HeapTupleData tts_minhdr;	/* workspace for minimal-tuple-only case */
-	long		tts_off;		/* saved state for slot_deform_tuple */
+	struct StorageSlotAmRoutine *tts_storageslotam; /* storage AM */
+	void	   *tts_storage;	/* storage AM's opaque space */
 } TupleTableSlot;
 
-#define TTS_HAS_PHYSICAL_TUPLE(slot)  \
-	((slot)->tts_tuple != NULL && (slot)->tts_tuple != &((slot)->tts_minhdr))
-
 /*
  * TupIsNull -- is a TupleTableSlot empty?
  */
@@ -143,9 +152,10 @@ extern TupleTableSlot *MakeTupleTableSlot(void);
 extern TupleTableSlot *ExecAllocTableSlot(List **tupleTable);
 extern void ExecResetTupleTable(List *tupleTable, bool shouldFree);
 extern TupleTableSlot *MakeSingleTupleTableSlot(TupleDesc tupdesc);
+extern bool ExecSlotCompare(TupleTableSlot *slot1, TupleTableSlot *slot2);
 extern void ExecDropSingleTupleTableSlot(TupleTableSlot *slot);
 extern void ExecSetSlotDescriptor(TupleTableSlot *slot, TupleDesc tupdesc);
-extern TupleTableSlot *ExecStoreTuple(HeapTuple tuple,
+extern TupleTableSlot *ExecStoreTuple(void *tuple,
 			   TupleTableSlot *slot,
 			   Buffer buffer,
 			   bool shouldFree);
@@ -155,12 +165,14 @@ extern TupleTableSlot *ExecStoreMinimalTuple(MinimalTuple mtup,
 extern TupleTableSlot *ExecClearTuple(TupleTableSlot *slot);
 extern TupleTableSlot *ExecStoreVirtualTuple(TupleTableSlot *slot);
 extern TupleTableSlot *ExecStoreAllNullTuple(TupleTableSlot *slot);
-extern HeapTuple ExecCopySlotTuple(TupleTableSlot *slot);
+extern StorageTuple ExecCopySlotTuple(TupleTableSlot *slot);
 extern MinimalTuple ExecCopySlotMinimalTuple(TupleTableSlot *slot);
-extern HeapTuple ExecFetchSlotTuple(TupleTableSlot *slot);
+extern void ExecSlotUpdateTupleTableoid(TupleTableSlot *slot, Oid tableoid);
+extern StorageTuple ExecFetchSlotTuple(TupleTableSlot *slot);
 extern MinimalTuple ExecFetchSlotMinimalTuple(TupleTableSlot *slot);
 extern Datum ExecFetchSlotTupleDatum(TupleTableSlot *slot);
-extern HeapTuple ExecMaterializeSlot(TupleTableSlot *slot);
+extern void ExecMaterializeSlot(TupleTableSlot *slot);
+extern StorageTuple ExecHeapifySlot(TupleTableSlot *slot);
 extern TupleTableSlot *ExecCopySlot(TupleTableSlot *dstslot,
 			 TupleTableSlot *srcslot);
 
-- 
2.15.0.windows.1



  [application/octet-stream] 0006-Tuple-Insert-API-is-added-to-Storage-AM.patch (99.5K, ../../CAJrrPGeoihBWs+BUP06q9=rCNm75m56J-EEStf3ygikNcpreYw@mail.gmail.com/9-0006-Tuple-Insert-API-is-added-to-Storage-AM.patch)
  download | inline diff:
From b80931c9e7c73daf109199c1dfecf636e21a26c7 Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Wed, 3 Jan 2018 17:21:07 +1100
Subject: [PATCH 06/12] Tuple Insert API is added to Storage AM

heap_insert, heap_delete, heap_fetch, heap_update,
heap_get_latest_oid, heap_lock_tuple and heap_multi_insert
functions are added to storage AM. Move the index insertion
logic into storage AM, The Index insert still outside for
the case of multi_insert (Yet to change). In case of delete
also, the index tuple delete function pointer is avaiable.

Replaced the usage of HeapTuple with storageTuple in
some places, increased the use of slot.
---
 src/backend/access/common/heaptuple.c    |  24 +++
 src/backend/access/heap/heapam.c         | 125 +++++++++-------
 src/backend/access/heap/heapam_storage.c | 235 +++++++++++++++++++++++++++++
 src/backend/access/heap/rewriteheap.c    |   5 +-
 src/backend/access/heap/tuptoaster.c     |   9 +-
 src/backend/access/storage/storageam.c   | 117 +++++++++++++++
 src/backend/commands/copy.c              |  39 ++---
 src/backend/commands/createas.c          |  24 +--
 src/backend/commands/matview.c           |  22 +--
 src/backend/commands/tablecmds.c         |   6 +-
 src/backend/commands/trigger.c           |  50 ++++---
 src/backend/executor/execIndexing.c      |   2 +-
 src/backend/executor/execMain.c          | 131 ++++++++---------
 src/backend/executor/execReplication.c   |  72 ++++-----
 src/backend/executor/nodeLockRows.c      |  47 +++---
 src/backend/executor/nodeModifyTable.c   | 245 ++++++++++++++-----------------
 src/backend/executor/nodeTidscan.c       |  23 ++-
 src/backend/utils/adt/tid.c              |   5 +-
 src/include/access/heapam.h              |  14 +-
 src/include/access/htup_details.h        |   1 +
 src/include/access/storage_common.h      |   3 -
 src/include/access/storageam.h           |  84 +++++++++++
 src/include/access/storageamapi.h        |  73 ++++++++-
 src/include/commands/trigger.h           |   2 +-
 src/include/executor/executor.h          |  15 +-
 src/include/executor/tuptable.h          |   1 +
 src/include/nodes/execnodes.h            |   8 +-
 27 files changed, 957 insertions(+), 425 deletions(-)
 create mode 100644 src/include/access/storageam.h

diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index a50a76dc76..627cf6fc64 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -685,6 +685,30 @@ heap_copy_tuple_as_datum(HeapTuple tuple, TupleDesc tupleDesc)
 	return PointerGetDatum(td);
 }
 
+/*
+ * heap_form_tuple_by_datum
+ *		construct a tuple from the given dataum
+ *
+ * The result is allocated in the current memory context.
+ */
+HeapTuple
+heap_form_tuple_by_datum(Datum data, Oid tableoid)
+{
+	HeapTuple	newTuple;
+	HeapTupleHeader td;
+
+	td = DatumGetHeapTupleHeader(data);
+
+	newTuple = (HeapTuple) palloc(HEAPTUPLESIZE + HeapTupleHeaderGetDatumLength(td));
+	newTuple->t_len = HeapTupleHeaderGetDatumLength(td);
+	newTuple->t_self = td->t_ctid;
+	newTuple->t_tableOid = tableoid;
+	newTuple->t_data = (HeapTupleHeader) ((char *) newTuple + HEAPTUPLESIZE);
+	memcpy((char *) newTuple->t_data, (char *) td, newTuple->t_len);
+
+	return newTuple;
+}
+
 /*
  * heap_form_tuple
  *		construct a tuple from the given values[] and isnull[] arrays,
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index bc092056f8..c1da192f14 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -1893,13 +1893,13 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
  */
 bool
 heap_fetch(Relation relation,
+		   ItemPointer tid,
 		   Snapshot snapshot,
 		   HeapTuple tuple,
 		   Buffer *userbuf,
 		   bool keep_buf,
 		   Relation stats_relation)
 {
-	ItemPointer tid = &(tuple->t_self);
 	ItemId		lp;
 	Buffer		buffer;
 	Page		page;
@@ -1933,7 +1933,6 @@ heap_fetch(Relation relation,
 			ReleaseBuffer(buffer);
 			*userbuf = InvalidBuffer;
 		}
-		tuple->t_data = NULL;
 		return false;
 	}
 
@@ -1955,13 +1954,13 @@ heap_fetch(Relation relation,
 			ReleaseBuffer(buffer);
 			*userbuf = InvalidBuffer;
 		}
-		tuple->t_data = NULL;
 		return false;
 	}
 
 	/*
-	 * fill in *tuple fields
+	 * fill in tuple fields and place it in stuple
 	 */
+	ItemPointerCopy(tid, &(tuple->t_self));
 	tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
 	tuple->t_len = ItemIdGetLength(lp);
 	tuple->t_tableOid = RelationGetRelid(relation);
@@ -2312,7 +2311,6 @@ heap_get_latest_tid(Relation relation,
 	}							/* end of loop */
 }
 
-
 /*
  * UpdateXmaxHintBits - update tuple hint bits after xmax transaction ends
  *
@@ -4576,13 +4574,12 @@ get_mxact_status_for_lock(LockTupleMode mode, bool is_update)
  * See README.tuplock for a thorough explanation of this mechanism.
  */
 HTSU_Result
-heap_lock_tuple(Relation relation, HeapTuple tuple,
+heap_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * stuple,
 				CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 				bool follow_updates,
 				Buffer *buffer, HeapUpdateFailureData *hufd)
 {
 	HTSU_Result result;
-	ItemPointer tid = &(tuple->t_self);
 	ItemId		lp;
 	Page		page;
 	Buffer		vmbuffer = InvalidBuffer;
@@ -4595,6 +4592,9 @@ heap_lock_tuple(Relation relation, HeapTuple tuple,
 	bool		first_time = true;
 	bool		have_tuple_lock = false;
 	bool		cleared_all_frozen = false;
+	HeapTupleData tuple;
+
+	Assert(stuple != NULL);
 
 	*buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
 	block = ItemPointerGetBlockNumber(tid);
@@ -4614,12 +4614,13 @@ heap_lock_tuple(Relation relation, HeapTuple tuple,
 	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid));
 	Assert(ItemIdIsNormal(lp));
 
-	tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	tuple->t_len = ItemIdGetLength(lp);
-	tuple->t_tableOid = RelationGetRelid(relation);
+	tuple.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+	tuple.t_len = ItemIdGetLength(lp);
+	tuple.t_tableOid = RelationGetRelid(relation);
+	ItemPointerCopy(tid, &tuple.t_self);
 
 l3:
-	result = relation->rd_stamroutine->snapshot_satisfiesUpdate(tuple, cid, *buffer);
+	result = HeapTupleSatisfiesUpdate(&tuple, cid, *buffer);
 
 	if (result == HeapTupleInvisible)
 	{
@@ -4641,10 +4642,10 @@ l3:
 		ItemPointerData t_ctid;
 
 		/* must copy state data before unlocking buffer */
-		xwait = HeapTupleHeaderGetRawXmax(tuple->t_data);
-		infomask = tuple->t_data->t_infomask;
-		infomask2 = tuple->t_data->t_infomask2;
-		ItemPointerCopy(&tuple->t_data->t_ctid, &t_ctid);
+		xwait = HeapTupleHeaderGetRawXmax(tuple.t_data);
+		infomask = tuple.t_data->t_infomask;
+		infomask2 = tuple.t_data->t_infomask2;
+		ItemPointerCopy(&tuple.t_data->t_ctid, &t_ctid);
 
 		LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
 
@@ -4776,7 +4777,7 @@ l3:
 				{
 					HTSU_Result res;
 
-					res = heap_lock_updated_tuple(relation, tuple, &t_ctid,
+					res = heap_lock_updated_tuple(relation, &tuple, &t_ctid,
 												  GetCurrentTransactionId(),
 												  mode);
 					if (res != HeapTupleMayBeUpdated)
@@ -4797,8 +4798,8 @@ l3:
 				 * now need to follow the update chain to lock the new
 				 * versions.
 				 */
-				if (!HeapTupleHeaderIsOnlyLocked(tuple->t_data) &&
-					((tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED) ||
+				if (!HeapTupleHeaderIsOnlyLocked(tuple.t_data) &&
+					((tuple.t_data->t_infomask2 & HEAP_KEYS_UPDATED) ||
 					 !updated))
 					goto l3;
 
@@ -4829,8 +4830,8 @@ l3:
 				 * Make sure it's still an appropriate lock, else start over.
 				 * See above about allowing xmax to change.
 				 */
-				if (!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask) ||
-					HEAP_XMAX_IS_EXCL_LOCKED(tuple->t_data->t_infomask))
+				if (!HEAP_XMAX_IS_LOCKED_ONLY(tuple.t_data->t_infomask) ||
+					HEAP_XMAX_IS_EXCL_LOCKED(tuple.t_data->t_infomask))
 					goto l3;
 				require_sleep = false;
 			}
@@ -4852,8 +4853,8 @@ l3:
 					 * meantime, start over.
 					 */
 					LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-					if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
-						!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple->t_data),
+					if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
+						!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple.t_data),
 											 xwait))
 						goto l3;
 
@@ -4866,9 +4867,9 @@ l3:
 				LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
 
 				/* if the xmax changed in the meantime, start over */
-				if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
+				if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
 					!TransactionIdEquals(
-										 HeapTupleHeaderGetRawXmax(tuple->t_data),
+										 HeapTupleHeaderGetRawXmax(tuple.t_data),
 										 xwait))
 					goto l3;
 				/* otherwise, we're good */
@@ -4893,11 +4894,11 @@ l3:
 		{
 			/* ... but if the xmax changed in the meantime, start over */
 			LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-			if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
-				!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple->t_data),
+			if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
+				!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple.t_data),
 									 xwait))
 				goto l3;
-			Assert(HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask));
+			Assert(HEAP_XMAX_IS_LOCKED_ONLY(tuple.t_data->t_infomask));
 			require_sleep = false;
 		}
 
@@ -4954,7 +4955,7 @@ l3:
 				{
 					case LockWaitBlock:
 						MultiXactIdWait((MultiXactId) xwait, status, infomask,
-										relation, &tuple->t_self, XLTW_Lock, NULL);
+										relation, &tuple.t_self, XLTW_Lock, NULL);
 						break;
 					case LockWaitSkip:
 						if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
@@ -4995,7 +4996,7 @@ l3:
 				switch (wait_policy)
 				{
 					case LockWaitBlock:
-						XactLockTableWait(xwait, relation, &tuple->t_self,
+						XactLockTableWait(xwait, relation, &tuple.t_self,
 										  XLTW_Lock);
 						break;
 					case LockWaitSkip:
@@ -5022,7 +5023,7 @@ l3:
 			{
 				HTSU_Result res;
 
-				res = heap_lock_updated_tuple(relation, tuple, &t_ctid,
+				res = heap_lock_updated_tuple(relation, &tuple, &t_ctid,
 											  GetCurrentTransactionId(),
 											  mode);
 				if (res != HeapTupleMayBeUpdated)
@@ -5041,8 +5042,8 @@ l3:
 			 * other xact could update this tuple before we get to this point.
 			 * Check for xmax change, and start over if so.
 			 */
-			if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
-				!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple->t_data),
+			if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
+				!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple.t_data),
 									 xwait))
 				goto l3;
 
@@ -5056,7 +5057,7 @@ l3:
 				 * don't check for this in the multixact case, because some
 				 * locker transactions might still be running.
 				 */
-				UpdateXmaxHintBits(tuple->t_data, *buffer, xwait);
+				UpdateXmaxHintBits(tuple.t_data, *buffer, xwait);
 			}
 		}
 
@@ -5068,9 +5069,9 @@ l3:
 		 * at all for whatever reason.
 		 */
 		if (!require_sleep ||
-			(tuple->t_data->t_infomask & HEAP_XMAX_INVALID) ||
-			HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask) ||
-			HeapTupleHeaderIsOnlyLocked(tuple->t_data))
+			(tuple.t_data->t_infomask & HEAP_XMAX_INVALID) ||
+			HEAP_XMAX_IS_LOCKED_ONLY(tuple.t_data->t_infomask) ||
+			HeapTupleHeaderIsOnlyLocked(tuple.t_data))
 			result = HeapTupleMayBeUpdated;
 		else
 			result = HeapTupleUpdated;
@@ -5081,11 +5082,11 @@ failed:
 	{
 		Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated ||
 			   result == HeapTupleWouldBlock);
-		Assert(!(tuple->t_data->t_infomask & HEAP_XMAX_INVALID));
-		hufd->ctid = tuple->t_data->t_ctid;
-		hufd->xmax = HeapTupleHeaderGetUpdateXid(tuple->t_data);
+		Assert(!(tuple.t_data->t_infomask & HEAP_XMAX_INVALID));
+		hufd->ctid = tuple.t_data->t_ctid;
+		hufd->xmax = HeapTupleHeaderGetUpdateXid(tuple.t_data);
 		if (result == HeapTupleSelfUpdated)
-			hufd->cmax = HeapTupleHeaderGetCmax(tuple->t_data);
+			hufd->cmax = HeapTupleHeaderGetCmax(tuple.t_data);
 		else
 			hufd->cmax = InvalidCommandId;
 		goto out_locked;
@@ -5108,8 +5109,8 @@ failed:
 		goto l3;
 	}
 
-	xmax = HeapTupleHeaderGetRawXmax(tuple->t_data);
-	old_infomask = tuple->t_data->t_infomask;
+	xmax = HeapTupleHeaderGetRawXmax(tuple.t_data);
+	old_infomask = tuple.t_data->t_infomask;
 
 	/*
 	 * If this is the first possibly-multixact-able operation in the current
@@ -5126,7 +5127,7 @@ failed:
 	 * not modify the tuple just yet, because that would leave it in the wrong
 	 * state if multixact.c elogs.
 	 */
-	compute_new_xmax_infomask(xmax, old_infomask, tuple->t_data->t_infomask2,
+	compute_new_xmax_infomask(xmax, old_infomask, tuple.t_data->t_infomask2,
 							  GetCurrentTransactionId(), mode, false,
 							  &xid, &new_infomask, &new_infomask2);
 
@@ -5142,13 +5143,13 @@ failed:
 	 * Also reset the HOT UPDATE bit, but only if there's no update; otherwise
 	 * we would break the HOT chain.
 	 */
-	tuple->t_data->t_infomask &= ~HEAP_XMAX_BITS;
-	tuple->t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
-	tuple->t_data->t_infomask |= new_infomask;
-	tuple->t_data->t_infomask2 |= new_infomask2;
+	tuple.t_data->t_infomask &= ~HEAP_XMAX_BITS;
+	tuple.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+	tuple.t_data->t_infomask |= new_infomask;
+	tuple.t_data->t_infomask2 |= new_infomask2;
 	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
-		HeapTupleHeaderClearHotUpdated(tuple->t_data);
-	HeapTupleHeaderSetXmax(tuple->t_data, xid);
+		HeapTupleHeaderClearHotUpdated(tuple.t_data);
+	HeapTupleHeaderSetXmax(tuple.t_data, xid);
 
 	/*
 	 * Make sure there is no forward chain link in t_ctid.  Note that in the
@@ -5158,7 +5159,7 @@ failed:
 	 * the tuple as well.
 	 */
 	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
-		tuple->t_data->t_ctid = *tid;
+		tuple.t_data->t_ctid = *tid;
 
 	/* Clear only the all-frozen bit on visibility map if needed */
 	if (PageIsAllVisible(page) &&
@@ -5189,10 +5190,10 @@ failed:
 		XLogBeginInsert();
 		XLogRegisterBuffer(0, *buffer, REGBUF_STANDARD);
 
-		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self);
+		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple.t_self);
 		xlrec.locking_xid = xid;
 		xlrec.infobits_set = compute_infobits(new_infomask,
-											  tuple->t_data->t_infomask2);
+											  tuple.t_data->t_infomask2);
 		xlrec.flags = cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
 		XLogRegisterData((char *) &xlrec, SizeOfHeapLock);
 
@@ -5226,6 +5227,7 @@ out_unlocked:
 	if (have_tuple_lock)
 		UnlockTupleTuplock(relation, tid, mode);
 
+	*stuple = heap_copytuple(&tuple);
 	return result;
 }
 
@@ -5683,9 +5685,8 @@ heap_lock_updated_tuple_rec(Relation rel, ItemPointer tid, TransactionId xid,
 		new_infomask = 0;
 		new_xmax = InvalidTransactionId;
 		block = ItemPointerGetBlockNumber(&tupid);
-		ItemPointerCopy(&tupid, &(mytup.t_self));
 
-		if (!heap_fetch(rel, SnapshotAny, &mytup, &buf, false, NULL))
+		if (!heap_fetch(rel, &tupid, SnapshotAny, &mytup, &buf, false, NULL))
 		{
 			/*
 			 * if we fail to find the updated version of the tuple, it's
@@ -6032,14 +6033,18 @@ heap_lock_updated_tuple(Relation rel, HeapTuple tuple, ItemPointer ctid,
  * An explicit confirmation WAL record also makes logical decoding simpler.
  */
 void
-heap_finish_speculative(Relation relation, HeapTuple tuple)
+heap_finish_speculative(Relation relation, TupleTableSlot *slot)
 {
+	HeapamTuple *stuple = (HeapamTuple *) slot->tts_storage;
+	HeapTuple	tuple = stuple->hst_heaptuple;
 	Buffer		buffer;
 	Page		page;
 	OffsetNumber offnum;
 	ItemId		lp = NULL;
 	HeapTupleHeader htup;
 
+	Assert(slot->tts_speculativeToken != 0);
+
 	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&(tuple->t_self)));
 	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 	page = (Page) BufferGetPage(buffer);
@@ -6094,6 +6099,7 @@ heap_finish_speculative(Relation relation, HeapTuple tuple)
 	END_CRIT_SECTION();
 
 	UnlockReleaseBuffer(buffer);
+	slot->tts_speculativeToken = 0;
 }
 
 /*
@@ -6123,8 +6129,10 @@ heap_finish_speculative(Relation relation, HeapTuple tuple)
  * confirmation records.
  */
 void
-heap_abort_speculative(Relation relation, HeapTuple tuple)
+heap_abort_speculative(Relation relation, TupleTableSlot *slot)
 {
+	HeapamTuple *stuple = (HeapamTuple *) slot->tts_storage;
+	HeapTuple	tuple = stuple->hst_heaptuple;
 	TransactionId xid = GetCurrentTransactionId();
 	ItemPointer tid = &(tuple->t_self);
 	ItemId		lp;
@@ -6133,6 +6141,10 @@ heap_abort_speculative(Relation relation, HeapTuple tuple)
 	BlockNumber block;
 	Buffer		buffer;
 
+	/*
+	 * Assert(slot->tts_speculativeToken != 0); This needs some update in
+	 * toast
+	 */
 	Assert(ItemPointerIsValid(tid));
 
 	block = ItemPointerGetBlockNumber(tid);
@@ -6246,6 +6258,7 @@ heap_abort_speculative(Relation relation, HeapTuple tuple)
 
 	/* count deletion, as we counted the insertion too */
 	pgstat_count_heap_delete(relation);
+	slot->tts_speculativeToken = 0;
 }
 
 /*
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index a953a690b3..438063cbe1 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -23,8 +23,231 @@
 #include "access/heapam.h"
 #include "access/storageamapi.h"
 #include "utils/builtins.h"
+#include "utils/rel.h"
 
 
+/* ----------------------------------------------------------------
+ *				storage AM support routines for heapam
+ * ----------------------------------------------------------------
+ */
+
+static bool
+heapam_fetch(Relation relation,
+			 ItemPointer tid,
+			 Snapshot snapshot,
+			 StorageTuple * stuple,
+			 Buffer *userbuf,
+			 bool keep_buf,
+			 Relation stats_relation)
+{
+	HeapTupleData tuple;
+
+	*stuple = NULL;
+	if (heap_fetch(relation, tid, snapshot, &tuple, userbuf, keep_buf, stats_relation))
+	{
+		*stuple = heap_copytuple(&tuple);
+		return true;
+	}
+
+	return false;
+}
+
+/*
+ * Insert a heap tuple from a slot, which may contain an OID and speculative
+ * insertion token.
+ */
+static Oid
+heapam_heap_insert(Relation relation, TupleTableSlot *slot, CommandId cid,
+				   int options, BulkInsertState bistate, InsertIndexTuples IndexFunc,
+				   EState *estate, List *arbiterIndexes, List **recheckIndexes)
+{
+	Oid			oid;
+	HeapTuple	tuple = NULL;
+
+	if (slot->tts_storage)
+	{
+		HeapamTuple *htuple = slot->tts_storage;
+
+		tuple = htuple->hst_heaptuple;
+
+		if (relation->rd_rel->relhasoids)
+			HeapTupleSetOid(tuple, InvalidOid);
+	}
+	else
+	{
+		/*
+		 * Obtain the physical tuple to insert, building from the slot values.
+		 * XXX: maybe the slot already contains a physical tuple in the right
+		 * format?  In fact, if the slot isn't fully deformed, this is
+		 * completely bogus ...
+		 */
+		tuple = heap_form_tuple(slot->tts_tupleDescriptor,
+								slot->tts_values,
+								slot->tts_isnull);
+	}
+
+	/* Set the OID, if the slot has one */
+	if (slot->tts_tupleOid != InvalidOid)
+		HeapTupleHeaderSetOid(tuple->t_data, slot->tts_tupleOid);
+
+	/* Update the tuple with table oid */
+	if (slot->tts_tableOid != InvalidOid)
+		tuple->t_tableOid = slot->tts_tableOid;
+
+	/* Set the speculative insertion token, if the slot has one */
+	if ((options & HEAP_INSERT_SPECULATIVE) && slot->tts_speculativeToken)
+		HeapTupleHeaderSetSpeculativeToken(tuple->t_data, slot->tts_speculativeToken);
+
+	/* Perform the insertion, and copy the resulting ItemPointer */
+	oid = heap_insert(relation, tuple, cid, options, bistate);
+	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
+
+	if (slot->tts_storage == NULL)
+		ExecStoreTuple(tuple, slot, InvalidBuffer, true);
+
+	if ((estate != NULL) && (estate->es_result_relation_info->ri_NumIndices > 0))
+	{
+		Assert(IndexFunc != NULL);
+
+		if (options & HEAP_INSERT_SPECULATIVE)
+		{
+			bool		specConflict = false;
+
+			*recheckIndexes = (IndexFunc) (slot, estate, true,
+										   &specConflict,
+										   arbiterIndexes);
+
+			/* adjust the tuple's state accordingly */
+			if (!specConflict)
+				heap_finish_speculative(relation, slot);
+			else
+			{
+				heap_abort_speculative(relation, slot);
+				slot->tts_specConflict = true;
+			}
+		}
+		else
+		{
+			*recheckIndexes = (IndexFunc) (slot, estate, false,
+										   NULL, arbiterIndexes);
+		}
+	}
+
+	return oid;
+}
+
+static HTSU_Result
+heapam_heap_delete(Relation relation, ItemPointer tid, CommandId cid,
+				   Snapshot crosscheck, bool wait, DeleteIndexTuples IndexFunc,
+				   HeapUpdateFailureData *hufd)
+{
+	/*
+	 * Currently Deleting of index tuples are handled at vacuum, in case
+	 * if the storage itself is cleaning the dead tuples by itself, it is
+	 * the time to call the index tuple deletion also.
+	 */
+	return heap_delete(relation, tid, cid, crosscheck, wait, hufd);
+}
+
+
+
+static HTSU_Result
+heapam_heap_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
+				   EState *estate, CommandId cid, Snapshot crosscheck,
+				   bool wait, HeapUpdateFailureData *hufd, LockTupleMode *lockmode,
+				   InsertIndexTuples IndexFunc, List **recheckIndexes)
+{
+	HeapTuple	tuple;
+	HTSU_Result result;
+
+	if (slot->tts_storage)
+	{
+		HeapamTuple *htuple = slot->tts_storage;
+
+		tuple = htuple->hst_heaptuple;
+	}
+	else
+	{
+		tuple = heap_form_tuple(slot->tts_tupleDescriptor,
+								slot->tts_values,
+								slot->tts_isnull);
+	}
+
+	/* Set the OID, if the slot has one */
+	if (slot->tts_tupleOid != InvalidOid)
+		HeapTupleHeaderSetOid(tuple->t_data, slot->tts_tupleOid);
+
+	/* Update the tuple with table oid */
+	if (slot->tts_tableOid != InvalidOid)
+		tuple->t_tableOid = slot->tts_tableOid;
+
+	result = heap_update(relation, otid, tuple, cid, crosscheck, wait,
+						 hufd, lockmode);
+	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
+
+	if (slot->tts_storage == NULL)
+		ExecStoreTuple(tuple, slot, InvalidBuffer, true);
+
+	/*
+	 * Note: instead of having to update the old index tuples associated with
+	 * the heap tuple, all we do is form and insert new index tuples. This is
+	 * because UPDATEs are actually DELETEs and INSERTs, and index tuple
+	 * deletion is done later by VACUUM (see notes in ExecDelete). All we do
+	 * here is insert new index tuples.  -cim 9/27/89
+	 */
+
+	/*
+	 * insert index entries for tuple
+	 *
+	 * Note: heap_update returns the tid (location) of the new tuple in the
+	 * t_self field.
+	 *
+	 * If it's a HOT update, we mustn't insert new index entries.
+	 */
+	if ((result == HeapTupleMayBeUpdated) &&
+		((estate != NULL) && (estate->es_result_relation_info->ri_NumIndices > 0)) &&
+		(!HeapTupleIsHeapOnly(tuple)))
+		*recheckIndexes = (IndexFunc) (slot, estate, false, NULL, NIL);
+
+	return result;
+}
+
+static tuple_data
+heapam_get_tuple_data(StorageTuple tuple, tuple_data_flags flags)
+{
+	tuple_data	result;
+
+	switch (flags)
+	{
+		case XMIN:
+			result.xid = HeapTupleHeaderGetXmin(((HeapTuple) tuple)->t_data);
+			break;
+		case UPDATED_XID:
+			result.xid = HeapTupleHeaderGetUpdateXid(((HeapTuple) tuple)->t_data);
+			break;
+		case CMIN:
+			result.cid = HeapTupleHeaderGetCmin(((HeapTuple) tuple)->t_data);
+			break;
+		case TID:
+			result.tid = ((HeapTuple) tuple)->t_self;
+			break;
+		case CTID:
+			result.tid = ((HeapTuple) tuple)->t_data->t_ctid;
+			break;
+		default:
+			Assert(0);
+			break;
+	}
+
+	return result;
+}
+
+static StorageTuple
+heapam_form_tuple_by_datum(Datum data, Oid tableoid)
+{
+	return heap_form_tuple_by_datum(data, tableoid);
+}
+
 Datum
 heapam_storage_handler(PG_FUNCTION_ARGS)
 {
@@ -37,5 +260,17 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 
 	amroutine->slot_storageam = heapam_storage_slot_handler;
 
+	amroutine->tuple_fetch = heapam_fetch;
+	amroutine->tuple_insert = heapam_heap_insert;
+	amroutine->tuple_delete = heapam_heap_delete;
+	amroutine->tuple_update = heapam_heap_update;
+	amroutine->tuple_lock = heap_lock_tuple;
+	amroutine->multi_insert = heap_multi_insert;
+
+	amroutine->get_tuple_data = heapam_get_tuple_data;
+	amroutine->tuple_from_datum = heapam_form_tuple_by_datum;
+	amroutine->tuple_get_latest_tid = heap_get_latest_tid;
+	amroutine->relation_sync = heap_sync;
+
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 7d466c2588..2e961a7694 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -110,6 +110,7 @@
 #include "access/heapam.h"
 #include "access/heapam_xlog.h"
 #include "access/rewriteheap.h"
+#include "access/storageam.h"
 #include "access/transam.h"
 #include "access/tuptoaster.h"
 #include "access/xact.h"
@@ -126,13 +127,13 @@
 
 #include "storage/bufmgr.h"
 #include "storage/fd.h"
+#include "storage/procarray.h"
 #include "storage/smgr.h"
 
 #include "utils/memutils.h"
 #include "utils/rel.h"
 #include "utils/tqual.h"
 
-#include "storage/procarray.h"
 
 /*
  * State associated with a rewrite operation. This is opaque to the user
@@ -357,7 +358,7 @@ end_heap_rewrite(RewriteState state)
 	 * wrote before the checkpoint.
 	 */
 	if (RelationNeedsWAL(state->rs_new_rel))
-		heap_sync(state->rs_new_rel);
+		storage_sync(state->rs_new_rel);
 
 	logical_end_heap_rewrite(state);
 
diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c
index 546f80f05c..34a06570f1 100644
--- a/src/backend/access/heap/tuptoaster.c
+++ b/src/backend/access/heap/tuptoaster.c
@@ -32,6 +32,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/storageam.h"
 #include "access/tuptoaster.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
@@ -1777,7 +1778,13 @@ toast_delete_datum(Relation rel, Datum value, bool is_speculative)
 		 * Have a chunk, delete it
 		 */
 		if (is_speculative)
-			heap_abort_speculative(toastrel, toasttup);
+		{
+			TupleTableSlot *slot = MakeSingleTupleTableSlot(RelationGetDescr(toastrel));
+
+			ExecStoreTuple(toasttup, slot, InvalidBuffer, false);
+			heap_abort_speculative(toastrel, slot);
+			ExecDropSingleTupleTableSlot(slot);
+		}
 		else
 			simple_heap_delete(toastrel, &toasttup->t_self);
 	}
diff --git a/src/backend/access/storage/storageam.c b/src/backend/access/storage/storageam.c
index 8541c75782..a8b971d004 100644
--- a/src/backend/access/storage/storageam.c
+++ b/src/backend/access/storage/storageam.c
@@ -13,3 +13,120 @@
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
+
+#include "access/storageam.h"
+#include "access/storageamapi.h"
+#include "utils/rel.h"
+
+/*
+ *	storage_fetch		- retrieve tuple with given tid
+ */
+bool
+storage_fetch(Relation relation,
+			  ItemPointer tid,
+			  Snapshot snapshot,
+			  StorageTuple * stuple,
+			  Buffer *userbuf,
+			  bool keep_buf,
+			  Relation stats_relation)
+{
+	return relation->rd_stamroutine->tuple_fetch(relation, tid, snapshot, stuple,
+												 userbuf, keep_buf, stats_relation);
+}
+
+
+/*
+ *	storage_lock_tuple - lock a tuple in shared or exclusive mode
+ */
+HTSU_Result
+storage_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * stuple,
+				   CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
+				   bool follow_updates, Buffer *buffer, HeapUpdateFailureData *hufd)
+{
+	return relation->rd_stamroutine->tuple_lock(relation, tid, stuple,
+												cid, mode, wait_policy,
+												follow_updates, buffer, hufd);
+}
+
+/*
+ * Insert a tuple from a slot into storage AM routine
+ */
+Oid
+storage_insert(Relation relation, TupleTableSlot *slot, CommandId cid,
+			   int options, BulkInsertState bistate, InsertIndexTuples IndexFunc,
+			   EState *estate, List *arbiterIndexes, List **recheckIndexes)
+{
+	return relation->rd_stamroutine->tuple_insert(relation, slot, cid, options,
+												  bistate, IndexFunc, estate,
+												  arbiterIndexes, recheckIndexes);
+}
+
+/*
+ * Delete a tuple from tid using storage AM routine
+ */
+HTSU_Result
+storage_delete(Relation relation, ItemPointer tid, CommandId cid,
+			   Snapshot crosscheck, bool wait, DeleteIndexTuples IndexFunc,
+			   HeapUpdateFailureData *hufd)
+{
+	return relation->rd_stamroutine->tuple_delete(relation, tid, cid,
+												  crosscheck, wait, IndexFunc, hufd);
+}
+
+/*
+ * update a tuple from tid using storage AM routine
+ */
+HTSU_Result
+storage_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
+			   EState *estate, CommandId cid, Snapshot crosscheck, bool wait,
+			   HeapUpdateFailureData *hufd, LockTupleMode *lockmode,
+			   InsertIndexTuples IndexFunc, List **recheckIndexes)
+{
+	return relation->rd_stamroutine->tuple_update(relation, otid, slot, estate,
+												  cid, crosscheck, wait, hufd,
+												  lockmode, IndexFunc, recheckIndexes);
+}
+
+
+/*
+ *	storage_multi_insert	- insert multiple tuple into a storage
+ */
+void
+storage_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
+					 CommandId cid, int options, BulkInsertState bistate)
+{
+	relation->rd_stamroutine->multi_insert(relation, tuples, ntuples,
+										   cid, options, bistate);
+}
+
+tuple_data
+storage_tuple_get_data(Relation relation, StorageTuple tuple, tuple_data_flags flags)
+{
+	return relation->rd_stamroutine->get_tuple_data(tuple, flags);
+}
+
+StorageTuple
+storage_tuple_by_datum(Relation relation, Datum data, Oid tableoid)
+{
+	if (relation)
+		return relation->rd_stamroutine->tuple_from_datum(data, tableoid);
+	else
+		return heap_form_tuple_by_datum(data, tableoid);
+}
+
+void
+storage_get_latest_tid(Relation relation,
+					   Snapshot snapshot,
+					   ItemPointer tid)
+{
+	relation->rd_stamroutine->tuple_get_latest_tid(relation, snapshot, tid);
+}
+
+/*
+ *	storage_sync		- sync a heap, for use when no WAL has been written
+ */
+void
+storage_sync(Relation rel)
+{
+	rel->rd_stamroutine->relation_sync(rel);
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index f44566591a..2dfc6e0a20 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -20,6 +20,7 @@
 
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "access/xlog.h"
@@ -2719,8 +2720,6 @@ CopyFrom(CopyState cstate)
 
 			if (slot == NULL)	/* "do nothing" */
 				skip_tuple = true;
-			else				/* trigger might have changed tuple */
-				tuple = ExecHeapifySlot(slot);
 		}
 
 		if (!skip_tuple)
@@ -2783,19 +2782,11 @@ CopyFrom(CopyState cstate)
 					List	   *recheckIndexes = NIL;
 
 					/* OK, store the tuple and create index entries for it */
-					heap_insert(resultRelInfo->ri_RelationDesc, tuple, mycid,
-								hi_options, bistate);
-
-					if (resultRelInfo->ri_NumIndices > 0)
-						recheckIndexes = ExecInsertIndexTuples(slot,
-															   &(tuple->t_self),
-															   estate,
-															   false,
-															   NULL,
-															   NIL);
+					storage_insert(resultRelInfo->ri_RelationDesc, slot, mycid, hi_options,
+								   bistate, ExecInsertIndexTuples, estate, NIL, &recheckIndexes);
 
 					/* AFTER ROW INSERT Triggers */
-					ExecARInsertTriggers(estate, resultRelInfo, tuple,
+					ExecARInsertTriggers(estate, resultRelInfo, slot,
 										 recheckIndexes, cstate->transition_capture);
 
 					list_free(recheckIndexes);
@@ -2891,7 +2882,7 @@ CopyFrom(CopyState cstate)
 	 * indexes since those use WAL anyway)
 	 */
 	if (hi_options & HEAP_INSERT_SKIP_WAL)
-		heap_sync(cstate->rel);
+		storage_sync(cstate->rel);
 
 	return processed;
 }
@@ -2924,12 +2915,12 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
 	 * before calling it.
 	 */
 	oldcontext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
-	heap_multi_insert(cstate->rel,
-					  bufferedTuples,
-					  nBufferedTuples,
-					  mycid,
-					  hi_options,
-					  bistate);
+	storage_multi_insert(cstate->rel,
+						 bufferedTuples,
+						 nBufferedTuples,
+						 mycid,
+						 hi_options,
+						 bistate);
 	MemoryContextSwitchTo(oldcontext);
 
 	/*
@@ -2945,10 +2936,9 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
 			cstate->cur_lineno = firstBufferedLineNo + i;
 			ExecStoreTuple(bufferedTuples[i], myslot, InvalidBuffer, false);
 			recheckIndexes =
-				ExecInsertIndexTuples(myslot, &(bufferedTuples[i]->t_self),
-									  estate, false, NULL, NIL);
+				ExecInsertIndexTuples(myslot, estate, false, NULL, NIL);
 			ExecARInsertTriggers(estate, resultRelInfo,
-								 bufferedTuples[i],
+								 myslot,
 								 recheckIndexes, cstate->transition_capture);
 			list_free(recheckIndexes);
 		}
@@ -2965,8 +2955,9 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
 		for (i = 0; i < nBufferedTuples; i++)
 		{
 			cstate->cur_lineno = firstBufferedLineNo + i;
+			ExecStoreTuple(bufferedTuples[i], myslot, InvalidBuffer, false);
 			ExecARInsertTriggers(estate, resultRelInfo,
-								 bufferedTuples[i],
+								 myslot,
 								 NIL, cstate->transition_capture);
 		}
 	}
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index ff2b7b75e9..f39551ac83 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -26,6 +26,7 @@
 
 #include "access/reloptions.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "access/xlog.h"
@@ -582,25 +583,28 @@ static bool
 intorel_receive(TupleTableSlot *slot, DestReceiver *self)
 {
 	DR_intorel *myState = (DR_intorel *) self;
-	HeapTuple	tuple;
 
 	/*
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
 	 */
-	tuple = ExecHeapifySlot(slot);
+	ExecMaterializeSlot(slot);
 
 	/*
 	 * force assignment of new OID (see comments in ExecInsert)
 	 */
 	if (myState->rel->rd_rel->relhasoids)
-		HeapTupleSetOid(tuple, InvalidOid);
-
-	heap_insert(myState->rel,
-				tuple,
-				myState->output_cid,
-				myState->hi_options,
-				myState->bistate);
+		slot->tts_tupleOid = InvalidOid;
+
+	storage_insert(myState->rel,
+				   slot,
+				   myState->output_cid,
+				   myState->hi_options,
+				   myState->bistate,
+				   NULL,
+				   NULL,
+				   NIL,
+				   NULL);
 
 	/* We know this is a newly created relation, so there are no indexes */
 
@@ -619,7 +623,7 @@ intorel_shutdown(DestReceiver *self)
 
 	/* If we skipped using WAL, must heap_sync before commit */
 	if (myState->hi_options & HEAP_INSERT_SKIP_WAL)
-		heap_sync(myState->rel);
+		storage_sync(myState->rel);
 
 	/* close rel, but keep lock until commit */
 	heap_close(myState->rel, NoLock);
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 467695160a..b681f4ef5d 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -16,6 +16,7 @@
 
 #include "access/htup_details.h"
 #include "access/multixact.h"
+#include "access/storageam.h"
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/catalog.h"
@@ -491,19 +492,22 @@ static bool
 transientrel_receive(TupleTableSlot *slot, DestReceiver *self)
 {
 	DR_transientrel *myState = (DR_transientrel *) self;
-	HeapTuple	tuple;
 
 	/*
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
 	 */
-	tuple = ExecHeapifySlot(slot);
-
-	heap_insert(myState->transientrel,
-				tuple,
-				myState->output_cid,
-				myState->hi_options,
-				myState->bistate);
+	ExecMaterializeSlot(slot);
+
+	storage_insert(myState->transientrel,
+				   slot,
+				   myState->output_cid,
+				   myState->hi_options,
+				   myState->bistate,
+				   NULL,
+				   NULL,
+				   NIL,
+				   NULL);
 
 	/* We know this is a newly created relation, so there are no indexes */
 
@@ -522,7 +526,7 @@ transientrel_shutdown(DestReceiver *self)
 
 	/* If we skipped using WAL, must heap_sync before commit */
 	if (myState->hi_options & HEAP_INSERT_SKIP_WAL)
-		heap_sync(myState->transientrel);
+		storage_sync(myState->transientrel);
 
 	/* close transientrel, but keep lock until commit */
 	heap_close(myState->transientrel, NoLock);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 62cf81e95a..9722d92849 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -19,6 +19,7 @@
 #include "access/multixact.h"
 #include "access/reloptions.h"
 #include "access/relscan.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/tupconvert.h"
 #include "access/xact.h"
@@ -4663,7 +4664,8 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 
 			/* Write the tuple out to the new relation */
 			if (newrel)
-				heap_insert(newrel, tuple, mycid, hi_options, bistate);
+				storage_insert(newrel, newslot, mycid, hi_options, bistate,
+							   NULL, NULL, NIL, NULL);
 
 			ResetExprContext(econtext);
 
@@ -4687,7 +4689,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 
 		/* If we skipped writing WAL, then we need to sync the heap. */
 		if (hi_options & HEAP_INSERT_SKIP_WAL)
-			heap_sync(newrel);
+			storage_sync(newrel);
 
 		heap_close(newrel, NoLock);
 	}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 502f1dee1f..ca413df263 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -15,6 +15,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/htup_details.h"
 #include "access/xact.h"
@@ -2352,17 +2353,21 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 
 void
 ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
-					 HeapTuple trigtuple, List *recheckIndexes,
+					 TupleTableSlot *slot, List *recheckIndexes,
 					 TransitionCaptureState *transition_capture)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 
 	if ((trigdesc && trigdesc->trig_insert_after_row) ||
 		(transition_capture && transition_capture->tcs_insert_new_table))
+	{
+		HeapTuple	trigtuple = ExecHeapifySlot(slot);
+
 		AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT,
 							  true, NULL, trigtuple,
 							  recheckIndexes, NULL,
 							  transition_capture);
+	}
 }
 
 TupleTableSlot *
@@ -3012,9 +3017,10 @@ GetTupleForTrigger(EState *estate,
 				   TupleTableSlot **newSlot)
 {
 	Relation	relation = relinfo->ri_RelationDesc;
-	HeapTupleData tuple;
+	StorageTuple tuple;
 	HeapTuple	result;
 	Buffer		buffer;
+	tuple_data	t_data;
 
 	if (newSlot != NULL)
 	{
@@ -3030,11 +3036,11 @@ GetTupleForTrigger(EState *estate,
 		 * lock tuple for update
 		 */
 ltrmark:;
-		tuple.t_self = *tid;
-		test = heap_lock_tuple(relation, &tuple,
-							   estate->es_output_cid,
-							   lockmode, LockWaitBlock,
-							   false, &buffer, &hufd);
+		test = storage_lock_tuple(relation, tid, &tuple,
+								  estate->es_output_cid,
+								  lockmode, LockWaitBlock,
+								  false, &buffer, &hufd);
+		result = tuple;
 		switch (test)
 		{
 			case HeapTupleSelfUpdated:
@@ -3066,7 +3072,8 @@ ltrmark:;
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("could not serialize access due to concurrent update")));
-				if (!ItemPointerEquals(&hufd.ctid, &tuple.t_self))
+				t_data = relation->rd_stamroutine->get_tuple_data(tuple, TID);
+				if (!ItemPointerEquals(&hufd.ctid, &(t_data.tid)))
 				{
 					/* it was updated, so look at the updated version */
 					TupleTableSlot *epqslot;
@@ -3112,6 +3119,7 @@ ltrmark:;
 	{
 		Page		page;
 		ItemId		lp;
+		HeapTupleData tupledata;
 
 		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
 
@@ -3130,17 +3138,17 @@ ltrmark:;
 
 		Assert(ItemIdIsNormal(lp));
 
-		tuple.t_data = (HeapTupleHeader) PageGetItem(page, lp);
-		tuple.t_len = ItemIdGetLength(lp);
-		tuple.t_self = *tid;
-		tuple.t_tableOid = RelationGetRelid(relation);
+		tupledata.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+		tupledata.t_len = ItemIdGetLength(lp);
+		tupledata.t_self = *tid;
+		tupledata.t_tableOid = RelationGetRelid(relation);
 
 		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+
+		result = heap_copytuple(&tupledata);
 	}
 
-	result = heap_copytuple(&tuple);
 	ReleaseBuffer(buffer);
-
 	return result;
 }
 
@@ -3946,8 +3954,8 @@ AfterTriggerExecute(AfterTriggerEvent event,
 	AfterTriggerShared evtshared = GetTriggerSharedData(event);
 	Oid			tgoid = evtshared->ats_tgoid;
 	TriggerData LocTriggerData;
-	HeapTupleData tuple1;
-	HeapTupleData tuple2;
+	StorageTuple tuple1;
+	StorageTuple tuple2;
 	HeapTuple	rettuple;
 	Buffer		buffer1 = InvalidBuffer;
 	Buffer		buffer2 = InvalidBuffer;
@@ -4020,10 +4028,9 @@ AfterTriggerExecute(AfterTriggerEvent event,
 		default:
 			if (ItemPointerIsValid(&(event->ate_ctid1)))
 			{
-				ItemPointerCopy(&(event->ate_ctid1), &(tuple1.t_self));
-				if (!heap_fetch(rel, SnapshotAny, &tuple1, &buffer1, false, NULL))
+				if (!storage_fetch(rel, &(event->ate_ctid1), SnapshotAny, &tuple1, &buffer1, false, NULL))
 					elog(ERROR, "failed to fetch tuple1 for AFTER trigger");
-				LocTriggerData.tg_trigtuple = &tuple1;
+				LocTriggerData.tg_trigtuple = tuple1;
 				LocTriggerData.tg_trigtuplebuf = buffer1;
 			}
 			else
@@ -4037,10 +4044,9 @@ AfterTriggerExecute(AfterTriggerEvent event,
 				AFTER_TRIGGER_2CTID &&
 				ItemPointerIsValid(&(event->ate_ctid2)))
 			{
-				ItemPointerCopy(&(event->ate_ctid2), &(tuple2.t_self));
-				if (!heap_fetch(rel, SnapshotAny, &tuple2, &buffer2, false, NULL))
+				if (!storage_fetch(rel, &(event->ate_ctid2), SnapshotAny, &tuple2, &buffer2, false, NULL))
 					elog(ERROR, "failed to fetch tuple2 for AFTER trigger");
-				LocTriggerData.tg_newtuple = &tuple2;
+				LocTriggerData.tg_newtuple = tuple2;
 				LocTriggerData.tg_newtuplebuf = buffer2;
 			}
 			else
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 62e51f1ef3..1038957c59 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -269,12 +269,12 @@ ExecCloseIndices(ResultRelInfo *resultRelInfo)
  */
 List *
 ExecInsertIndexTuples(TupleTableSlot *slot,
-					  ItemPointer tupleid,
 					  EState *estate,
 					  bool noDupErr,
 					  bool *specConflict,
 					  List *arbiterIndexes)
 {
+	ItemPointer tupleid = &slot->tts_tid;
 	List	   *result = NIL;
 	ResultRelInfo *resultRelInfo;
 	int			i;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d8bc5028e8..db9196924b 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -38,6 +38,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -1894,7 +1895,7 @@ ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
 		/* See the comment above. */
 		if (resultRelInfo->ri_PartitionRoot)
 		{
-			HeapTuple	tuple = ExecFetchSlotTuple(slot);
+			StorageTuple tuple = ExecFetchSlotTuple(slot);
 			TupleDesc	old_tupdesc = RelationGetDescr(rel);
 			TupleConversionMap *map;
 
@@ -1974,7 +1975,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				 */
 				if (resultRelInfo->ri_PartitionRoot)
 				{
-					HeapTuple	tuple = ExecFetchSlotTuple(slot);
+					StorageTuple tuple = ExecFetchSlotTuple(slot);
 					TupleConversionMap *map;
 
 					rel = resultRelInfo->ri_PartitionRoot;
@@ -2021,7 +2022,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 			/* See the comment above. */
 			if (resultRelInfo->ri_PartitionRoot)
 			{
-				HeapTuple	tuple = ExecFetchSlotTuple(slot);
+				StorageTuple tuple = ExecFetchSlotTuple(slot);
 				TupleDesc	old_tupdesc = RelationGetDescr(rel);
 				TupleConversionMap *map;
 
@@ -2480,7 +2481,8 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
 			 ItemPointer tid, TransactionId priorXmax)
 {
 	TupleTableSlot *slot;
-	HeapTuple	copyTuple;
+	StorageTuple copyTuple;
+	tuple_data	t_data;
 
 	Assert(rti > 0);
 
@@ -2497,7 +2499,9 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
 	 * For UPDATE/DELETE we have to return tid of actual row we're executing
 	 * PQ for.
 	 */
-	*tid = copyTuple->t_self;
+
+	t_data = storage_tuple_get_data(relation, copyTuple, TID);
+	*tid = t_data.tid;
 
 	/*
 	 * Need to run a recheck subquery.  Initialize or reinitialize EPQ state.
@@ -2528,7 +2532,7 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
 	 * is to guard against early re-use of the EPQ query.
 	 */
 	if (!TupIsNull(slot))
-		(void) ExecMaterializeSlot(slot);
+		ExecMaterializeSlot(slot);
 
 	/*
 	 * Clear out the test tuple.  This is needed in case the EPQ query is
@@ -2561,14 +2565,14 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
  * Note: properly, lockmode should be declared as enum LockTupleMode,
  * but we use "int" to avoid having to include heapam.h in executor.h.
  */
-HeapTuple
+StorageTuple
 EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 				  LockWaitPolicy wait_policy,
 				  ItemPointer tid, TransactionId priorXmax)
 {
-	HeapTuple	copyTuple = NULL;
-	HeapTupleData tuple;
+	StorageTuple tuple = NULL;
 	SnapshotData SnapshotDirty;
+	tuple_data	t_data;
 
 	/*
 	 * fetch target tuple
@@ -2576,12 +2580,12 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 	 * Loop here to deal with updated or busy tuples
 	 */
 	InitDirtySnapshot(SnapshotDirty);
-	tuple.t_self = *tid;
 	for (;;)
 	{
 		Buffer		buffer;
+		ItemPointerData ctid;
 
-		if (heap_fetch(relation, &SnapshotDirty, &tuple, &buffer, true, NULL))
+		if (storage_fetch(relation, tid, &SnapshotDirty, &tuple, &buffer, true, NULL))
 		{
 			HTSU_Result test;
 			HeapUpdateFailureData hufd;
@@ -2595,7 +2599,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 			 * 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),
+			if (!TransactionIdEquals(HeapTupleHeaderGetXmin(((HeapTuple) tuple)->t_data),
 									 priorXmax))
 			{
 				ReleaseBuffer(buffer);
@@ -2617,7 +2621,8 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 				{
 					case LockWaitBlock:
 						XactLockTableWait(SnapshotDirty.xmax,
-										  relation, &tuple.t_self,
+										  relation,
+										  tid,
 										  XLTW_FetchUpdated);
 						break;
 					case LockWaitSkip:
@@ -2646,20 +2651,23 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 			 * that priorXmax == xmin, so we can test that variable instead of
 			 * doing HeapTupleHeaderGetXmin again.
 			 */
-			if (TransactionIdIsCurrentTransactionId(priorXmax) &&
-				HeapTupleHeaderGetCmin(tuple.t_data) >= estate->es_output_cid)
+			if (TransactionIdIsCurrentTransactionId(priorXmax))
 			{
-				ReleaseBuffer(buffer);
-				return NULL;
+				t_data = storage_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 = heap_lock_tuple(relation, &tuple,
-								   estate->es_output_cid,
-								   lockmode, wait_policy,
-								   false, &buffer, &hufd);
+			test = storage_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);
 
@@ -2695,12 +2703,15 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 								(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 								 errmsg("could not serialize access due to concurrent update")));
 
+#if 0 //hari
 					/* Should not encounter speculative tuple on recheck */
 					Assert(!HeapTupleHeaderIsSpeculative(tuple.t_data));
-					if (!ItemPointerEquals(&hufd.ctid, &tuple.t_self))
+#endif
+					t_data = storage_tuple_get_data(relation, tuple, TID);
+					if (!ItemPointerEquals(&hufd.ctid, &t_data.tid))
 					{
 						/* it was updated, so look at the updated version */
-						tuple.t_self = hufd.ctid;
+						*tid = hufd.ctid;
 						/* updated row should have xmin matching this xmax */
 						priorXmax = hufd.xmax;
 						continue;
@@ -2722,10 +2733,6 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 					return NULL;	/* keep compiler quiet */
 			}
 
-			/*
-			 * We got tuple - now copy it for use by recheck query.
-			 */
-			copyTuple = heap_copytuple(&tuple);
 			ReleaseBuffer(buffer);
 			break;
 		}
@@ -2734,7 +2741,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 		 * 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)
+		if (tuple == NULL)
 		{
 			ReleaseBuffer(buffer);
 			return NULL;
@@ -2743,7 +2750,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 		/*
 		 * As above, if xmin isn't what we're expecting, do nothing.
 		 */
-		if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple.t_data),
+		if (!TransactionIdEquals(HeapTupleHeaderGetXmin(((HeapTuple) tuple)->t_data),
 								 priorXmax))
 		{
 			ReleaseBuffer(buffer);
@@ -2762,7 +2769,9 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 		 * 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))
+		t_data = storage_tuple_get_data(relation, tuple, CTID);
+		ctid = t_data.tid;
+		if (ItemPointerEquals(tid, &ctid))
 		{
 			/* deleted, so forget about it */
 			ReleaseBuffer(buffer);
@@ -2770,17 +2779,19 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 		}
 
 		/* updated, so look at the updated row */
-		tuple.t_self = tuple.t_data->t_ctid;
+		*tid = ctid;
+
 		/* updated row should have xmin matching this xmax */
-		priorXmax = HeapTupleHeaderGetUpdateXid(tuple.t_data);
+		t_data = storage_tuple_get_data(relation, tuple, UPDATED_XID);
+		priorXmax = t_data.xid;
 		ReleaseBuffer(buffer);
 		/* loop back to fetch next in chain */
 	}
 
 	/*
-	 * Return the copied tuple
+	 * Return the tuple
 	 */
-	return copyTuple;
+	return tuple;
 }
 
 /*
@@ -2826,7 +2837,7 @@ EvalPlanQualSetPlan(EPQState *epqstate, Plan *subplan, List *auxrowmarks)
  * NB: passed tuple must be palloc'd; it may get freed later
  */
 void
-EvalPlanQualSetTuple(EPQState *epqstate, Index rti, HeapTuple tuple)
+EvalPlanQualSetTuple(EPQState *epqstate, Index rti, StorageTuple tuple)
 {
 	EState	   *estate = epqstate->estate;
 
@@ -2845,7 +2856,7 @@ EvalPlanQualSetTuple(EPQState *epqstate, Index rti, HeapTuple tuple)
 /*
  * Fetch back the current test tuple (if any) for the specified RTI
  */
-HeapTuple
+StorageTuple
 EvalPlanQualGetTuple(EPQState *epqstate, Index rti)
 {
 	EState	   *estate = epqstate->estate;
@@ -2873,7 +2884,7 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 		ExecRowMark *erm = aerm->rowmark;
 		Datum		datum;
 		bool		isNull;
-		HeapTupleData tuple;
+		StorageTuple tuple;
 
 		if (RowMarkRequiresRowShareLock(erm->markType))
 			elog(ERROR, "EvalPlanQual doesn't support locking rowmarks");
@@ -2904,8 +2915,6 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 
 		if (erm->markType == ROW_MARK_REFERENCE)
 		{
-			HeapTuple	copyTuple;
-
 			Assert(erm->relation != NULL);
 
 			/* fetch the tuple's ctid */
@@ -2929,11 +2938,11 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 							 errmsg("cannot lock rows in foreign table \"%s\"",
 									RelationGetRelationName(erm->relation))));
-				copyTuple = fdwroutine->RefetchForeignRow(epqstate->estate,
-														  erm,
-														  datum,
-														  &updated);
-				if (copyTuple == NULL)
+				tuple = fdwroutine->RefetchForeignRow(epqstate->estate,
+													  erm,
+													  datum,
+													  &updated);
+				if (tuple == NULL)
 					elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck");
 
 				/*
@@ -2947,23 +2956,18 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 				/* ordinary table, fetch the tuple */
 				Buffer		buffer;
 
-				tuple.t_self = *((ItemPointer) DatumGetPointer(datum));
-				if (!heap_fetch(erm->relation, SnapshotAny, &tuple, &buffer,
-								false, NULL))
+				if (!storage_fetch(erm->relation, (ItemPointer) DatumGetPointer(datum), SnapshotAny, &tuple, &buffer,
+								   false, NULL))
 					elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck");
 
-				/* successful, copy tuple */
-				copyTuple = heap_copytuple(&tuple);
 				ReleaseBuffer(buffer);
 			}
 
 			/* store tuple */
-			EvalPlanQualSetTuple(epqstate, erm->rti, copyTuple);
+			EvalPlanQualSetTuple(epqstate, erm->rti, tuple);
 		}
 		else
 		{
-			HeapTupleHeader td;
-
 			Assert(erm->markType == ROW_MARK_COPY);
 
 			/* fetch the whole-row Var for the relation */
@@ -2973,19 +2977,12 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 			/* non-locked rels could be on the inside of outer joins */
 			if (isNull)
 				continue;
-			td = DatumGetHeapTupleHeader(datum);
-
-			/* build a temporary HeapTuple control structure */
-			tuple.t_len = HeapTupleHeaderGetDatumLength(td);
-			tuple.t_data = td;
-			/* relation might be a foreign table, if so provide tableoid */
-			tuple.t_tableOid = erm->relid;
-			/* also copy t_ctid in case there's valid data there */
-			tuple.t_self = td->t_ctid;
-
-			/* copy and store tuple */
-			EvalPlanQualSetTuple(epqstate, erm->rti,
-								 heap_copytuple(&tuple));
+
+			tuple = storage_tuple_by_datum(erm->relation, datum, erm->relid);
+
+			/* store tuple */
+			EvalPlanQualSetTuple(epqstate, erm->rti, tuple);
+
 		}
 	}
 }
@@ -3154,8 +3151,8 @@ EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
 	}
 	else
 	{
-		estate->es_epqTuple = (HeapTuple *)
-			palloc0(rtsize * sizeof(HeapTuple));
+		estate->es_epqTuple = (StorageTuple *)
+			palloc0(rtsize * sizeof(StorageTuple));
 		estate->es_epqTupleSet = (bool *)
 			palloc0(rtsize * sizeof(bool));
 	}
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 864ca769e9..c89631fe00 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -15,6 +15,7 @@
 #include "postgres.h"
 
 #include "access/relscan.h"
+#include "access/storageam.h"
 #include "access/transam.h"
 #include "access/xact.h"
 #include "commands/trigger.h"
@@ -169,19 +170,19 @@ retry:
 		Buffer		buf;
 		HeapUpdateFailureData hufd;
 		HTSU_Result res;
-		HeapTupleData locktup;
-
-		ItemPointerCopy(&outslot->tts_tid, &locktup.t_self);
+		StorageTuple locktup;
 
 		PushActiveSnapshot(GetLatestSnapshot());
 
-		res = heap_lock_tuple(rel, &locktup, GetCurrentCommandId(false),
-							  lockmode,
-							  LockWaitBlock,
-							  false /* don't follow updates */ ,
-							  &buf, &hufd);
+		res = storage_lock_tuple(rel, &(outslot->tts_tid), &locktup, GetCurrentCommandId(false),
+								 lockmode,
+								 LockWaitBlock,
+								 false /* don't follow updates */ ,
+								 &buf, &hufd);
 		/* the tuple slot already has the buffer pinned */
-		ReleaseBuffer(buf);
+		if (BufferIsValid(buf))
+			ReleaseBuffer(buf);
+		pfree(locktup);
 
 		PopActiveSnapshot();
 
@@ -277,19 +278,20 @@ retry:
 		Buffer		buf;
 		HeapUpdateFailureData hufd;
 		HTSU_Result res;
-		HeapTupleData locktup;
-
-		ItemPointerCopy(&outslot->tts_tid, &locktup.t_self);
+		StorageTuple locktup;
 
 		PushActiveSnapshot(GetLatestSnapshot());
 
-		res = heap_lock_tuple(rel, &locktup, GetCurrentCommandId(false),
-							  lockmode,
-							  LockWaitBlock,
-							  false /* don't follow updates */ ,
-							  &buf, &hufd);
+		res = storage_lock_tuple(rel, &(outslot->tts_tid), &locktup, GetCurrentCommandId(false),
+								 lockmode,
+								 LockWaitBlock,
+								 false /* don't follow updates */ ,
+								 &buf, &hufd);
 		/* the tuple slot already has the buffer pinned */
-		ReleaseBuffer(buf);
+		if (BufferIsValid(buf))
+			ReleaseBuffer(buf);
+
+		pfree(locktup);
 
 		PopActiveSnapshot();
 
@@ -327,7 +329,6 @@ void
 ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 {
 	bool		skip_tuple = false;
-	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 
@@ -354,19 +355,12 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 		if (rel->rd_att->constr)
 			ExecConstraints(resultRelInfo, slot, estate);
 
-		/* Store the slot into tuple that we can inspect. */
-		tuple = ExecHeapifySlot(slot);
-
-		/* OK, store the tuple and create index entries for it */
-		simple_heap_insert(rel, tuple);
-
-		if (resultRelInfo->ri_NumIndices > 0)
-			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
-												   estate, false, NULL,
-												   NIL);
+		storage_insert(resultRelInfo->ri_RelationDesc, slot,
+					   GetCurrentCommandId(true), 0, NULL,
+					   ExecInsertIndexTuples, estate, NIL, &recheckIndexes);
 
 		/* AFTER ROW INSERT Triggers */
-		ExecARInsertTriggers(estate, resultRelInfo, tuple,
+		ExecARInsertTriggers(estate, resultRelInfo, slot,
 							 recheckIndexes, NULL);
 
 		/*
@@ -390,7 +384,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 						 TupleTableSlot *searchslot, TupleTableSlot *slot)
 {
 	bool		skip_tuple = false;
-	HeapTuple	tuple;
+	StorageTuple tuple;
 	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	ItemPointer tid = &(searchslot->tts_tid);
@@ -415,22 +409,18 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 	if (!skip_tuple)
 	{
 		List	   *recheckIndexes = NIL;
+		HeapUpdateFailureData hufd;
+		LockTupleMode lockmode;
+		InsertIndexTuples IndexFunc = ExecInsertIndexTuples;
 
 		/* Check the constraints of the tuple */
 		if (rel->rd_att->constr)
 			ExecConstraints(resultRelInfo, slot, estate);
 
-		/* Store the slot into tuple that we can write. */
-		tuple = ExecHeapifySlot(slot);
+		storage_update(rel, tid, slot, estate, GetCurrentCommandId(true), InvalidSnapshot,
+					   true, &hufd, &lockmode, IndexFunc, &recheckIndexes);
 
-		/* OK, update the tuple and index entries for it */
-		simple_heap_update(rel, tid, tuple);
-
-		if (resultRelInfo->ri_NumIndices > 0 &&
-			!HeapTupleIsHeapOnly(tuple))
-			recheckIndexes = ExecInsertIndexTuples(slot, tid,
-												   estate, false, NULL,
-												   NIL);
+		tuple = ExecHeapifySlot(slot);
 
 		/* AFTER ROW UPDATE Triggers */
 		ExecARUpdateTriggers(estate, resultRelInfo,
diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c
index 7961b4be6a..c8327ee1ce 100644
--- a/src/backend/executor/nodeLockRows.c
+++ b/src/backend/executor/nodeLockRows.c
@@ -22,6 +22,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/xact.h"
 #include "executor/executor.h"
 #include "executor/nodeLockRows.h"
@@ -74,18 +75,20 @@ lnext:
 	{
 		ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc);
 		ExecRowMark *erm = aerm->rowmark;
-		HeapTuple  *testTuple;
+		StorageTuple *testTuple;
 		Datum		datum;
 		bool		isNull;
-		HeapTupleData tuple;
+		StorageTuple tuple;
 		Buffer		buffer;
 		HeapUpdateFailureData hufd;
 		LockTupleMode lockmode;
 		HTSU_Result test;
-		HeapTuple	copyTuple;
+		StorageTuple copyTuple;
+		ItemPointerData tid;
+		tuple_data	t_data;
 
 		/* clear any leftover test tuple for this rel */
-		testTuple = &(node->lr_curtuples[erm->rti - 1]);
+		testTuple = (StorageTuple) (&(node->lr_curtuples[erm->rti - 1]));
 		if (*testTuple != NULL)
 			heap_freetuple(*testTuple);
 		*testTuple = NULL;
@@ -159,7 +162,7 @@ lnext:
 		}
 
 		/* okay, try to lock the tuple */
-		tuple.t_self = *((ItemPointer) DatumGetPointer(datum));
+		tid = *((ItemPointer) DatumGetPointer(datum));
 		switch (erm->markType)
 		{
 			case ROW_MARK_EXCLUSIVE:
@@ -180,11 +183,13 @@ lnext:
 				break;
 		}
 
-		test = heap_lock_tuple(erm->relation, &tuple,
-							   estate->es_output_cid,
-							   lockmode, erm->waitPolicy, true,
-							   &buffer, &hufd);
-		ReleaseBuffer(buffer);
+		test = storage_lock_tuple(erm->relation, &tid, &tuple,
+								  estate->es_output_cid,
+								  lockmode, erm->waitPolicy, true,
+								  &buffer, &hufd);
+		if (BufferIsValid(buffer))
+			ReleaseBuffer(buffer);
+
 		switch (test)
 		{
 			case HeapTupleWouldBlock:
@@ -218,7 +223,8 @@ lnext:
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("could not serialize access due to concurrent update")));
-				if (ItemPointerEquals(&hufd.ctid, &tuple.t_self))
+				t_data = erm->relation->rd_stamroutine->get_tuple_data(tuple, TID);
+				if (ItemPointerEquals(&hufd.ctid, &(t_data.tid)))
 				{
 					/* Tuple was deleted, so don't return it */
 					goto lnext;
@@ -238,7 +244,8 @@ lnext:
 					goto lnext;
 				}
 				/* remember the actually locked tuple's TID */
-				tuple.t_self = copyTuple->t_self;
+				t_data = erm->relation->rd_stamroutine->get_tuple_data(copyTuple, TID);
+				tid = t_data.tid;
 
 				/* Save locked tuple for EvalPlanQual testing below */
 				*testTuple = copyTuple;
@@ -258,7 +265,7 @@ lnext:
 		}
 
 		/* Remember locked tuple's TID for EPQ testing and WHERE CURRENT OF */
-		erm->curCtid = tuple.t_self;
+		erm->curCtid = tid;
 	}
 
 	/*
@@ -280,7 +287,7 @@ lnext:
 		{
 			ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc);
 			ExecRowMark *erm = aerm->rowmark;
-			HeapTupleData tuple;
+			StorageTuple tuple;
 			Buffer		buffer;
 
 			/* skip non-active child tables, but clear their test tuples */
@@ -308,14 +315,12 @@ lnext:
 			Assert(ItemPointerIsValid(&(erm->curCtid)));
 
 			/* okay, fetch the tuple */
-			tuple.t_self = erm->curCtid;
-			if (!heap_fetch(erm->relation, SnapshotAny, &tuple, &buffer,
-							false, NULL))
+			if (!storage_fetch(erm->relation, &erm->curCtid, SnapshotAny, &tuple, &buffer,
+							   false, NULL))
 				elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck");
 
 			/* successful, copy and store tuple */
-			EvalPlanQualSetTuple(&node->lr_epqstate, erm->rti,
-								 heap_copytuple(&tuple));
+			EvalPlanQualSetTuple(&node->lr_epqstate, erm->rti, tuple);
 			ReleaseBuffer(buffer);
 		}
 
@@ -394,8 +399,8 @@ ExecInitLockRows(LockRows *node, EState *estate, int eflags)
 	 * Create workspace in which we can remember per-RTE locked tuples
 	 */
 	lrstate->lr_ntables = list_length(estate->es_range_table);
-	lrstate->lr_curtuples = (HeapTuple *)
-		palloc0(lrstate->lr_ntables * sizeof(HeapTuple));
+	lrstate->lr_curtuples = (StorageTuple *)
+		palloc0(lrstate->lr_ntables * sizeof(StorageTuple));
 
 	/*
 	 * Locate the ExecRowMark(s) that this node is responsible for, and
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 9b62ed0a3a..c8cc1e0f12 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -38,7 +38,10 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/storageam.h"
+#include "access/storageam.h"
 #include "access/xact.h"
+#include "catalog/pg_am.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -165,15 +168,13 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
 		econtext->ecxt_scantuple = tupleSlot;
 	else
 	{
-		HeapTuple	tuple;
-
 		/*
 		 * RETURNING expressions might reference the tableoid column, so
 		 * initialize t_tableOid before evaluating them.
 		 */
 		Assert(!TupIsNull(econtext->ecxt_scantuple));
-		tuple = ExecHeapifySlot(econtext->ecxt_scantuple);
-		tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+		ExecSlotUpdateTupleTableoid(econtext->ecxt_scantuple,
+									RelationGetRelid(resultRelInfo->ri_RelationDesc));
 	}
 	econtext->ecxt_outertuple = planSlot;
 
@@ -192,7 +193,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
 static void
 ExecCheckHeapTupleVisible(EState *estate,
 						  Relation rel,
-						  HeapTuple tuple,
+						  StorageTuple tuple,
 						  Buffer buffer)
 {
 	if (!IsolationUsesXactSnapshot())
@@ -205,13 +206,15 @@ ExecCheckHeapTupleVisible(EState *estate,
 	LockBuffer(buffer, BUFFER_LOCK_SHARE);
 	if (!HeapTupleSatisfiesVisibility(rel->rd_stamroutine, tuple, estate->es_snapshot, buffer))
 	{
+		tuple_data	t_data = storage_tuple_get_data(rel, tuple, XMIN);
+
 		/*
 		 * We should not raise a serialization failure if the conflict is
 		 * against a tuple inserted by our own transaction, even if it's not
 		 * visible to our snapshot.  (This would happen, for example, if
 		 * conflicting keys are proposed for insertion in a single command.)
 		 */
-		if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple->t_data)))
+		if (!TransactionIdIsCurrentTransactionId(t_data.xid))
 			ereport(ERROR,
 					(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 					 errmsg("could not serialize access due to concurrent update")));
@@ -227,19 +230,20 @@ ExecCheckTIDVisible(EState *estate,
 					ResultRelInfo *relinfo,
 					ItemPointer tid)
 {
-	Relation	rel = relinfo->ri_RelationDesc;
 	Buffer		buffer;
-	HeapTupleData tuple;
+	Relation	rel = relinfo->ri_RelationDesc;
+	StorageTuple tuple;
 
 	/* Redundantly check isolation level */
 	if (!IsolationUsesXactSnapshot())
 		return;
 
-	tuple.t_self = *tid;
-	if (!heap_fetch(rel, SnapshotAny, &tuple, &buffer, false, NULL))
+	if (!storage_fetch(rel, tid, SnapshotAny, &tuple, &buffer, false, NULL))
 		elog(ERROR, "failed to fetch conflicting tuple for ON CONFLICT");
-	ExecCheckHeapTupleVisible(estate, rel, &tuple, buffer);
-	ReleaseBuffer(buffer);
+	ExecCheckHeapTupleVisible(estate, rel, tuple, buffer);
+	if (BufferIsValid(buffer))
+		ReleaseBuffer(buffer);
+	pfree(tuple);
 }
 
 /* ----------------------------------------------------------------
@@ -260,7 +264,7 @@ ExecInsert(ModifyTableState *mtstate,
 		   EState *estate,
 		   bool canSetTag)
 {
-	HeapTuple	tuple;
+	StorageTuple tuple;
 	ResultRelInfo *resultRelInfo;
 	ResultRelInfo *saved_resultRelInfo = NULL;
 	Relation	resultRelationDesc;
@@ -268,12 +272,6 @@ ExecInsert(ModifyTableState *mtstate,
 	List	   *recheckIndexes = NIL;
 	TupleTableSlot *result = NULL;
 
-	/*
-	 * get the heap tuple out of the tuple table slot, making sure we have a
-	 * writable copy
-	 */
-	tuple = ExecHeapifySlot(slot);
-
 	/*
 	 * get information on the (current) result relation
 	 */
@@ -285,6 +283,8 @@ ExecInsert(ModifyTableState *mtstate,
 		int			leaf_part_index;
 		TupleConversionMap *map;
 
+		tuple = ExecHeapifySlot(slot);
+
 		/*
 		 * Away we go ... If we end up not finding a partition after all,
 		 * ExecFindPartition() does not return and errors out instead.
@@ -375,19 +375,31 @@ ExecInsert(ModifyTableState *mtstate,
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/*
-	 * If the result relation has OIDs, force the tuple's OID to zero so that
-	 * heap_insert will assign a fresh OID.  Usually the OID already will be
-	 * zero at this point, but there are corner cases where the plan tree can
-	 * return a tuple extracted literally from some table with the same
-	 * rowtype.
+	 * get the heap tuple out of the tuple table slot, making sure we have a
+	 * writable copy  <-- obsolete comment XXX explain what we really do here
+	 *
+	 * Do we really need to do this here?
+	 */
+	ExecMaterializeSlot(slot);
+
+
+	/*
+	 * If the result relation uses heapam and has OIDs, force the tuple's OID
+	 * to zero so that heap_insert will assign a fresh OID.  Usually the OID
+	 * already will be zero at this point, but there are corner cases where
+	 * the plan tree can return a tuple extracted literally from some table
+	 * with the same rowtype.
 	 *
 	 * XXX if we ever wanted to allow users to assign their own OIDs to new
 	 * rows, this'd be the place to do it.  For the moment, we make a point of
 	 * doing this before calling triggers, so that a user-supplied trigger
 	 * could hack the OID if desired.
 	 */
-	if (resultRelationDesc->rd_rel->relhasoids)
-		HeapTupleSetOid(tuple, InvalidOid);
+	if (resultRelationDesc->rd_rel->relam == HEAPAM_STORAGE_AM_OID &&
+		resultRelationDesc->rd_rel->relhasoids)
+	{
+		slot->tts_tupleOid = InvalidOid;
+	}
 
 	/*
 	 * BEFORE ROW INSERT Triggers.
@@ -405,9 +417,6 @@ ExecInsert(ModifyTableState *mtstate,
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
-
-		/* trigger might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW INSERT Triggers */
@@ -419,9 +428,6 @@ ExecInsert(ModifyTableState *mtstate,
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
 
-		/* trigger might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
-
 		newId = InvalidOid;
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
@@ -437,14 +443,12 @@ ExecInsert(ModifyTableState *mtstate,
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
 
-		/* FDW might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
-
 		/*
 		 * AFTER ROW Triggers or RETURNING expressions might reference the
 		 * tableoid column, so initialize t_tableOid before evaluating them.
 		 */
-		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
+		slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
+		ExecSlotUpdateTupleTableoid(slot, slot->tts_tableOid);
 
 		newId = InvalidOid;
 	}
@@ -464,7 +468,8 @@ ExecInsert(ModifyTableState *mtstate,
 		 * Constraints might reference the tableoid column, so initialize
 		 * t_tableOid before evaluating them.
 		 */
-		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
+		slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
+		ExecSlotUpdateTupleTableoid(slot, slot->tts_tableOid);
 
 		/*
 		 * Check any RLS INSERT WITH CHECK policies
@@ -494,7 +499,6 @@ ExecInsert(ModifyTableState *mtstate,
 			/* Perform a speculative insertion. */
 			uint32		specToken;
 			ItemPointerData conflictTid;
-			bool		specConflict;
 
 			/*
 			 * Do a non-conclusive check for conflicts first.
@@ -509,7 +513,7 @@ ExecInsert(ModifyTableState *mtstate,
 			 * speculatively.
 			 */
 	vlock:
-			specConflict = false;
+			slot->tts_specConflict = false;
 			if (!ExecCheckIndexConstraints(slot, estate, &conflictTid,
 										   arbiterIndexes))
 			{
@@ -555,24 +559,17 @@ ExecInsert(ModifyTableState *mtstate,
 			 * waiting for the whole transaction to complete.
 			 */
 			specToken = SpeculativeInsertionLockAcquire(GetCurrentTransactionId());
-			HeapTupleHeaderSetSpeculativeToken(tuple->t_data, specToken);
+			slot->tts_speculativeToken = specToken;
 
 			/* insert the tuple, with the speculative token */
-			newId = heap_insert(resultRelationDesc, tuple,
-								estate->es_output_cid,
-								HEAP_INSERT_SPECULATIVE,
-								NULL);
-
-			/* insert index entries for tuple */
-			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
-												   estate, true, &specConflict,
-												   arbiterIndexes);
-
-			/* adjust the tuple's state accordingly */
-			if (!specConflict)
-				heap_finish_speculative(resultRelationDesc, tuple);
-			else
-				heap_abort_speculative(resultRelationDesc, tuple);
+			newId = storage_insert(resultRelationDesc, slot,
+								   estate->es_output_cid,
+								   HEAP_INSERT_SPECULATIVE,
+								   NULL,
+								   ExecInsertIndexTuples,
+								   estate,
+								   arbiterIndexes,
+								   &recheckIndexes);
 
 			/*
 			 * Wake up anyone waiting for our decision.  They will re-check
@@ -588,7 +585,7 @@ ExecInsert(ModifyTableState *mtstate,
 			 * the pre-check again, which will now find the conflicting tuple
 			 * (unless it aborts before we get there).
 			 */
-			if (specConflict)
+			if (slot->tts_specConflict)
 			{
 				list_free(recheckIndexes);
 				goto vlock;
@@ -600,19 +597,14 @@ ExecInsert(ModifyTableState *mtstate,
 		{
 			/*
 			 * insert the tuple normally.
-			 *
-			 * Note: heap_insert returns the tid (location) of the new tuple
-			 * in the t_self field.
 			 */
-			newId = heap_insert(resultRelationDesc, tuple,
-								estate->es_output_cid,
-								0, NULL);
-
-			/* insert index entries for tuple */
-			if (resultRelInfo->ri_NumIndices > 0)
-				recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
-													   estate, false, NULL,
-													   arbiterIndexes);
+			newId = storage_insert(resultRelationDesc, slot,
+								   estate->es_output_cid,
+								   0, NULL,
+								   ExecInsertIndexTuples,
+								   estate,
+								   arbiterIndexes,
+								   &recheckIndexes);
 		}
 	}
 
@@ -620,11 +612,11 @@ ExecInsert(ModifyTableState *mtstate,
 	{
 		(estate->es_processed)++;
 		estate->es_lastoid = newId;
-		setLastTid(&(tuple->t_self));
+		setLastTid(&(slot->tts_tid));
 	}
 
 	/* AFTER ROW INSERT Triggers */
-	ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes,
+	ExecARInsertTriggers(estate, resultRelInfo, slot, recheckIndexes,
 						 mtstate->mt_transition_capture);
 
 	list_free(recheckIndexes);
@@ -675,7 +667,7 @@ ExecInsert(ModifyTableState *mtstate,
 static TupleTableSlot *
 ExecDelete(ModifyTableState *mtstate,
 		   ItemPointer tupleid,
-		   HeapTuple oldtuple,
+		   StorageTuple oldtuple,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
 		   EState *estate,
@@ -720,8 +712,6 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
-		HeapTuple	tuple;
-
 		/*
 		 * delete from foreign table: let the FDW do it
 		 *
@@ -747,8 +737,10 @@ ExecDelete(ModifyTableState *mtstate,
 		 */
 		if (slot->tts_isempty)
 			ExecStoreAllNullTuple(slot);
-		tuple = ExecHeapifySlot(slot);
-		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
+
+		ExecMaterializeSlot(slot);
+
+		slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
 	}
 	else
 	{
@@ -762,11 +754,12 @@ ExecDelete(ModifyTableState *mtstate,
 		 * mode transactions.
 		 */
 ldelete:;
-		result = heap_delete(resultRelationDesc, tupleid,
-							 estate->es_output_cid,
-							 estate->es_crosscheck_snapshot,
-							 true /* wait for commit */ ,
-							 &hufd);
+		result = storage_delete(resultRelationDesc, tupleid,
+								estate->es_output_cid,
+								estate->es_crosscheck_snapshot,
+								true /* wait for commit */ ,
+								NULL,
+								&hufd);
 		switch (result)
 		{
 			case HeapTupleSelfUpdated:
@@ -862,7 +855,7 @@ ldelete:;
 		 * gotta fetch it.  We can use the trigger tuple slot.
 		 */
 		TupleTableSlot *rslot;
-		HeapTupleData deltuple;
+		StorageTuple deltuple = NULL;
 		Buffer		delbuffer;
 
 		if (resultRelInfo->ri_FdwRoutine)
@@ -876,20 +869,19 @@ ldelete:;
 			slot = estate->es_trig_tuple_slot;
 			if (oldtuple != NULL)
 			{
-				deltuple = *oldtuple;
+				deltuple = heap_copytuple(oldtuple);
 				delbuffer = InvalidBuffer;
 			}
 			else
 			{
-				deltuple.t_self = *tupleid;
-				if (!heap_fetch(resultRelationDesc, SnapshotAny,
-								&deltuple, &delbuffer, false, NULL))
+				if (!storage_fetch(resultRelationDesc, tupleid, SnapshotAny,
+								   &deltuple, &delbuffer, false, NULL))
 					elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
 			}
 
 			if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
 				ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
-			ExecStoreTuple(&deltuple, slot, InvalidBuffer, false);
+			ExecStoreTuple(deltuple, slot, InvalidBuffer, false);
 		}
 
 		rslot = ExecProcessReturning(resultRelInfo, slot, planSlot);
@@ -898,7 +890,7 @@ ldelete:;
 		 * Before releasing the target tuple again, make sure rslot has a
 		 * local copy of any pass-by-reference values.
 		 */
-		ExecHeapifySlot(rslot);
+		ExecMaterializeSlot(rslot);
 
 		ExecClearTuple(slot);
 		if (BufferIsValid(delbuffer))
@@ -935,14 +927,14 @@ ldelete:;
 static TupleTableSlot *
 ExecUpdate(ModifyTableState *mtstate,
 		   ItemPointer tupleid,
-		   HeapTuple oldtuple,
+		   StorageTuple oldtuple,
 		   TupleTableSlot *slot,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
 		   EState *estate,
 		   bool canSetTag)
 {
-	HeapTuple	tuple;
+	StorageTuple tuple;
 	ResultRelInfo *resultRelInfo;
 	Relation	resultRelationDesc;
 	HTSU_Result result;
@@ -1007,14 +999,14 @@ ExecUpdate(ModifyTableState *mtstate,
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
 
-		/* FDW might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
-
 		/*
 		 * AFTER ROW Triggers or RETURNING expressions might reference the
 		 * tableoid column, so initialize t_tableOid before evaluating them.
 		 */
-		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
+		ExecSlotUpdateTupleTableoid(slot, RelationGetRelid(resultRelationDesc));
+
+		/* FDW might have changed tuple */
+		tuple = ExecHeapifySlot(slot);
 	}
 	else
 	{
@@ -1024,7 +1016,7 @@ ExecUpdate(ModifyTableState *mtstate,
 		 * Constraints might reference the tableoid column, so initialize
 		 * t_tableOid before evaluating them.
 		 */
-		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
+		slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
 
 		/*
 		 * Check any RLS UPDATE WITH CHECK policies
@@ -1060,11 +1052,14 @@ lreplace:;
 		 * needed for referential integrity updates in transaction-snapshot
 		 * mode transactions.
 		 */
-		result = heap_update(resultRelationDesc, tupleid, tuple,
-							 estate->es_output_cid,
-							 estate->es_crosscheck_snapshot,
-							 true /* wait for commit */ ,
-							 &hufd, &lockmode);
+		result = storage_update(resultRelationDesc, tupleid, slot,
+								estate,
+								estate->es_output_cid,
+								estate->es_crosscheck_snapshot,
+								true /* wait for commit */ ,
+								&hufd, &lockmode,
+								ExecInsertIndexTuples,
+								&recheckIndexes);
 		switch (result)
 		{
 			case HeapTupleSelfUpdated:
@@ -1135,26 +1130,6 @@ lreplace:;
 				elog(ERROR, "unrecognized heap_update status: %u", result);
 				return NULL;
 		}
-
-		/*
-		 * Note: instead of having to update the old index tuples associated
-		 * with the heap tuple, all we do is form and insert new index tuples.
-		 * This is because UPDATEs are actually DELETEs and INSERTs, and index
-		 * tuple deletion is done later by VACUUM (see notes in ExecDelete).
-		 * All we do here is insert new index tuples.  -cim 9/27/89
-		 */
-
-		/*
-		 * insert index entries for tuple
-		 *
-		 * Note: heap_update returns the tid (location) of the new tuple in
-		 * the t_self field.
-		 *
-		 * If it's a HOT update, we mustn't insert new index entries.
-		 */
-		if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple))
-			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
-												   estate, false, NULL, NIL);
 	}
 
 	if (canSetTag)
@@ -1212,11 +1187,12 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	ExprContext *econtext = mtstate->ps.ps_ExprContext;
 	Relation	relation = resultRelInfo->ri_RelationDesc;
 	ExprState  *onConflictSetWhere = resultRelInfo->ri_onConflictSetWhere;
-	HeapTupleData tuple;
+	StorageTuple tuple = NULL;
 	HeapUpdateFailureData hufd;
 	LockTupleMode lockmode;
 	HTSU_Result test;
 	Buffer		buffer;
+	tuple_data	t_data;
 
 	/* Determine lock mode to use */
 	lockmode = ExecUpdateLockMode(estate, resultRelInfo);
@@ -1227,10 +1203,8 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	 * previous conclusion that the tuple is conclusively committed is not
 	 * true anymore.
 	 */
-	tuple.t_self = *conflictTid;
-	test = heap_lock_tuple(relation, &tuple, estate->es_output_cid,
-						   lockmode, LockWaitBlock, false, &buffer,
-						   &hufd);
+	test = storage_lock_tuple(relation, conflictTid, &tuple, estate->es_output_cid,
+							  lockmode, LockWaitBlock, false, &buffer, &hufd);
 	switch (test)
 	{
 		case HeapTupleMayBeUpdated:
@@ -1255,7 +1229,8 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 			 * that for SQL MERGE, an exception must be raised in the event of
 			 * an attempt to update the same row twice.
 			 */
-			if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple.t_data)))
+			t_data = storage_tuple_get_data(relation, tuple, XMIN);
+			if (TransactionIdIsCurrentTransactionId(t_data.xid))
 				ereport(ERROR,
 						(errcode(ERRCODE_CARDINALITY_VIOLATION),
 						 errmsg("ON CONFLICT DO UPDATE command cannot affect row a second time"),
@@ -1286,7 +1261,9 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 			 * loop here, as the new version of the row might not conflict
 			 * anymore, or the conflicting tuple has actually been deleted.
 			 */
-			ReleaseBuffer(buffer);
+			if (BufferIsValid(buffer))
+				ReleaseBuffer(buffer);
+			pfree(tuple);
 			return false;
 
 		default:
@@ -1314,10 +1291,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, buffer);
 
 	/* Store target's existing tuple in the state's dedicated slot */
-	ExecStoreTuple(&tuple, mtstate->mt_existing, buffer, false);
+	ExecStoreTuple(tuple, mtstate->mt_existing, buffer, false);
 
 	/*
 	 * Make tuple and any needed join variables available to ExecQual and
@@ -1332,7 +1309,9 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 
 	if (!ExecQual(onConflictSetWhere, econtext))
 	{
-		ReleaseBuffer(buffer);
+		if (BufferIsValid(buffer))
+			ReleaseBuffer(buffer);
+		pfree(tuple);
 		InstrCountFiltered1(&mtstate->ps, 1);
 		return true;			/* done with the tuple */
 	}
@@ -1372,12 +1351,14 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	 */
 
 	/* Execute UPDATE with projection */
-	*returning = ExecUpdate(mtstate, &tuple.t_self, NULL,
+	*returning = ExecUpdate(mtstate, conflictTid, NULL,
 							mtstate->mt_conflproj, planSlot,
 							&mtstate->mt_epqstate, mtstate->ps.state,
 							canSetTag);
 
-	ReleaseBuffer(buffer);
+	if (BufferIsValid(buffer))
+		ReleaseBuffer(buffer);
+	pfree(tuple);
 	return true;
 }
 
@@ -1580,7 +1561,7 @@ ExecModifyTable(PlanState *pstate)
 	ItemPointer tupleid;
 	ItemPointerData tuple_ctid;
 	HeapTupleData oldtupdata;
-	HeapTuple	oldtuple;
+	StorageTuple oldtuple;
 
 	CHECK_FOR_INTERRUPTS();
 
diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c
index f2737bb7ef..766f6f4193 100644
--- a/src/backend/executor/nodeTidscan.c
+++ b/src/backend/executor/nodeTidscan.c
@@ -22,6 +22,7 @@
  */
 #include "postgres.h"
 
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "catalog/pg_type.h"
 #include "executor/execdebug.h"
@@ -306,7 +307,7 @@ TidNext(TidScanState *node)
 	ScanDirection direction;
 	Snapshot	snapshot;
 	Relation	heapRelation;
-	HeapTuple	tuple;
+	StorageTuple tuple;
 	TupleTableSlot *slot;
 	Buffer		buffer = InvalidBuffer;
 	ItemPointerData *tidList;
@@ -331,12 +332,6 @@ TidNext(TidScanState *node)
 	tidList = node->tss_TidList;
 	numTids = node->tss_NumTids;
 
-	/*
-	 * We use node->tss_htup as the tuple pointer; note this can't just be a
-	 * local variable here, as the scan tuple slot will keep a pointer to it.
-	 */
-	tuple = &(node->tss_htup);
-
 	/*
 	 * Initialize or advance scan position, depending on direction.
 	 */
@@ -364,7 +359,7 @@ TidNext(TidScanState *node)
 
 	while (node->tss_TidPtr >= 0 && node->tss_TidPtr < numTids)
 	{
-		tuple->t_self = tidList[node->tss_TidPtr];
+		ItemPointerData tid = tidList[node->tss_TidPtr];
 
 		/*
 		 * For WHERE CURRENT OF, the tuple retrieved from the cursor might
@@ -372,9 +367,9 @@ TidNext(TidScanState *node)
 		 * current according to our snapshot.
 		 */
 		if (node->tss_isCurrentOf)
-			heap_get_latest_tid(heapRelation, snapshot, &tuple->t_self);
+			storage_get_latest_tid(heapRelation, snapshot, &tid);
 
-		if (heap_fetch(heapRelation, snapshot, tuple, &buffer, false, NULL))
+		if (storage_fetch(heapRelation, &tid, snapshot, &tuple, &buffer, false, NULL))
 		{
 			/*
 			 * store the scanned tuple in the scan tuple slot of the scan
@@ -385,14 +380,16 @@ TidNext(TidScanState *node)
 			 */
 			ExecStoreTuple(tuple,	/* tuple to store */
 						   slot,	/* slot to store in */
-						   buffer,	/* buffer associated with tuple  */
-						   false);	/* don't pfree */
+						   InvalidBuffer,	/* buffer associated with tuple  */
+						   true);	/* don't pfree */
 
 			/*
 			 * At this point we have an extra pin on the buffer, because
 			 * ExecStoreTuple incremented the pin count. Drop our local pin.
 			 */
-			ReleaseBuffer(buffer);
+			/* hari */
+			if (BufferIsValid(buffer))
+				ReleaseBuffer(buffer);
 
 			return slot;
 		}
diff --git a/src/backend/utils/adt/tid.c b/src/backend/utils/adt/tid.c
index 5ed5fdaffe..8cf759bc40 100644
--- a/src/backend/utils/adt/tid.c
+++ b/src/backend/utils/adt/tid.c
@@ -21,6 +21,7 @@
 #include <limits.h>
 
 #include "access/heapam.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_type.h"
@@ -352,7 +353,7 @@ currtid_byreloid(PG_FUNCTION_ARGS)
 	ItemPointerCopy(tid, result);
 
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	heap_get_latest_tid(rel, snapshot, result);
+	storage_get_latest_tid(rel, snapshot, result);
 	UnregisterSnapshot(snapshot);
 
 	heap_close(rel, AccessShareLock);
@@ -387,7 +388,7 @@ currtid_byrelname(PG_FUNCTION_ARGS)
 	ItemPointerCopy(tid, result);
 
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	heap_get_latest_tid(rel, snapshot, result);
+	storage_get_latest_tid(rel, snapshot, result);
 	UnregisterSnapshot(snapshot);
 
 	heap_close(rel, AccessShareLock);
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 5bbe55f18b..a55d701c88 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -134,7 +134,7 @@ extern void heap_parallelscan_initialize(ParallelHeapScanDesc target,
 extern void heap_parallelscan_reinitialize(ParallelHeapScanDesc parallel_scan);
 extern HeapScanDesc heap_beginscan_parallel(Relation, ParallelHeapScanDesc);
 
-extern bool heap_fetch(Relation relation, Snapshot snapshot,
+extern bool heap_fetch(Relation relation, ItemPointer tid, Snapshot snapshot,
 		   HeapTuple tuple, Buffer *userbuf, bool keep_buf,
 		   Relation stats_relation);
 extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation,
@@ -142,7 +142,6 @@ extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation,
 					   bool *all_dead, bool first_call);
 extern bool heap_hot_search(ItemPointer tid, Relation relation,
 				Snapshot snapshot, bool *all_dead);
-
 extern void heap_get_latest_tid(Relation relation, Snapshot snapshot,
 					ItemPointer tid);
 extern void setLastTid(const ItemPointer tid);
@@ -158,16 +157,17 @@ extern void heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 extern HTSU_Result heap_delete(Relation relation, ItemPointer tid,
 			CommandId cid, Snapshot crosscheck, bool wait,
 			HeapUpdateFailureData *hufd);
-extern void heap_finish_speculative(Relation relation, HeapTuple tuple);
-extern void heap_abort_speculative(Relation relation, HeapTuple tuple);
+extern void heap_finish_speculative(Relation relation, TupleTableSlot *slot);
+extern void heap_abort_speculative(Relation relation, TupleTableSlot *slot);
 extern HTSU_Result heap_update(Relation relation, ItemPointer otid,
 			HeapTuple newtup,
 			CommandId cid, Snapshot crosscheck, bool wait,
 			HeapUpdateFailureData *hufd, LockTupleMode *lockmode);
-extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
+extern HTSU_Result heap_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * tuple,
 				CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 				bool follow_update,
 				Buffer *buffer, HeapUpdateFailureData *hufd);
+
 extern void heap_inplace_update(Relation relation, HeapTuple tuple);
 extern bool heap_freeze_tuple(HeapTupleHeader tuple,
 				  TransactionId relfrozenxid, TransactionId relminmxid,
@@ -184,6 +184,10 @@ extern void simple_heap_update(Relation relation, ItemPointer otid,
 extern void heap_sync(Relation relation);
 extern void heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot);
 
+/* in heap/heapam_visibility.c */
+extern HTSU_Result HeapTupleSatisfiesUpdate(StorageTuple stup, CommandId curcid,
+						 Buffer buffer);
+
 /* in heap/pruneheap.c */
 extern void heap_page_prune_opt(Relation relation, Buffer buffer);
 extern int heap_page_prune(Relation relation, Buffer buffer,
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index b1ceb854cd..3eb93c9112 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -816,6 +816,7 @@ extern Datum heap_getsysattr(HeapTuple tup, int attnum, TupleDesc tupleDesc,
 extern HeapTuple heap_copytuple(HeapTuple tuple);
 extern void heap_copytuple_with_tuple(HeapTuple src, HeapTuple dest);
 extern Datum heap_copy_tuple_as_datum(HeapTuple tuple, TupleDesc tupleDesc);
+extern HeapTuple heap_form_tuple_by_datum(Datum data, Oid relid);
 extern HeapTuple heap_form_tuple(TupleDesc tupleDescriptor,
 				Datum *values, bool *isnull);
 extern HeapTuple heap_modify_tuple(HeapTuple tuple,
diff --git a/src/include/access/storage_common.h b/src/include/access/storage_common.h
index 6128607313..baba7261d4 100644
--- a/src/include/access/storage_common.h
+++ b/src/include/access/storage_common.h
@@ -55,9 +55,6 @@ typedef MinimalTuple (*SlotGetMinTuple_function) (TupleTableSlot *slot, bool pal
 
 typedef void (*SlotUpdateTableoid_function) (TupleTableSlot *slot, Oid tableoid);
 
-typedef void (*SpeculativeAbort_function) (Relation rel,
-										   TupleTableSlot *slot);
-
 typedef struct StorageSlotAmRoutine
 {
 	/* Operations on TupleTableSlot */
diff --git a/src/include/access/storageam.h b/src/include/access/storageam.h
new file mode 100644
index 0000000000..d6500ae5f2
--- /dev/null
+++ b/src/include/access/storageam.h
@@ -0,0 +1,84 @@
+/*-------------------------------------------------------------------------
+ *
+ * storageam.h
+ *	  POSTGRES storage access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/storageam.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGEAM_H
+#define STORAGEAM_H
+
+#include "access/heapam.h"
+#include "access/storage_common.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
+
+typedef union tuple_data
+{
+	TransactionId xid;
+	CommandId	cid;
+	ItemPointerData tid;
+}			tuple_data;
+
+typedef enum tuple_data_flags
+{
+	XMIN = 0,
+	UPDATED_XID,
+	CMIN,
+	TID,
+	CTID
+}			tuple_data_flags;
+
+/* Function pointer to let the index tuple insert from storage am */
+typedef List *(*InsertIndexTuples) (TupleTableSlot *slot, EState *estate, bool noDupErr,
+									bool *specConflict, List *arbiterIndexes);
+
+/* Function pointer to let the index tuple delete from storage am */
+typedef void (*DeleteIndexTuples) (Relation rel, ItemPointer tid, TransactionId old_xmin);
+
+extern bool storage_fetch(Relation relation,
+			  ItemPointer tid,
+			  Snapshot snapshot,
+			  StorageTuple * stuple,
+			  Buffer *userbuf,
+			  bool keep_buf,
+			  Relation stats_relation);
+
+extern HTSU_Result storage_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * stuple,
+				   CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
+				   bool follow_updates,
+				   Buffer *buffer, HeapUpdateFailureData *hufd);
+
+extern Oid storage_insert(Relation relation, TupleTableSlot *slot, CommandId cid,
+			   int options, BulkInsertState bistate, InsertIndexTuples IndexFunc,
+			   EState *estate, List *arbiterIndexes, List **recheckIndexes);
+
+extern HTSU_Result storage_delete(Relation relation, ItemPointer tid, CommandId cid,
+			   Snapshot crosscheck, bool wait, DeleteIndexTuples IndexFunc,
+			   HeapUpdateFailureData *hufd);
+
+extern HTSU_Result storage_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
+			   EState *estate, CommandId cid, Snapshot crosscheck, bool wait,
+			   HeapUpdateFailureData *hufd, LockTupleMode *lockmode,
+			   InsertIndexTuples IndexFunc, List **recheckIndexes);
+
+extern void storage_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
+					 CommandId cid, int options, BulkInsertState bistate);
+
+extern tuple_data storage_tuple_get_data(Relation relation, StorageTuple tuple, tuple_data_flags flags);
+
+extern StorageTuple storage_tuple_by_datum(Relation relation, Datum data, Oid tableoid);
+
+extern void storage_get_latest_tid(Relation relation,
+					   Snapshot snapshot,
+					   ItemPointer tid);
+
+extern void storage_sync(Relation rel);
+
+#endif
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
index 7df51c4167..03c814783f 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -11,7 +11,9 @@
 #ifndef STORAGEAMAPI_H
 #define STORAGEAMAPI_H
 
-#include "access/storage_common.h"
+#include "access/heapam.h"
+#include "access/storageam.h"
+#include "nodes/execnodes.h"
 #include "nodes/nodes.h"
 #include "utils/snapshot.h"
 #include "fmgr.h"
@@ -25,6 +27,61 @@ typedef HTSU_Result (*SnapshotSatisfiesUpdate_function) (StorageTuple htup, Comm
 typedef HTSV_Result (*SnapshotSatisfiesVacuum_function) (StorageTuple htup, TransactionId OldestXmin, Buffer buffer);
 
 
+typedef Oid (*TupleInsert_function) (Relation rel, TupleTableSlot *slot, CommandId cid,
+									 int options, BulkInsertState bistate, InsertIndexTuples IndexFunc,
+									 EState *estate, List *arbiterIndexes, List **recheckIndexes);
+
+typedef HTSU_Result (*TupleDelete_function) (Relation relation,
+											 ItemPointer tid,
+											 CommandId cid,
+											 Snapshot crosscheck,
+											 bool wait,
+											 DeleteIndexTuples IndexFunc,
+											 HeapUpdateFailureData *hufd);
+
+typedef HTSU_Result (*TupleUpdate_function) (Relation relation,
+											 ItemPointer otid,
+											 TupleTableSlot *slot,
+											 EState *estate,
+											 CommandId cid,
+											 Snapshot crosscheck,
+											 bool wait,
+											 HeapUpdateFailureData *hufd,
+											 LockTupleMode *lockmode,
+											 InsertIndexTuples IndexFunc,
+											 List **recheckIndexes);
+
+typedef bool (*TupleFetch_function) (Relation relation,
+									 ItemPointer tid,
+									 Snapshot snapshot,
+									 StorageTuple * tuple,
+									 Buffer *userbuf,
+									 bool keep_buf,
+									 Relation stats_relation);
+
+typedef HTSU_Result (*TupleLock_function) (Relation relation,
+										   ItemPointer tid,
+										   StorageTuple * tuple,
+										   CommandId cid,
+										   LockTupleMode mode,
+										   LockWaitPolicy wait_policy,
+										   bool follow_update,
+										   Buffer *buffer,
+										   HeapUpdateFailureData *hufd);
+
+typedef void (*MultiInsert_function) (Relation relation, HeapTuple *tuples, int ntuples,
+									  CommandId cid, int options, BulkInsertState bistate);
+
+typedef void (*TupleGetLatestTid_function) (Relation relation,
+											Snapshot snapshot,
+											ItemPointer tid);
+
+typedef tuple_data(*GetTupleData_function) (StorageTuple tuple, tuple_data_flags flags);
+
+typedef StorageTuple(*TupleFromDatum_function) (Datum data, Oid tableoid);
+
+typedef void (*RelationSync_function) (Relation relation);
+
 /*
  * API struct for a storage AM.  Note this must be stored in a single palloc'd
  * chunk of memory.
@@ -44,6 +101,20 @@ typedef struct StorageAmRoutine
 
 	slot_storageam_hook slot_storageam;
 
+	/* Operations on physical tuples */
+	TupleInsert_function tuple_insert;	/* heap_insert */
+	TupleUpdate_function tuple_update;	/* heap_update */
+	TupleDelete_function tuple_delete;	/* heap_delete */
+	TupleFetch_function tuple_fetch;	/* heap_fetch */
+	TupleLock_function tuple_lock;	/* heap_lock_tuple */
+	MultiInsert_function multi_insert;	/* heap_multi_insert */
+	TupleGetLatestTid_function tuple_get_latest_tid;	/* heap_get_latest_tid */
+
+	GetTupleData_function get_tuple_data;
+	TupleFromDatum_function tuple_from_datum;
+
+	RelationSync_function relation_sync;	/* heap_sync */
+
 }			StorageAmRoutine;
 
 extern StorageAmRoutine * GetStorageAmRoutine(Oid amhandler);
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index ff5546cf28..093d1ae112 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -190,7 +190,7 @@ extern TupleTableSlot *ExecBRInsertTriggers(EState *estate,
 					 TupleTableSlot *slot);
 extern void ExecARInsertTriggers(EState *estate,
 					 ResultRelInfo *relinfo,
-					 HeapTuple trigtuple,
+					 TupleTableSlot *slot,
 					 List *recheckIndexes,
 					 TransitionCaptureState *transition_capture);
 extern TupleTableSlot *ExecIRInsertTriggers(EState *estate,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index e6569e1038..612d468f1f 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -198,16 +198,16 @@ 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 HeapTuple EvalPlanQualFetch(EState *estate, Relation relation,
-				  int lockmode, LockWaitPolicy wait_policy, ItemPointer tid,
-				  TransactionId priorXmax);
+extern StorageTuple EvalPlanQualFetch(EState *estate, Relation relation,
+									  int lockmode, LockWaitPolicy wait_policy, ItemPointer tid,
+									  TransactionId priorXmax);
 extern void EvalPlanQualInit(EPQState *epqstate, EState *estate,
 				 Plan *subplan, List *auxrowmarks, int epqParam);
 extern void EvalPlanQualSetPlan(EPQState *epqstate,
 					Plan *subplan, List *auxrowmarks);
 extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
-					 HeapTuple tuple);
-extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+					 StorageTuple tuple);
+extern StorageTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
 
 #define EvalPlanQualSetSlot(epqstate, slot)  ((epqstate)->origslot = (slot))
 extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
@@ -522,9 +522,8 @@ extern int	ExecCleanTargetListLength(List *targetlist);
  */
 extern void ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative);
 extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
-extern List *ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid,
-					  EState *estate, bool noDupErr, bool *specConflict,
-					  List *arbiterIndexes);
+extern List *ExecInsertIndexTuples(TupleTableSlot *slot, EState *estate, bool noDupErr,
+					  bool *specConflict, List *arbiterIndexes);
 extern bool ExecCheckIndexConstraints(TupleTableSlot *slot, EState *estate,
 						  ItemPointer conflictTid, List *arbiterIndexes);
 extern void check_exclusion_constraint(Relation heap, Relation index,
diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h
index df0fd0101c..6c024f34be 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -133,6 +133,7 @@ typedef struct TupleTableSlot
 	Oid			tts_tupleOid;	/* XXX describe */
 	int			tts_nvalid;		/* # of valid values in tts_values */
 	uint32		tts_speculativeToken;	/* XXX describe */
+	bool		tts_specConflict;	/* XXX describe */
 	bool		tts_shouldFree;
 	bool		tts_shouldFreeMin;
 	Datum	   *tts_values;		/* current per-attribute values */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index b121e16688..55b8a64506 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -515,7 +515,7 @@ typedef struct EState
 	 * remember if the tuple has been returned already.  Arrays are of size
 	 * list_length(es_range_table) and are indexed by scan node scanrelid - 1.
 	 */
-	HeapTuple  *es_epqTuple;	/* array of EPQ substitute tuples */
+	StorageTuple *es_epqTuple;	/* array of EPQ substitute tuples */
 	bool	   *es_epqTupleSet; /* true if EPQ tuple is provided */
 	bool	   *es_epqScanDone; /* true if EPQ tuple has been fetched */
 
@@ -2012,7 +2012,7 @@ typedef struct HashInstrumentation
 	int			nbatch;			/* number of batches at end of execution */
 	int			nbatch_original;	/* planned number of batches */
 	size_t		space_peak;		/* speak memory usage in bytes */
-} HashInstrumentation;
+}			HashInstrumentation;
 
 /* ----------------
  *	 Shared memory container for per-worker hash information
@@ -2022,7 +2022,7 @@ typedef struct SharedHashInfo
 {
 	int			num_workers;
 	HashInstrumentation hinstrument[FLEXIBLE_ARRAY_MEMBER];
-} SharedHashInfo;
+}			SharedHashInfo;
 
 /* ----------------
  *	 HashState information
@@ -2083,7 +2083,7 @@ typedef struct LockRowsState
 	PlanState	ps;				/* its first field is NodeTag */
 	List	   *lr_arowMarks;	/* List of ExecAuxRowMarks */
 	EPQState	lr_epqstate;	/* for evaluating EvalPlanQual rechecks */
-	HeapTuple  *lr_curtuples;	/* locked tuples (one entry per RT entry) */
+	StorageTuple *lr_curtuples; /* locked tuples (one entry per RT entry) */
 	int			lr_ntables;		/* length of lr_curtuples[] array */
 } LockRowsState;
 
-- 
2.15.0.windows.1



  [application/octet-stream] 0007-Scan-functions-are-added-to-storage-AM.patch (104.2K, ../../CAJrrPGeoihBWs+BUP06q9=rCNm75m56J-EEStf3ygikNcpreYw@mail.gmail.com/10-0007-Scan-functions-are-added-to-storage-AM.patch)
  download | inline diff:
From c3b7d4d6b528a7c6f4eb4566fcd5f9633d0b69b6 Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Wed, 3 Jan 2018 17:30:30 +1100
Subject: [PATCH 07/12] Scan functions are added to storage AM

All the scan functions that are present
in heapam module are moved into heapm_storage
and corresponding function hooks are added.

Replaced HeapTuple with StorageTuple whereever
possible.

Currently directly returning slot functionality
instead of tuple is added only to limited number
of places.
---
 contrib/pgrowlocks/pgrowlocks.c            |   6 +-
 contrib/pgstattuple/pgstattuple.c          |   6 +-
 src/backend/access/heap/heapam.c           | 212 ++++++++++-----------------
 src/backend/access/heap/heapam_storage.c   |  11 +-
 src/backend/access/index/genam.c           |  11 +-
 src/backend/access/index/indexam.c         |  13 +-
 src/backend/access/nbtree/nbtinsert.c      |   7 +-
 src/backend/access/storage/storageam.c     | 223 +++++++++++++++++++++++++++++
 src/backend/bootstrap/bootstrap.c          |  25 ++--
 src/backend/catalog/aclchk.c               |  13 +-
 src/backend/catalog/index.c                |  59 ++++----
 src/backend/catalog/partition.c            |   7 +-
 src/backend/catalog/pg_conversion.c        |   7 +-
 src/backend/catalog/pg_db_role_setting.c   |   7 +-
 src/backend/catalog/pg_publication.c       |   7 +-
 src/backend/catalog/pg_subscription.c      |   7 +-
 src/backend/commands/cluster.c             |  13 +-
 src/backend/commands/constraint.c          |   3 +-
 src/backend/commands/copy.c                |   6 +-
 src/backend/commands/dbcommands.c          |  19 +--
 src/backend/commands/indexcmds.c           |   7 +-
 src/backend/commands/tablecmds.c           |  30 ++--
 src/backend/commands/tablespace.c          |  39 ++---
 src/backend/commands/typecmds.c            |  13 +-
 src/backend/commands/vacuum.c              |  13 +-
 src/backend/executor/execAmi.c             |   2 +-
 src/backend/executor/execIndexing.c        |  13 +-
 src/backend/executor/execReplication.c     |  15 +-
 src/backend/executor/execTuples.c          |   8 +-
 src/backend/executor/functions.c           |   4 +-
 src/backend/executor/nodeAgg.c             |   4 +-
 src/backend/executor/nodeBitmapHeapscan.c  |  19 +--
 src/backend/executor/nodeForeignscan.c     |   6 +-
 src/backend/executor/nodeGather.c          |   8 +-
 src/backend/executor/nodeGatherMerge.c     |  14 +-
 src/backend/executor/nodeIndexonlyscan.c   |   8 +-
 src/backend/executor/nodeIndexscan.c       |  16 +--
 src/backend/executor/nodeSamplescan.c      |  34 ++---
 src/backend/executor/nodeSeqscan.c         |  45 ++----
 src/backend/executor/nodeWindowAgg.c       |   4 +-
 src/backend/executor/spi.c                 |  20 +--
 src/backend/executor/tqueue.c              |   2 +-
 src/backend/postmaster/autovacuum.c        |  18 +--
 src/backend/postmaster/pgstat.c            |   7 +-
 src/backend/replication/logical/launcher.c |   7 +-
 src/backend/rewrite/rewriteDefine.c        |   7 +-
 src/backend/utils/init/postinit.c          |   7 +-
 src/include/access/heapam.h                |  27 ++--
 src/include/access/storage_common.h        |   1 +
 src/include/access/storageam.h             |  35 +++++
 src/include/access/storageamapi.h          |  44 ++++++
 src/include/executor/functions.h           |   2 +-
 src/include/executor/spi.h                 |  12 +-
 src/include/executor/tqueue.h              |   4 +-
 src/include/funcapi.h                      |   2 +-
 55 files changed, 712 insertions(+), 447 deletions(-)

diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index 830e74fd07..bc8b423975 100644
--- a/contrib/pgrowlocks/pgrowlocks.c
+++ b/contrib/pgrowlocks/pgrowlocks.c
@@ -125,7 +125,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
 			aclcheck_error(aclresult, ACL_KIND_CLASS,
 						   RelationGetRelationName(rel));
 
-		scan = heap_beginscan(rel, GetActiveSnapshot(), 0, NULL);
+		scan = storage_beginscan(rel, GetActiveSnapshot(), 0, NULL);
 		mydata = palloc(sizeof(*mydata));
 		mydata->rel = rel;
 		mydata->scan = scan;
@@ -141,7 +141,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
 	scan = mydata->scan;
 
 	/* scan the relation */
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		HTSU_Result htsu;
 		TransactionId xmax;
@@ -306,7 +306,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
 		}
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(mydata->rel, AccessShareLock);
 
 	SRF_RETURN_DONE(funcctx);
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index e098202f84..c4b10d6efc 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -325,13 +325,13 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 	StorageAmRoutine *method = rel->rd_stamroutine;
 
 	/* Disable syncscan because we assume we scan from block zero upwards */
-	scan = heap_beginscan_strat(rel, SnapshotAny, 0, NULL, true, false);
+	scan = storage_beginscan_strat(rel, SnapshotAny, 0, NULL, true, false);
 	InitDirtySnapshot(SnapshotDirty);
 
 	nblocks = scan->rs_nblocks; /* # blocks to be scanned */
 
 	/* scan the relation */
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		CHECK_FOR_INTERRUPTS();
 
@@ -384,7 +384,7 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 		block++;
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	relation_close(rel, AccessShareLock);
 
 	stat.table_len = (uint64) nblocks * BLCKSZ;
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index c1da192f14..2a46dc7ebc 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -80,17 +80,6 @@
 /* GUC variable */
 bool		synchronize_seqscans = true;
 
-
-static HeapScanDesc heap_beginscan_internal(Relation relation,
-						Snapshot snapshot,
-						int nkeys, ScanKey key,
-						ParallelHeapScanDesc parallel_scan,
-						bool allow_strat,
-						bool allow_sync,
-						bool allow_pagemode,
-						bool is_bitmapscan,
-						bool is_samplescan,
-						bool temp_snap);
 static void heap_parallelscan_startblock_init(HeapScanDesc scan);
 static BlockNumber heap_parallelscan_nextpage(HeapScanDesc scan);
 static HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup,
@@ -1387,87 +1376,16 @@ heap_openrv_extended(const RangeVar *relation, LOCKMODE lockmode,
 	return r;
 }
 
-
-/* ----------------
- *		heap_beginscan	- begin relation scan
- *
- * heap_beginscan is the "standard" case.
- *
- * heap_beginscan_catalog differs in setting up its own temporary snapshot.
- *
- * heap_beginscan_strat offers an extended API that lets the caller control
- * whether a nondefault buffer access strategy can be used, and whether
- * syncscan can be chosen (possibly resulting in the scan not starting from
- * block zero).  Both of these default to true with plain heap_beginscan.
- *
- * heap_beginscan_bm is an alternative entry point for setting up a
- * HeapScanDesc for a bitmap heap scan.  Although that scan technology is
- * really quite unlike a standard seqscan, there is just enough commonality
- * to make it worth using the same data structure.
- *
- * heap_beginscan_sampling is an alternative entry point for setting up a
- * HeapScanDesc for a TABLESAMPLE scan.  As with bitmap scans, it's worth
- * using the same data structure although the behavior is rather different.
- * In addition to the options offered by heap_beginscan_strat, this call
- * also allows control of whether page-mode visibility checking is used.
- * ----------------
- */
 HeapScanDesc
 heap_beginscan(Relation relation, Snapshot snapshot,
-			   int nkeys, ScanKey key)
-{
-	return heap_beginscan_internal(relation, snapshot, nkeys, key, NULL,
-								   true, true, true, false, false, false);
-}
-
-HeapScanDesc
-heap_beginscan_catalog(Relation relation, int nkeys, ScanKey key)
-{
-	Oid			relid = RelationGetRelid(relation);
-	Snapshot	snapshot = RegisterSnapshot(GetCatalogSnapshot(relid));
-
-	return heap_beginscan_internal(relation, snapshot, nkeys, key, NULL,
-								   true, true, true, false, false, true);
-}
-
-HeapScanDesc
-heap_beginscan_strat(Relation relation, Snapshot snapshot,
-					 int nkeys, ScanKey key,
-					 bool allow_strat, bool allow_sync)
-{
-	return heap_beginscan_internal(relation, snapshot, nkeys, key, NULL,
-								   allow_strat, allow_sync, true,
-								   false, false, false);
-}
-
-HeapScanDesc
-heap_beginscan_bm(Relation relation, Snapshot snapshot,
-				  int nkeys, ScanKey key)
-{
-	return heap_beginscan_internal(relation, snapshot, nkeys, key, NULL,
-								   false, false, true, true, false, false);
-}
-
-HeapScanDesc
-heap_beginscan_sampling(Relation relation, Snapshot snapshot,
-						int nkeys, ScanKey key,
-						bool allow_strat, bool allow_sync, bool allow_pagemode)
-{
-	return heap_beginscan_internal(relation, snapshot, nkeys, key, NULL,
-								   allow_strat, allow_sync, allow_pagemode,
-								   false, true, false);
-}
-
-static HeapScanDesc
-heap_beginscan_internal(Relation relation, Snapshot snapshot,
-						int nkeys, ScanKey key,
-						ParallelHeapScanDesc parallel_scan,
-						bool allow_strat,
-						bool allow_sync,
-						bool allow_pagemode,
-						bool is_bitmapscan,
-						bool is_samplescan,
-						bool temp_snap)
+			   int nkeys, ScanKey key,
+			   ParallelHeapScanDesc parallel_scan,
+			   bool allow_strat,
+			   bool allow_sync,
+			   bool allow_pagemode,
+			   bool is_bitmapscan,
+			   bool is_samplescan,
+			   bool temp_snap)
 {
 	HeapScanDesc scan;
 
@@ -1537,9 +1455,16 @@ heap_beginscan_internal(Relation relation, Snapshot snapshot,
  * ----------------
  */
 void
-heap_rescan(HeapScanDesc scan,
-			ScanKey key)
+heap_rescan(HeapScanDesc scan, ScanKey key, bool set_params,
+			bool allow_strat, bool allow_sync, bool allow_pagemode)
 {
+	if (set_params)
+	{
+		scan->rs_allow_strat = allow_strat;
+		scan->rs_allow_sync = allow_sync;
+		scan->rs_pageatatime = allow_pagemode && IsMVCCSnapshot(scan->rs_snapshot);
+	}
+
 	/*
 	 * unpin scan buffers
 	 */
@@ -1550,27 +1475,21 @@ heap_rescan(HeapScanDesc scan,
 	 * reinitialize scan descriptor
 	 */
 	initscan(scan, key, true);
-}
 
-/* ----------------
- *		heap_rescan_set_params	- restart a relation scan after changing params
- *
- * This call allows changing the buffer strategy, syncscan, and pagemode
- * options before starting a fresh scan.  Note that although the actual use
- * of syncscan might change (effectively, enabling or disabling reporting),
- * the previously selected startblock will be kept.
- * ----------------
- */
-void
-heap_rescan_set_params(HeapScanDesc scan, ScanKey key,
-					   bool allow_strat, bool allow_sync, bool allow_pagemode)
-{
-	/* adjust parameters */
-	scan->rs_allow_strat = allow_strat;
-	scan->rs_allow_sync = allow_sync;
-	scan->rs_pageatatime = allow_pagemode && IsMVCCSnapshot(scan->rs_snapshot);
-	/* ... and rescan */
-	heap_rescan(scan, key);
+	/*
+	 * reset parallel scan, if present
+	 */
+	if (scan->rs_parallel != NULL)
+	{
+		ParallelHeapScanDesc parallel_scan;
+
+		/*
+		 * Caller is responsible for making sure that all workers have
+		 * finished the scan before calling this.
+		 */
+		parallel_scan = scan->rs_parallel;
+		pg_atomic_write_u64(&parallel_scan->phs_nallocated, 0);
+	}
 }
 
 /* ----------------
@@ -1659,25 +1578,6 @@ heap_parallelscan_reinitialize(ParallelHeapScanDesc parallel_scan)
 	pg_atomic_write_u64(&parallel_scan->phs_nallocated, 0);
 }
 
-/* ----------------
- *		heap_beginscan_parallel - join a parallel scan
- *
- *		Caller must hold a suitable lock on the correct relation.
- * ----------------
- */
-HeapScanDesc
-heap_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan)
-{
-	Snapshot	snapshot;
-
-	Assert(RelationGetRelid(relation) == parallel_scan->phs_relid);
-	snapshot = RestoreSnapshot(parallel_scan->phs_snapshot_data);
-	RegisterSnapshot(snapshot);
-
-	return heap_beginscan_internal(relation, snapshot, 0, NULL, parallel_scan,
-								   true, true, true, false, false, true);
-}
-
 /* ----------------
  *		heap_parallelscan_startblock_init - find and set the scan's startblock
  *
@@ -1822,8 +1722,7 @@ heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
 #define HEAPDEBUG_3
 #endif							/* !defined(HEAPDEBUGALL) */
 
-
-HeapTuple
+StorageTuple
 heap_getnext(HeapScanDesc scan, ScanDirection direction)
 {
 	/* Note: no locking manipulations needed */
@@ -1853,6 +1752,53 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 	return heap_copytuple(&(scan->rs_ctup));
 }
 
+#ifdef HEAPAMSLOTDEBUGALL
+#define HEAPAMSLOTDEBUG_1 \
+	elog(DEBUG2, "heapam_getnext([%s,nkeys=%d],dir=%d) called", \
+		 RelationGetRelationName(scan->rs_rd), scan->rs_nkeys, (int) direction)
+#define HEAPAMSLOTDEBUG_2 \
+	elog(DEBUG2, "heapam_getnext returning EOS")
+#define HEAPAMSLOTDEBUG_3 \
+	elog(DEBUG2, "heapam_getnext returning tuple")
+#else
+#define HEAPAMSLOTDEBUG_1
+#define HEAPAMSLOTDEBUG_2
+#define HEAPAMSLOTDEBUG_3
+#endif
+
+TupleTableSlot *
+heap_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
+{
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
+	/* Note: no locking manipulations needed */
+
+	HEAPAMSLOTDEBUG_1;			/* heap_getnext( info ) */
+
+	if (scan->rs_pageatatime)
+		heapgettup_pagemode(scan, direction,
+							scan->rs_nkeys, scan->rs_key);
+	else
+		heapgettup(scan, direction, scan->rs_nkeys, scan->rs_key);
+
+	if (scan->rs_ctup.t_data == NULL)
+	{
+		HEAPAMSLOTDEBUG_2;		/* heap_getnext returning EOS */
+		ExecClearTuple(slot);
+		return slot;
+	}
+
+	/*
+	 * if we get here it means we have a new current scan tuple, so point to
+	 * the proper return buffer and return the tuple.
+	 */
+	HEAPAMSLOTDEBUG_3;			/* heap_getnext returning tuple */
+
+	pgstat_count_heap_getnext(scan->rs_rd);
+	return ExecStoreTuple(heap_copytuple(&(scan->rs_ctup)),
+						  slot, InvalidBuffer, true);
+}
+
 /*
  *	heap_fetch		- retrieve tuple with given tid
  *
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index 438063cbe1..dd0e05edb7 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -149,8 +149,6 @@ heapam_heap_delete(Relation relation, ItemPointer tid, CommandId cid,
 	return heap_delete(relation, tid, cid, crosscheck, wait, hufd);
 }
 
-
-
 static HTSU_Result
 heapam_heap_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 				   EState *estate, CommandId cid, Snapshot crosscheck,
@@ -260,6 +258,15 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 
 	amroutine->slot_storageam = heapam_storage_slot_handler;
 
+	amroutine->scan_begin = heap_beginscan;
+	amroutine->scansetlimits = heap_setscanlimits;
+	amroutine->scan_getnext = heap_getnext;
+	amroutine->scan_getnextslot = heap_getnextslot;
+	amroutine->scan_end = heap_endscan;
+	amroutine->scan_rescan = heap_rescan;
+	amroutine->scan_update_snapshot = heap_update_snapshot;
+	amroutine->hot_search_buffer = heap_hot_search_buffer;
+
 	amroutine->tuple_fetch = heapam_fetch;
 	amroutine->tuple_insert = heapam_heap_insert;
 	amroutine->tuple_delete = heapam_heap_delete;
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 8f0a827b5d..02c88bf1f3 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -20,6 +20,7 @@
 #include "postgres.h"
 
 #include "access/relscan.h"
+#include "access/storageam.h"
 #include "access/transam.h"
 #include "catalog/index.h"
 #include "lib/stringinfo.h"
@@ -394,9 +395,9 @@ systable_beginscan(Relation heapRelation,
 		 * disadvantage; and there are no compensating advantages, because
 		 * it's unlikely that such scans will occur in parallel.
 		 */
-		sysscan->scan = heap_beginscan_strat(heapRelation, snapshot,
-											 nkeys, key,
-											 true, false);
+		sysscan->scan = storage_beginscan_strat(heapRelation, snapshot,
+												nkeys, key,
+												true, false);
 		sysscan->iscan = NULL;
 	}
 
@@ -432,7 +433,7 @@ systable_getnext(SysScanDesc sysscan)
 			elog(ERROR, "system catalog scans with lossy index conditions are not implemented");
 	}
 	else
-		htup = heap_getnext(sysscan->scan, ForwardScanDirection);
+		htup = storage_getnext(sysscan->scan, ForwardScanDirection);
 
 	return htup;
 }
@@ -504,7 +505,7 @@ systable_endscan(SysScanDesc sysscan)
 		index_close(sysscan->irel, AccessShareLock);
 	}
 	else
-		heap_endscan(sysscan->scan);
+		storage_endscan(sysscan->scan);
 
 	if (sysscan->snapshot)
 		UnregisterSnapshot(sysscan->snapshot);
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index 1b61cd9515..c704132f2d 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -71,6 +71,7 @@
 
 #include "access/amapi.h"
 #include "access/relscan.h"
+#include "access/storageam.h"
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "catalog/catalog.h"
@@ -605,12 +606,12 @@ index_fetch_heap(IndexScanDesc scan)
 
 	/* Obtain share-lock on the buffer so we can examine visibility */
 	LockBuffer(scan->xs_cbuf, BUFFER_LOCK_SHARE);
-	got_heap_tuple = heap_hot_search_buffer(tid, scan->heapRelation,
-											scan->xs_cbuf,
-											scan->xs_snapshot,
-											&scan->xs_ctup,
-											&all_dead,
-											!scan->xs_continue_hot);
+	got_heap_tuple = storage_hot_search_buffer(tid, scan->heapRelation,
+											   scan->xs_cbuf,
+											   scan->xs_snapshot,
+											   &scan->xs_ctup,
+											   &all_dead,
+											   !scan->xs_continue_hot);
 	LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK);
 
 	if (got_heap_tuple)
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index 51059c0c7d..dcccf61081 100644
--- a/src/backend/access/nbtree/nbtinsert.c
+++ b/src/backend/access/nbtree/nbtinsert.c
@@ -18,6 +18,7 @@
 #include "access/heapam.h"
 #include "access/nbtree.h"
 #include "access/nbtxlog.h"
+#include "access/storageam.h"
 #include "access/transam.h"
 #include "access/xloginsert.h"
 #include "miscadmin.h"
@@ -325,8 +326,8 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
 				 * that satisfies SnapshotDirty.  This is necessary because we
 				 * have just a single index entry for the entire chain.
 				 */
-				else if (heap_hot_search(&htid, heapRel, &SnapshotDirty,
-										 &all_dead))
+				else if (storage_hot_search(&htid, heapRel, &SnapshotDirty,
+											&all_dead))
 				{
 					TransactionId xwait;
 
@@ -379,7 +380,7 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
 					 * entry.
 					 */
 					htid = itup->t_tid;
-					if (heap_hot_search(&htid, heapRel, SnapshotSelf, NULL))
+					if (storage_hot_search(&htid, heapRel, SnapshotSelf, NULL))
 					{
 						/* Normal case --- it's still live */
 					}
diff --git a/src/backend/access/storage/storageam.c b/src/backend/access/storage/storageam.c
index a8b971d004..e56dc189ed 100644
--- a/src/backend/access/storage/storageam.c
+++ b/src/backend/access/storage/storageam.c
@@ -16,7 +16,9 @@
 
 #include "access/storageam.h"
 #include "access/storageamapi.h"
+#include "access/relscan.h"
 #include "utils/rel.h"
+#include "utils/tqual.h"
 
 /*
  *	storage_fetch		- retrieve tuple with given tid
@@ -48,6 +50,174 @@ storage_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * stuple,
 												follow_updates, buffer, hufd);
 }
 
+/* ----------------
+ *		heap_beginscan_parallel - join a parallel scan
+ *
+ *		Caller must hold a suitable lock on the correct relation.
+ * ----------------
+ */
+HeapScanDesc
+storage_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan)
+{
+	Snapshot	snapshot;
+
+	Assert(RelationGetRelid(relation) == parallel_scan->phs_relid);
+	snapshot = RestoreSnapshot(parallel_scan->phs_snapshot_data);
+	RegisterSnapshot(snapshot);
+
+	return relation->rd_stamroutine->scan_begin(relation, snapshot, 0, NULL, parallel_scan,
+												true, true, true, false, false, true);
+}
+
+/*
+ * heap_setscanlimits - restrict range of a heapscan
+ *
+ * startBlk is the page to start at
+ * numBlks is number of pages to scan (InvalidBlockNumber means "all")
+ */
+void
+storage_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
+{
+	sscan->rs_rd->rd_stamroutine->scansetlimits(sscan, startBlk, numBlks);
+}
+
+
+/* ----------------
+ *		heap_beginscan	- begin relation scan
+ *
+ * heap_beginscan is the "standard" case.
+ *
+ * heap_beginscan_catalog differs in setting up its own temporary snapshot.
+ *
+ * heap_beginscan_strat offers an extended API that lets the caller control
+ * whether a nondefault buffer access strategy can be used, and whether
+ * syncscan can be chosen (possibly resulting in the scan not starting from
+ * block zero).  Both of these default to true with plain heap_beginscan.
+ *
+ * heap_beginscan_bm is an alternative entry point for setting up a
+ * HeapScanDesc for a bitmap heap scan.  Although that scan technology is
+ * really quite unlike a standard seqscan, there is just enough commonality
+ * to make it worth using the same data structure.
+ *
+ * heap_beginscan_sampling is an alternative entry point for setting up a
+ * HeapScanDesc for a TABLESAMPLE scan.  As with bitmap scans, it's worth
+ * using the same data structure although the behavior is rather different.
+ * In addition to the options offered by heap_beginscan_strat, this call
+ * also allows control of whether page-mode visibility checking is used.
+ * ----------------
+ */
+HeapScanDesc
+storage_beginscan(Relation relation, Snapshot snapshot,
+				  int nkeys, ScanKey key)
+{
+	return relation->rd_stamroutine->scan_begin(relation, snapshot, nkeys, key, NULL,
+												true, true, true, false, false, false);
+}
+
+HeapScanDesc
+storage_beginscan_catalog(Relation relation, int nkeys, ScanKey key)
+{
+	Oid			relid = RelationGetRelid(relation);
+	Snapshot	snapshot = RegisterSnapshot(GetCatalogSnapshot(relid));
+
+	return relation->rd_stamroutine->scan_begin(relation, snapshot, nkeys, key, NULL,
+												true, true, true, false, false, true);
+}
+
+HeapScanDesc
+storage_beginscan_strat(Relation relation, Snapshot snapshot,
+						int nkeys, ScanKey key,
+						bool allow_strat, bool allow_sync)
+{
+	return relation->rd_stamroutine->scan_begin(relation, snapshot, nkeys, key, NULL,
+												allow_strat, allow_sync, true,
+												false, false, false);
+}
+
+HeapScanDesc
+storage_beginscan_bm(Relation relation, Snapshot snapshot,
+					 int nkeys, ScanKey key)
+{
+	return relation->rd_stamroutine->scan_begin(relation, snapshot, nkeys, key, NULL,
+												false, false, true, true, false, false);
+}
+
+HeapScanDesc
+storage_beginscan_sampling(Relation relation, Snapshot snapshot,
+						   int nkeys, ScanKey key,
+						   bool allow_strat, bool allow_sync, bool allow_pagemode)
+{
+	return relation->rd_stamroutine->scan_begin(relation, snapshot, nkeys, key, NULL,
+												allow_strat, allow_sync, allow_pagemode,
+												false, true, false);
+}
+
+/* ----------------
+ *		heap_rescan		- restart a relation scan
+ * ----------------
+ */
+void
+storage_rescan(HeapScanDesc scan,
+			   ScanKey key)
+{
+	scan->rs_rd->rd_stamroutine->scan_rescan(scan, key, false, false, false, false);
+}
+
+/* ----------------
+ *		heap_rescan_set_params	- restart a relation scan after changing params
+ *
+ * This call allows changing the buffer strategy, syncscan, and pagemode
+ * options before starting a fresh scan.  Note that although the actual use
+ * of syncscan might change (effectively, enabling or disabling reporting),
+ * the previously selected startblock will be kept.
+ * ----------------
+ */
+void
+storage_rescan_set_params(HeapScanDesc scan, ScanKey key,
+						  bool allow_strat, bool allow_sync, bool allow_pagemode)
+{
+	scan->rs_rd->rd_stamroutine->scan_rescan(scan, key, true,
+											 allow_strat, allow_sync, (allow_pagemode && IsMVCCSnapshot(scan->rs_snapshot)));
+}
+
+/* ----------------
+ *		heap_endscan	- end relation scan
+ *
+ *		See how to integrate with index scans.
+ *		Check handling if reldesc caching.
+ * ----------------
+ */
+void
+storage_endscan(HeapScanDesc scan)
+{
+	scan->rs_rd->rd_stamroutine->scan_end(scan);
+}
+
+
+/* ----------------
+ *		heap_update_snapshot
+ *
+ *		Update snapshot info in heap scan descriptor.
+ * ----------------
+ */
+void
+storage_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
+{
+	scan->rs_rd->rd_stamroutine->scan_update_snapshot(scan, snapshot);
+}
+
+StorageTuple
+storage_getnext(HeapScanDesc sscan, ScanDirection direction)
+{
+	return sscan->rs_rd->rd_stamroutine->scan_getnext(sscan, direction);
+}
+
+TupleTableSlot *
+storage_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
+{
+	return sscan->rs_rd->rd_stamroutine->scan_getnextslot(sscan, direction, slot);
+}
+
 /*
  * Insert a tuple from a slot into storage AM routine
  */
@@ -87,6 +257,59 @@ storage_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 												  lockmode, IndexFunc, recheckIndexes);
 }
 
+/*
+ *	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
+ * of a HOT chain), and buffer is the buffer holding this tuple.  We search
+ * for the first chain member satisfying the given snapshot.  If one is
+ * found, we update *tid to reference that tuple's offset number, and
+ * return true.  If no match, return false without modifying *tid.
+ *
+ * heapTuple is a caller-supplied buffer.  When a match is found, we return
+ * the tuple here, in addition to updating *tid.  If no match is found, the
+ * contents of this buffer on return are undefined.
+ *
+ * If all_dead is not NULL, we check non-visible tuples to see if they are
+ * globally dead; *all_dead is set true if all members of the HOT chain
+ * are vacuumable, false if not.
+ *
+ * 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.
+ */
+bool
+storage_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
+						  Snapshot snapshot, HeapTuple heapTuple,
+						  bool *all_dead, bool first_call)
+{
+	return relation->rd_stamroutine->hot_search_buffer(tid, relation, buffer,
+													   snapshot, heapTuple, all_dead, first_call);
+}
+
+/*
+ *	heap_hot_search		- search HOT chain for tuple satisfying snapshot
+ *
+ * This has the same API as heap_hot_search_buffer, except that the caller
+ * does not provide the buffer containing the page, rather we access it
+ * locally.
+ */
+bool
+storage_hot_search(ItemPointer tid, Relation relation, Snapshot snapshot,
+				   bool *all_dead)
+{
+	bool		result;
+	Buffer		buffer;
+	HeapTupleData heapTuple;
+
+	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
+	LockBuffer(buffer, BUFFER_LOCK_SHARE);
+	result = relation->rd_stamroutine->hot_search_buffer(tid, relation, buffer,
+														 snapshot, &heapTuple, all_dead, true);
+	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+	ReleaseBuffer(buffer);
+	return result;
+}
 
 /*
  *	storage_multi_insert	- insert multiple tuple into a storage
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 80860128fb..dd190a959f 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -18,6 +18,7 @@
 #include <signal.h>
 
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/xact.h"
 #include "access/xlog_internal.h"
 #include "bootstrap/bootstrap.h"
@@ -586,18 +587,18 @@ boot_openrel(char *relname)
 	{
 		/* We can now load the pg_type data */
 		rel = heap_open(TypeRelationId, NoLock);
-		scan = heap_beginscan_catalog(rel, 0, NULL);
+		scan = storage_beginscan_catalog(rel, 0, NULL);
 		i = 0;
-		while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tup = storage_getnext(scan, ForwardScanDirection)) != NULL)
 			++i;
-		heap_endscan(scan);
+		storage_endscan(scan);
 		app = Typ = ALLOC(struct typmap *, i + 1);
 		while (i-- > 0)
 			*app++ = ALLOC(struct typmap, 1);
 		*app = NULL;
-		scan = heap_beginscan_catalog(rel, 0, NULL);
+		scan = storage_beginscan_catalog(rel, 0, NULL);
 		app = Typ;
-		while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tup = storage_getnext(scan, ForwardScanDirection)) != NULL)
 		{
 			(*app)->am_oid = HeapTupleGetOid(tup);
 			memcpy((char *) &(*app)->am_typ,
@@ -605,7 +606,7 @@ boot_openrel(char *relname)
 				   sizeof((*app)->am_typ));
 			app++;
 		}
-		heap_endscan(scan);
+		storage_endscan(scan);
 		heap_close(rel, NoLock);
 	}
 
@@ -916,25 +917,25 @@ gettype(char *type)
 		}
 		elog(DEBUG4, "external type: %s", type);
 		rel = heap_open(TypeRelationId, NoLock);
-		scan = heap_beginscan_catalog(rel, 0, NULL);
+		scan = storage_beginscan_catalog(rel, 0, NULL);
 		i = 0;
-		while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tup = storage_getnext(scan, ForwardScanDirection)) != NULL)
 			++i;
-		heap_endscan(scan);
+		storage_endscan(scan);
 		app = Typ = ALLOC(struct typmap *, i + 1);
 		while (i-- > 0)
 			*app++ = ALLOC(struct typmap, 1);
 		*app = NULL;
-		scan = heap_beginscan_catalog(rel, 0, NULL);
+		scan = storage_beginscan_catalog(rel, 0, NULL);
 		app = Typ;
-		while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tup = storage_getnext(scan, ForwardScanDirection)) != NULL)
 		{
 			(*app)->am_oid = HeapTupleGetOid(tup);
 			memmove((char *) &(*app++)->am_typ,
 					(char *) GETSTRUCT(tup),
 					sizeof((*app)->am_typ));
 		}
-		heap_endscan(scan);
+		storage_endscan(scan);
 		heap_close(rel, NoLock);
 		return gettype(type);
 	}
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index fac80612b8..0614ff202f 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -20,6 +20,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "catalog/binary_upgrade.h"
@@ -847,14 +848,14 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 									InvalidOid);
 
 					rel = heap_open(ProcedureRelationId, AccessShareLock);
-					scan = heap_beginscan_catalog(rel, keycount, key);
+					scan = storage_beginscan_catalog(rel, keycount, key);
 
-					while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+					while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 					{
 						objects = lappend_oid(objects, HeapTupleGetOid(tuple));
 					}
 
-					heap_endscan(scan);
+					storage_endscan(scan);
 					heap_close(rel, AccessShareLock);
 				}
 				break;
@@ -892,14 +893,14 @@ getRelationsInNamespace(Oid namespaceId, char relkind)
 				CharGetDatum(relkind));
 
 	rel = heap_open(RelationRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(rel, 2, key);
+	scan = storage_beginscan_catalog(rel, 2, key);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		relations = lappend_oid(relations, HeapTupleGetOid(tuple));
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(rel, AccessShareLock);
 
 	return relations;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 16819abb68..a96b5ab7e4 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -26,6 +26,7 @@
 #include "access/amapi.h"
 #include "access/multixact.h"
 #include "access/relscan.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/transam.h"
 #include "access/visibilitymap.h"
@@ -1907,10 +1908,10 @@ index_update_stats(Relation rel,
 					BTEqualStrategyNumber, F_OIDEQ,
 					ObjectIdGetDatum(relid));
 
-		pg_class_scan = heap_beginscan_catalog(pg_class, 1, key);
-		tuple = heap_getnext(pg_class_scan, ForwardScanDirection);
+		pg_class_scan = storage_beginscan_catalog(pg_class, 1, key);
+		tuple = storage_getnext(pg_class_scan, ForwardScanDirection);
 		tuple = heap_copytuple(tuple);
-		heap_endscan(pg_class_scan);
+		storage_endscan(pg_class_scan);
 	}
 	else
 	{
@@ -2282,16 +2283,16 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	}
 
 	method = heapRelation->rd_stamroutine;
-	scan = heap_beginscan_strat(heapRelation,	/* relation */
-								snapshot,	/* snapshot */
-								0,	/* number of keys */
-								NULL,	/* scan key */
-								true,	/* buffer access strategy OK */
-								allow_sync);	/* syncscan OK? */
+	scan = storage_beginscan_strat(heapRelation,	/* relation */
+								   snapshot,	/* snapshot */
+								   0,	/* number of keys */
+								   NULL,	/* scan key */
+								   true,	/* buffer access strategy OK */
+								   allow_sync); /* syncscan OK? */
 
 	/* set our scan endpoints */
 	if (!allow_sync)
-		heap_setscanlimits(scan, start_blockno, numblocks);
+		storage_setscanlimits(scan, start_blockno, numblocks);
 	else
 	{
 		/* syncscan can only be requested on whole relation */
@@ -2304,7 +2305,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	/*
 	 * Scan all tuples in the base relation.
 	 */
-	while ((heapTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((heapTuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		bool		tupleIsAlive;
 
@@ -2616,7 +2617,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 		}
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	/* we can now forget our snapshot, if set */
 	if (IsBootstrapProcessingMode() || indexInfo->ii_Concurrent)
@@ -2687,14 +2688,14 @@ IndexCheckExclusion(Relation heapRelation,
 	 * Scan all live tuples in the base relation.
 	 */
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	scan = heap_beginscan_strat(heapRelation,	/* relation */
-								snapshot,	/* snapshot */
-								0,	/* number of keys */
-								NULL,	/* scan key */
-								true,	/* buffer access strategy OK */
-								true);	/* syncscan OK */
-
-	while ((heapTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	scan = storage_beginscan_strat(heapRelation,	/* relation */
+								   snapshot,	/* snapshot */
+								   0,	/* number of keys */
+								   NULL,	/* scan key */
+								   true,	/* buffer access strategy OK */
+								   true);	/* syncscan OK */
+
+	while ((heapTuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		CHECK_FOR_INTERRUPTS();
 
@@ -2730,7 +2731,7 @@ IndexCheckExclusion(Relation heapRelation,
 								   estate, true);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 
 	ExecDropSingleTupleTableSlot(slot);
@@ -3007,17 +3008,17 @@ validate_index_heapscan(Relation heapRelation,
 	 * here, because it's critical that we read from block zero forward to
 	 * match the sorted TIDs.
 	 */
-	scan = heap_beginscan_strat(heapRelation,	/* relation */
-								snapshot,	/* snapshot */
-								0,	/* number of keys */
-								NULL,	/* scan key */
-								true,	/* buffer access strategy OK */
-								false); /* syncscan not OK */
+	scan = storage_beginscan_strat(heapRelation,	/* relation */
+								   snapshot,	/* snapshot */
+								   0,	/* number of keys */
+								   NULL,	/* scan key */
+								   true,	/* buffer access strategy OK */
+								   false);	/* syncscan not OK */
 
 	/*
 	 * Scan all tuples matching the snapshot.
 	 */
-	while ((heapTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((heapTuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		ItemPointer heapcursor = &heapTuple->t_self;
 		ItemPointerData rootTuple;
@@ -3174,7 +3175,7 @@ validate_index_heapscan(Relation heapRelation,
 		}
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	ExecDropSingleTupleTableSlot(slot);
 
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index ac9a2bda2e..081c23aa1b 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -19,6 +19,7 @@
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/nbtree.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
@@ -1324,7 +1325,7 @@ check_default_allows_bound(Relation parent, Relation default_rel,
 
 		econtext = GetPerTupleExprContext(estate);
 		snapshot = RegisterSnapshot(GetLatestSnapshot());
-		scan = heap_beginscan(part_rel, snapshot, 0, NULL);
+		scan = storage_beginscan(part_rel, snapshot, 0, NULL);
 		tupslot = MakeSingleTupleTableSlot(tupdesc);
 
 		/*
@@ -1333,7 +1334,7 @@ check_default_allows_bound(Relation parent, Relation default_rel,
 		 */
 		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
 
-		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 		{
 			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
 			econtext->ecxt_scantuple = tupslot;
@@ -1349,7 +1350,7 @@ check_default_allows_bound(Relation parent, Relation default_rel,
 		}
 
 		MemoryContextSwitchTo(oldCxt);
-		heap_endscan(scan);
+		storage_endscan(scan);
 		UnregisterSnapshot(snapshot);
 		ExecDropSingleTupleTableSlot(tupslot);
 		FreeExecutorState(estate);
diff --git a/src/backend/catalog/pg_conversion.c b/src/backend/catalog/pg_conversion.c
index 76fcd8fd9c..2a0cdca0b8 100644
--- a/src/backend/catalog/pg_conversion.c
+++ b/src/backend/catalog/pg_conversion.c
@@ -16,6 +16,7 @@
 
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
@@ -161,14 +162,14 @@ RemoveConversionById(Oid conversionOid)
 	/* open pg_conversion */
 	rel = heap_open(ConversionRelationId, RowExclusiveLock);
 
-	scan = heap_beginscan_catalog(rel, 1, &scanKeyData);
+	scan = storage_beginscan_catalog(rel, 1, &scanKeyData);
 
 	/* search for the target tuple */
-	if (HeapTupleIsValid(tuple = heap_getnext(scan, ForwardScanDirection)))
+	if (HeapTupleIsValid(tuple = storage_getnext(scan, ForwardScanDirection)))
 		CatalogTupleDelete(rel, &tuple->t_self);
 	else
 		elog(ERROR, "could not find tuple for conversion %u", conversionOid);
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(rel, RowExclusiveLock);
 }
 
diff --git a/src/backend/catalog/pg_db_role_setting.c b/src/backend/catalog/pg_db_role_setting.c
index e123691923..84ce11f75e 100644
--- a/src/backend/catalog/pg_db_role_setting.c
+++ b/src/backend/catalog/pg_db_role_setting.c
@@ -13,6 +13,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_db_role_setting.h"
@@ -196,12 +197,12 @@ DropSetting(Oid databaseid, Oid roleid)
 		numkeys++;
 	}
 
-	scan = heap_beginscan_catalog(relsetting, numkeys, keys);
-	while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+	scan = storage_beginscan_catalog(relsetting, numkeys, keys);
+	while (HeapTupleIsValid(tup = storage_getnext(scan, ForwardScanDirection)))
 	{
 		CatalogTupleDelete(relsetting, &tup->t_self);
 	}
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	heap_close(relsetting, RowExclusiveLock);
 }
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index b4a5f48b4e..93d6a84ac0 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -21,6 +21,7 @@
 #include "access/hash.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/xact.h"
 
 #include "catalog/catalog.h"
@@ -324,9 +325,9 @@ GetAllTablesPublicationRelations(void)
 				BTEqualStrategyNumber, F_CHAREQ,
 				CharGetDatum(RELKIND_RELATION));
 
-	scan = heap_beginscan_catalog(classRel, 1, key);
+	scan = storage_beginscan_catalog(classRel, 1, key);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Oid			relid = HeapTupleGetOid(tuple);
 		Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
@@ -335,7 +336,7 @@ GetAllTablesPublicationRelations(void)
 			result = lappend_oid(result, relid);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(classRel, AccessShareLock);
 
 	return result;
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index 8e16d3b7bc..607692bb09 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -19,6 +19,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/xact.h"
 
 #include "catalog/indexing.h"
@@ -402,12 +403,12 @@ RemoveSubscriptionRel(Oid subid, Oid relid)
 	}
 
 	/* Do the search and delete what we found. */
-	scan = heap_beginscan_catalog(rel, nkeys, skey);
-	while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+	scan = storage_beginscan_catalog(rel, nkeys, skey);
+	while (HeapTupleIsValid(tup = storage_getnext(scan, ForwardScanDirection)))
 	{
 		CatalogTupleDelete(rel, &tup->t_self);
 	}
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	heap_close(rel, RowExclusiveLock);
 }
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 27362cbbbf..7e40532b1d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -21,6 +21,7 @@
 #include "access/multixact.h"
 #include "access/relscan.h"
 #include "access/rewriteheap.h"
+#include "access/storageam.h"
 #include "access/storageamapi.h"
 #include "access/transam.h"
 #include "access/tuptoaster.h"
@@ -913,7 +914,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	}
 	else
 	{
-		heapScan = heap_beginscan(OldHeap, SnapshotAny, 0, (ScanKey) NULL);
+		heapScan = storage_beginscan(OldHeap, SnapshotAny, 0, (ScanKey) NULL);
 		indexScan = NULL;
 	}
 
@@ -963,7 +964,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 		}
 		else
 		{
-			tuple = heap_getnext(heapScan, ForwardScanDirection);
+			tuple = storage_getnext(heapScan, ForwardScanDirection);
 			if (tuple == NULL)
 				break;
 
@@ -1049,7 +1050,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	if (indexScan != NULL)
 		index_endscan(indexScan);
 	if (heapScan != NULL)
-		heap_endscan(heapScan);
+		storage_endscan(heapScan);
 
 	/*
 	 * In scan-and-sort mode, complete the sort, then read out all live tuples
@@ -1686,8 +1687,8 @@ get_tables_to_cluster(MemoryContext cluster_context)
 				Anum_pg_index_indisclustered,
 				BTEqualStrategyNumber, F_BOOLEQ,
 				BoolGetDatum(true));
-	scan = heap_beginscan_catalog(indRelation, 1, &entry);
-	while ((indexTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	scan = storage_beginscan_catalog(indRelation, 1, &entry);
+	while ((indexTuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		index = (Form_pg_index) GETSTRUCT(indexTuple);
 
@@ -1707,7 +1708,7 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 		MemoryContextSwitchTo(old_context);
 	}
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	relation_close(indRelation, AccessShareLock);
 
diff --git a/src/backend/commands/constraint.c b/src/backend/commands/constraint.c
index 90f19ad3dd..cd72efc973 100644
--- a/src/backend/commands/constraint.c
+++ b/src/backend/commands/constraint.c
@@ -13,6 +13,7 @@
  */
 #include "postgres.h"
 
+#include "access/storageam.h"
 #include "catalog/index.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
@@ -102,7 +103,7 @@ unique_key_recheck(PG_FUNCTION_ARGS)
 	 * removed.
 	 */
 	tmptid = new_row->t_self;
-	if (!heap_hot_search(&tmptid, trigdata->tg_relation, SnapshotSelf, NULL))
+	if (!storage_hot_search(&tmptid, trigdata->tg_relation, SnapshotSelf, NULL))
 	{
 		/*
 		 * 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 2dfc6e0a20..901894c90a 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2038,10 +2038,10 @@ CopyTo(CopyState cstate)
 		values = (Datum *) palloc(num_phys_attrs * sizeof(Datum));
 		nulls = (bool *) palloc(num_phys_attrs * sizeof(bool));
 
-		scandesc = heap_beginscan(cstate->rel, GetActiveSnapshot(), 0, NULL);
+		scandesc = storage_beginscan(cstate->rel, GetActiveSnapshot(), 0, NULL);
 
 		processed = 0;
-		while ((tuple = heap_getnext(scandesc, ForwardScanDirection)) != NULL)
+		while ((tuple = storage_getnext(scandesc, ForwardScanDirection)) != NULL)
 		{
 			CHECK_FOR_INTERRUPTS();
 
@@ -2053,7 +2053,7 @@ CopyTo(CopyState cstate)
 			processed++;
 		}
 
-		heap_endscan(scandesc);
+		storage_endscan(scandesc);
 
 		pfree(values);
 		pfree(nulls);
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 0b111fc5cf..91732f911f 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -26,6 +26,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -590,8 +591,8 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 		 * each one to the new database.
 		 */
 		rel = heap_open(TableSpaceRelationId, AccessShareLock);
-		scan = heap_beginscan_catalog(rel, 0, NULL);
-		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		scan = storage_beginscan_catalog(rel, 0, NULL);
+		while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 		{
 			Oid			srctablespace = HeapTupleGetOid(tuple);
 			Oid			dsttablespace;
@@ -643,7 +644,7 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 								  XLOG_DBASE_CREATE | XLR_SPECIAL_REL_UPDATE);
 			}
 		}
-		heap_endscan(scan);
+		storage_endscan(scan);
 		heap_close(rel, AccessShareLock);
 
 		/*
@@ -1875,8 +1876,8 @@ remove_dbtablespaces(Oid db_id)
 	HeapTuple	tuple;
 
 	rel = heap_open(TableSpaceRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(rel, 0, NULL);
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	scan = storage_beginscan_catalog(rel, 0, NULL);
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Oid			dsttablespace = HeapTupleGetOid(tuple);
 		char	   *dstpath;
@@ -1917,7 +1918,7 @@ remove_dbtablespaces(Oid db_id)
 		pfree(dstpath);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(rel, AccessShareLock);
 }
 
@@ -1942,8 +1943,8 @@ check_db_file_conflict(Oid db_id)
 	HeapTuple	tuple;
 
 	rel = heap_open(TableSpaceRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(rel, 0, NULL);
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	scan = storage_beginscan_catalog(rel, 0, NULL);
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Oid			dsttablespace = HeapTupleGetOid(tuple);
 		char	   *dstpath;
@@ -1966,7 +1967,7 @@ check_db_file_conflict(Oid db_id)
 		pfree(dstpath);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(rel, AccessShareLock);
 
 	return result;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9e6ba92008..22c0201481 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -18,6 +18,7 @@
 #include "access/amapi.h"
 #include "access/htup_details.h"
 #include "access/reloptions.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
@@ -1967,8 +1968,8 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 	 * rels will be processed indirectly by reindex_relation).
 	 */
 	relationRelation = heap_open(RelationRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(relationRelation, num_keys, scan_keys);
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	scan = storage_beginscan_catalog(relationRelation, num_keys, scan_keys);
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Form_pg_class classtuple = (Form_pg_class) GETSTRUCT(tuple);
 		Oid			relid = HeapTupleGetOid(tuple);
@@ -2008,7 +2009,7 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 
 		MemoryContextSwitchTo(old);
 	}
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(relationRelation, AccessShareLock);
 
 	/* Now reindex each rel in a separate transaction */
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 9722d92849..13ea3a2938 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4551,7 +4551,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		 * checking all the constraints.
 		 */
 		snapshot = RegisterSnapshot(GetLatestSnapshot());
-		scan = heap_beginscan(oldrel, snapshot, 0, NULL);
+		scan = storage_beginscan(oldrel, snapshot, 0, NULL);
 
 		/*
 		 * Switch to per-tuple memory context and reset it for each tuple
@@ -4559,7 +4559,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		 */
 		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
 
-		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 		{
 			if (tab->rewrite > 0)
 			{
@@ -4673,7 +4673,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 
 		MemoryContextSwitchTo(oldCxt);
-		heap_endscan(scan);
+		storage_endscan(scan);
 		UnregisterSnapshot(snapshot);
 
 		ExecDropSingleTupleTableSlot(oldslot);
@@ -5076,9 +5076,9 @@ find_typed_table_dependencies(Oid typeOid, const char *typeName, DropBehavior be
 				BTEqualStrategyNumber, F_OIDEQ,
 				ObjectIdGetDatum(typeOid));
 
-	scan = heap_beginscan_catalog(classRel, 1, key);
+	scan = storage_beginscan_catalog(classRel, 1, key);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		if (behavior == DROP_RESTRICT)
 			ereport(ERROR,
@@ -5090,7 +5090,7 @@ find_typed_table_dependencies(Oid typeOid, const char *typeName, DropBehavior be
 			result = lappend_oid(result, HeapTupleGetOid(tuple));
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(classRel, AccessShareLock);
 
 	return result;
@@ -8260,7 +8260,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	econtext->ecxt_scantuple = slot;
 
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	scan = heap_beginscan(rel, snapshot, 0, NULL);
+	scan = storage_beginscan(rel, snapshot, 0, NULL);
 
 	/*
 	 * Switch to per-tuple memory context and reset it for each tuple
@@ -8268,7 +8268,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	 */
 	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
@@ -8283,7 +8283,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	}
 
 	MemoryContextSwitchTo(oldcxt);
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 	ExecDropSingleTupleTableSlot(slot);
 	FreeExecutorState(estate);
@@ -8338,9 +8338,9 @@ validateForeignKeyConstraint(char *conname,
 	 * ereport(ERROR) and that's that.
 	 */
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	scan = heap_beginscan(rel, snapshot, 0, NULL);
+	scan = storage_beginscan(rel, snapshot, 0, NULL);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		FunctionCallInfoData fcinfo;
 		TriggerData trigdata;
@@ -8369,7 +8369,7 @@ validateForeignKeyConstraint(char *conname,
 		RI_FKey_check_ins(&fcinfo);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 }
 
@@ -10874,8 +10874,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 				ObjectIdGetDatum(orig_tablespaceoid));
 
 	rel = heap_open(RelationRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(rel, 1, key);
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	scan = storage_beginscan_catalog(rel, 1, key);
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Oid			relOid = HeapTupleGetOid(tuple);
 		Form_pg_class relForm;
@@ -10934,7 +10934,7 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 		relations = lappend_oid(relations, relOid);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(rel, AccessShareLock);
 
 	if (relations == NIL)
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 8cb834c271..67648db528 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -53,6 +53,7 @@
 #include "access/heapam.h"
 #include "access/reloptions.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "access/xlog.h"
@@ -416,8 +417,8 @@ DropTableSpace(DropTableSpaceStmt *stmt)
 				Anum_pg_tablespace_spcname,
 				BTEqualStrategyNumber, F_NAMEEQ,
 				CStringGetDatum(tablespacename));
-	scandesc = heap_beginscan_catalog(rel, 1, entry);
-	tuple = heap_getnext(scandesc, ForwardScanDirection);
+	scandesc = storage_beginscan_catalog(rel, 1, entry);
+	tuple = storage_getnext(scandesc, ForwardScanDirection);
 
 	if (!HeapTupleIsValid(tuple))
 	{
@@ -434,7 +435,7 @@ DropTableSpace(DropTableSpaceStmt *stmt)
 					(errmsg("tablespace \"%s\" does not exist, skipping",
 							tablespacename)));
 			/* XXX I assume I need one or both of these next two calls */
-			heap_endscan(scandesc);
+			storage_endscan(scandesc);
 			heap_close(rel, NoLock);
 		}
 		return;
@@ -461,7 +462,7 @@ DropTableSpace(DropTableSpaceStmt *stmt)
 	 */
 	CatalogTupleDelete(rel, &tuple->t_self);
 
-	heap_endscan(scandesc);
+	storage_endscan(scandesc);
 
 	/*
 	 * Remove any comments or security labels on this tablespace.
@@ -925,8 +926,8 @@ RenameTableSpace(const char *oldname, const char *newname)
 				Anum_pg_tablespace_spcname,
 				BTEqualStrategyNumber, F_NAMEEQ,
 				CStringGetDatum(oldname));
-	scan = heap_beginscan_catalog(rel, 1, entry);
-	tup = heap_getnext(scan, ForwardScanDirection);
+	scan = storage_beginscan_catalog(rel, 1, entry);
+	tup = storage_getnext(scan, ForwardScanDirection);
 	if (!HeapTupleIsValid(tup))
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
@@ -937,7 +938,7 @@ RenameTableSpace(const char *oldname, const char *newname)
 	newtuple = heap_copytuple(tup);
 	newform = (Form_pg_tablespace) GETSTRUCT(newtuple);
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	/* Must be owner */
 	if (!pg_tablespace_ownercheck(HeapTupleGetOid(newtuple), GetUserId()))
@@ -955,15 +956,15 @@ RenameTableSpace(const char *oldname, const char *newname)
 				Anum_pg_tablespace_spcname,
 				BTEqualStrategyNumber, F_NAMEEQ,
 				CStringGetDatum(newname));
-	scan = heap_beginscan_catalog(rel, 1, entry);
-	tup = heap_getnext(scan, ForwardScanDirection);
+	scan = storage_beginscan_catalog(rel, 1, entry);
+	tup = storage_getnext(scan, ForwardScanDirection);
 	if (HeapTupleIsValid(tup))
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("tablespace \"%s\" already exists",
 						newname)));
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	/* OK, update the entry */
 	namestrcpy(&(newform->spcname), newname);
@@ -1005,8 +1006,8 @@ AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt)
 				Anum_pg_tablespace_spcname,
 				BTEqualStrategyNumber, F_NAMEEQ,
 				CStringGetDatum(stmt->tablespacename));
-	scandesc = heap_beginscan_catalog(rel, 1, entry);
-	tup = heap_getnext(scandesc, ForwardScanDirection);
+	scandesc = storage_beginscan_catalog(rel, 1, entry);
+	tup = storage_getnext(scandesc, ForwardScanDirection);
 	if (!HeapTupleIsValid(tup))
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
@@ -1047,7 +1048,7 @@ AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt)
 	heap_freetuple(newtuple);
 
 	/* Conclude heap scan. */
-	heap_endscan(scandesc);
+	storage_endscan(scandesc);
 	heap_close(rel, NoLock);
 
 	return tablespaceoid;
@@ -1396,8 +1397,8 @@ get_tablespace_oid(const char *tablespacename, bool missing_ok)
 				Anum_pg_tablespace_spcname,
 				BTEqualStrategyNumber, F_NAMEEQ,
 				CStringGetDatum(tablespacename));
-	scandesc = heap_beginscan_catalog(rel, 1, entry);
-	tuple = heap_getnext(scandesc, ForwardScanDirection);
+	scandesc = storage_beginscan_catalog(rel, 1, entry);
+	tuple = storage_getnext(scandesc, ForwardScanDirection);
 
 	/* We assume that there can be at most one matching tuple */
 	if (HeapTupleIsValid(tuple))
@@ -1405,7 +1406,7 @@ get_tablespace_oid(const char *tablespacename, bool missing_ok)
 	else
 		result = InvalidOid;
 
-	heap_endscan(scandesc);
+	storage_endscan(scandesc);
 	heap_close(rel, AccessShareLock);
 
 	if (!OidIsValid(result) && !missing_ok)
@@ -1442,8 +1443,8 @@ get_tablespace_name(Oid spc_oid)
 				ObjectIdAttributeNumber,
 				BTEqualStrategyNumber, F_OIDEQ,
 				ObjectIdGetDatum(spc_oid));
-	scandesc = heap_beginscan_catalog(rel, 1, entry);
-	tuple = heap_getnext(scandesc, ForwardScanDirection);
+	scandesc = storage_beginscan_catalog(rel, 1, entry);
+	tuple = storage_getnext(scandesc, ForwardScanDirection);
 
 	/* We assume that there can be at most one matching tuple */
 	if (HeapTupleIsValid(tuple))
@@ -1451,7 +1452,7 @@ get_tablespace_name(Oid spc_oid)
 	else
 		result = NULL;
 
-	heap_endscan(scandesc);
+	storage_endscan(scandesc);
 	heap_close(rel, AccessShareLock);
 
 	return result;
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index a40b3cf752..4d7833276c 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -32,6 +32,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/xact.h"
 #include "catalog/binary_upgrade.h"
 #include "catalog/catalog.h"
@@ -2387,8 +2388,8 @@ AlterDomainNotNull(List *names, bool notNull)
 
 			/* Scan all tuples in this relation */
 			snapshot = RegisterSnapshot(GetLatestSnapshot());
-			scan = heap_beginscan(testrel, snapshot, 0, NULL);
-			while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+			scan = storage_beginscan(testrel, snapshot, 0, NULL);
+			while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 			{
 				int			i;
 
@@ -2417,7 +2418,7 @@ AlterDomainNotNull(List *names, bool notNull)
 					}
 				}
 			}
-			heap_endscan(scan);
+			storage_endscan(scan);
 			UnregisterSnapshot(snapshot);
 
 			/* Close each rel after processing, but keep lock */
@@ -2783,8 +2784,8 @@ validateDomainConstraint(Oid domainoid, char *ccbin)
 
 		/* Scan all tuples in this relation */
 		snapshot = RegisterSnapshot(GetLatestSnapshot());
-		scan = heap_beginscan(testrel, snapshot, 0, NULL);
-		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		scan = storage_beginscan(testrel, snapshot, 0, NULL);
+		while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 		{
 			int			i;
 
@@ -2827,7 +2828,7 @@ validateDomainConstraint(Oid domainoid, char *ccbin)
 
 			ResetExprContext(econtext);
 		}
-		heap_endscan(scan);
+		storage_endscan(scan);
 		UnregisterSnapshot(snapshot);
 
 		/* Hold relation lock till commit (XXX bad for concurrency) */
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7aca69a0ba..c742753743 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -28,6 +28,7 @@
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/multixact.h"
+#include "access/storageam.h"
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
@@ -533,9 +534,9 @@ get_all_vacuum_rels(void)
 
 	pgclass = heap_open(RelationRelationId, AccessShareLock);
 
-	scan = heap_beginscan_catalog(pgclass, 0, NULL);
+	scan = storage_beginscan_catalog(pgclass, 0, NULL);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
 		MemoryContext oldcontext;
@@ -562,7 +563,7 @@ get_all_vacuum_rels(void)
 		MemoryContextSwitchTo(oldcontext);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(pgclass, AccessShareLock);
 
 	return vacrels;
@@ -1214,9 +1215,9 @@ vac_truncate_clog(TransactionId frozenXID,
 	 */
 	relation = heap_open(DatabaseRelationId, AccessShareLock);
 
-	scan = heap_beginscan_catalog(relation, 0, NULL);
+	scan = storage_beginscan_catalog(relation, 0, NULL);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		volatile FormData_pg_database *dbform = (Form_pg_database) GETSTRUCT(tuple);
 		TransactionId datfrozenxid = dbform->datfrozenxid;
@@ -1253,7 +1254,7 @@ vac_truncate_clog(TransactionId frozenXID,
 		}
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	heap_close(relation, AccessShareLock);
 
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index 9e78421978..dced1d8fac 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -544,7 +544,7 @@ static bool
 IndexSupportsBackwardScan(Oid indexid)
 {
 	bool		result;
-	HeapTuple	ht_idxrel;
+	StorageTuple ht_idxrel;
 	Form_pg_class idxrelrec;
 	IndexAmRoutine *amroutine;
 
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 1038957c59..b371bb7f41 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -650,7 +650,7 @@ check_exclusion_or_unique_constraint(Relation heap, Relation index,
 	Oid		   *index_collations = index->rd_indcollation;
 	int			index_natts = index->rd_index->indnatts;
 	IndexScanDesc index_scan;
-	HeapTuple	tup;
+	StorageTuple tup;
 	ScanKeyData scankeys[INDEX_MAX_KEYS];
 	SnapshotData DirtySnapshot;
 	int			i;
@@ -732,12 +732,13 @@ retry:
 		bool		existing_isnull[INDEX_MAX_KEYS];
 		char	   *error_new;
 		char	   *error_existing;
+		tuple_data	t_data = storage_tuple_get_data(heap, tup, TID);
 
 		/*
 		 * Ignore the entry for the tuple we're trying to check.
 		 */
 		if (ItemPointerIsValid(tupleid) &&
-			ItemPointerEquals(tupleid, &tup->t_self))
+			ItemPointerEquals(tupleid, &(t_data.tid)))
 		{
 			if (found_self)		/* should not happen */
 				elog(ERROR, "found self tuple multiple times in index \"%s\"",
@@ -785,7 +786,8 @@ retry:
 			  DirtySnapshot.speculativeToken &&
 			  TransactionIdPrecedes(GetCurrentTransactionId(), xwait))))
 		{
-			ctid_wait = tup->t_data->t_ctid;
+			t_data = storage_tuple_get_data(heap, tup, CTID);
+			ctid_wait = t_data.tid;
 			reason_wait = indexInfo->ii_ExclusionOps ?
 				XLTW_RecheckExclusionConstr : XLTW_InsertIndex;
 			index_endscan(index_scan);
@@ -805,7 +807,10 @@ retry:
 		{
 			conflict = true;
 			if (conflictTid)
-				*conflictTid = tup->t_self;
+			{
+				t_data = storage_tuple_get_data(heap, tup, TID);
+				*conflictTid = t_data.tid;
+			}
 			break;
 		}
 
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index c89631fe00..8a658109d8 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -118,7 +118,7 @@ RelationFindReplTupleByIndex(Relation rel, Oid idxoid,
 							 TupleTableSlot *searchslot,
 							 TupleTableSlot *outslot)
 {
-	HeapTuple	scantuple;
+	StorageTuple scantuple;
 	ScanKeyData skey[INDEX_MAX_KEYS];
 	IndexScanDesc scan;
 	SnapshotData snap;
@@ -228,8 +228,7 @@ RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 						 TupleTableSlot *searchslot, TupleTableSlot *outslot)
 {
 	TupleTableSlot *scanslot;
-	HeapTuple	scantuple;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	SnapshotData snap;
 	TransactionId xwait;
 	bool		found;
@@ -239,19 +238,19 @@ RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 
 	/* Start a heap scan. */
 	InitDirtySnapshot(snap);
-	scan = heap_beginscan(rel, &snap, 0, NULL);
+	scan = storage_beginscan(rel, &snap, 0, NULL);
 
 	scanslot = MakeSingleTupleTableSlot(desc);
 
 retry:
 	found = false;
 
-	heap_rescan(scan, NULL);
+	storage_rescan(scan, NULL);
 
 	/* Try to find the tuple */
-	while ((scantuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((scanslot = storage_getnextslot(scan, ForwardScanDirection, scanslot))
+		   && !TupIsNull(scanslot))
 	{
-		ExecStoreTuple(scantuple, scanslot, InvalidBuffer, false);
 		if (!ExecSlotCompare(scanslot, searchslot))
 			continue;
 
@@ -313,7 +312,7 @@ retry:
 		}
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	ExecDropSingleTupleTableSlot(scanslot);
 
 	return found;
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index ba53eb85cf..bd4e3b7e81 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -682,7 +682,7 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 Datum
 ExecFetchSlotTupleDatum(TupleTableSlot *slot)
 {
-	HeapTuple	tup;
+	StorageTuple tup;
 	TupleDesc	tupdesc;
 
 	/* Fetch slot's contents in regular-physical-tuple form */
@@ -766,7 +766,7 @@ ExecHeapifySlot(TupleTableSlot *slot)
 TupleTableSlot *
 ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
 {
-	HeapTuple	newTuple;
+	StorageTuple newTuple;
 	MemoryContext oldContext;
 
 	/*
@@ -1086,7 +1086,7 @@ TupleDescGetAttInMetadata(TupleDesc tupdesc)
  * values is an array of C strings, one for each attribute of the return tuple.
  * A NULL string pointer indicates we want to create a NULL field.
  */
-HeapTuple
+StorageTuple
 BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values)
 {
 	TupleDesc	tupdesc = attinmeta->tupdesc;
@@ -1094,7 +1094,7 @@ BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values)
 	Datum	   *dvalues;
 	bool	   *nulls;
 	int			i;
-	HeapTuple	tuple;
+	StorageTuple tuple;
 
 	dvalues = (Datum *) palloc(natts * sizeof(Datum));
 	nulls = (bool *) palloc(natts * sizeof(bool));
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 7e249f575f..7a28ebdfe7 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -181,7 +181,7 @@ static void sqlfunction_destroy(DestReceiver *self);
  * polymorphic arguments.
  */
 SQLFunctionParseInfoPtr
-prepare_sql_fn_parse_info(HeapTuple procedureTuple,
+prepare_sql_fn_parse_info(StorageTuple procedureTuple,
 						  Node *call_expr,
 						  Oid inputCollation)
 {
@@ -598,7 +598,7 @@ init_sql_fcache(FmgrInfo *finfo, Oid collation, bool lazyEvalOK)
 	MemoryContext fcontext;
 	MemoryContext oldcontext;
 	Oid			rettype;
-	HeapTuple	procedureTuple;
+	StorageTuple procedureTuple;
 	Form_pg_proc procedureStruct;
 	SQLFunctionCachePtr fcache;
 	List	   *raw_parsetree_list;
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 46ee880415..f119c55dd1 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -3139,7 +3139,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 		Oid			inputTypes[FUNC_MAX_ARGS];
 		int			numArguments;
 		int			numDirectArgs;
-		HeapTuple	aggTuple;
+		StorageTuple aggTuple;
 		Form_pg_aggregate aggform;
 		AclResult	aclresult;
 		Oid			transfn_oid,
@@ -3264,7 +3264,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 
 		/* Check that aggregate owner has permission to call component fns */
 		{
-			HeapTuple	procTuple;
+			StorageTuple procTuple;
 			Oid			aggOwner;
 
 			procTuple = SearchSysCache1(PROCOID,
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index 5f11c94713..5c1da5c5df 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -38,6 +38,7 @@
 #include <math.h>
 
 #include "access/relscan.h"
+#include "access/storageam.h"
 #include "access/transam.h"
 #include "access/visibilitymap.h"
 #include "executor/execdebug.h"
@@ -433,8 +434,8 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 			HeapTupleData heapTuple;
 
 			ItemPointerSet(&tid, page, offnum);
-			if (heap_hot_search_buffer(&tid, scan->rs_rd, buffer, snapshot,
-									   &heapTuple, NULL, true))
+			if (storage_hot_search_buffer(&tid, scan->rs_rd, buffer, snapshot,
+										  &heapTuple, NULL, true))
 				scan->rs_vistuples[ntup++] = ItemPointerGetOffsetNumber(&tid);
 		}
 	}
@@ -747,7 +748,7 @@ ExecReScanBitmapHeapScan(BitmapHeapScanState *node)
 	PlanState  *outerPlan = outerPlanState(node);
 
 	/* rescan to release any page pin */
-	heap_rescan(node->ss.ss_currentScanDesc, NULL);
+	storage_rescan(node->ss.ss_currentScanDesc, NULL);
 
 	/* release bitmaps and buffers if any */
 	if (node->tbmiterator)
@@ -837,7 +838,7 @@ ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 	/*
 	 * close heap scan
 	 */
-	heap_endscan(scanDesc);
+	storage_endscan(scanDesc);
 
 	/*
 	 * close the heap relation.
@@ -952,10 +953,10 @@ ExecInitBitmapHeapScan(BitmapHeapScan *node, EState *estate, int eflags)
 	 * Even though we aren't going to do a conventional seqscan, it is useful
 	 * to create a HeapScanDesc --- most of the fields in it are usable.
 	 */
-	scanstate->ss.ss_currentScanDesc = heap_beginscan_bm(currentRelation,
-														 estate->es_snapshot,
-														 0,
-														 NULL);
+	scanstate->ss.ss_currentScanDesc = storage_beginscan_bm(currentRelation,
+															estate->es_snapshot,
+															0,
+															NULL);
 
 	/*
 	 * get the scan type from the relation descriptor.
@@ -1123,5 +1124,5 @@ ExecBitmapHeapInitializeWorker(BitmapHeapScanState *node,
 	node->pstate = pstate;
 
 	snapshot = RestoreSnapshot(pstate->phs_snapshot_data);
-	heap_update_snapshot(node->ss.ss_currentScanDesc, snapshot);
+	storage_update_snapshot(node->ss.ss_currentScanDesc, snapshot);
 }
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index b9cf3037b1..4f2e23e1e7 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -62,9 +62,9 @@ ForeignNext(ForeignScanState *node)
 	 */
 	if (plan->fsSystemCol && !TupIsNull(slot))
 	{
-		HeapTuple	tup = ExecHeapifySlot(slot);
-
-		tup->t_tableOid = RelationGetRelid(node->ss.ss_currentRelation);
+		ExecMaterializeSlot(slot);
+		ExecSlotUpdateTupleTableoid(slot,
+									RelationGetRelid(node->ss.ss_currentRelation));
 	}
 
 	return slot;
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index 89266b5371..fdb3d6ccab 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -46,7 +46,7 @@
 
 static TupleTableSlot *ExecGather(PlanState *pstate);
 static TupleTableSlot *gather_getnext(GatherState *gatherstate);
-static HeapTuple gather_readnext(GatherState *gatherstate);
+static StorageTuple gather_readnext(GatherState *gatherstate);
 static void ExecShutdownGatherWorkers(GatherState *node);
 
 
@@ -254,7 +254,7 @@ gather_getnext(GatherState *gatherstate)
 	PlanState  *outerPlan = outerPlanState(gatherstate);
 	TupleTableSlot *outerTupleSlot;
 	TupleTableSlot *fslot = gatherstate->funnel_slot;
-	HeapTuple	tup;
+	StorageTuple tup;
 
 	while (gatherstate->nreaders > 0 || gatherstate->need_to_scan_locally)
 	{
@@ -298,7 +298,7 @@ gather_getnext(GatherState *gatherstate)
 /*
  * Attempt to read a tuple from one of our parallel workers.
  */
-static HeapTuple
+static StorageTuple
 gather_readnext(GatherState *gatherstate)
 {
 	int			nvisited = 0;
@@ -306,7 +306,7 @@ gather_readnext(GatherState *gatherstate)
 	for (;;)
 	{
 		TupleQueueReader *reader;
-		HeapTuple	tup;
+		StorageTuple tup;
 		bool		readerdone;
 
 		/* Check for async events, particularly messages from workers. */
diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c
index a3e34c6980..dc953c350f 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -45,7 +45,7 @@
  */
 typedef struct GMReaderTupleBuffer
 {
-	HeapTuple  *tuple;			/* array of length MAX_TUPLE_STORE */
+	StorageTuple *tuple;		/* array of length MAX_TUPLE_STORE */
 	int			nTuples;		/* number of tuples currently stored */
 	int			readCounter;	/* index of next tuple to extract */
 	bool		done;			/* true if reader is known exhausted */
@@ -54,8 +54,8 @@ typedef struct GMReaderTupleBuffer
 static TupleTableSlot *ExecGatherMerge(PlanState *pstate);
 static int32 heap_compare_slots(Datum a, Datum b, void *arg);
 static TupleTableSlot *gather_merge_getnext(GatherMergeState *gm_state);
-static HeapTuple gm_readnext_tuple(GatherMergeState *gm_state, int nreader,
-				  bool nowait, bool *done);
+static StorageTuple gm_readnext_tuple(GatherMergeState *gm_state, int nreader,
+									  bool nowait, bool *done);
 static void ExecShutdownGatherMergeWorkers(GatherMergeState *node);
 static void gather_merge_setup(GatherMergeState *gm_state);
 static void gather_merge_init(GatherMergeState *gm_state);
@@ -407,7 +407,7 @@ gather_merge_setup(GatherMergeState *gm_state)
 	{
 		/* Allocate the tuple array with length MAX_TUPLE_STORE */
 		gm_state->gm_tuple_buffers[i].tuple =
-			(HeapTuple *) palloc0(sizeof(HeapTuple) * MAX_TUPLE_STORE);
+			(StorageTuple *) palloc0(sizeof(StorageTuple) * MAX_TUPLE_STORE);
 
 		/* Initialize tuple slot for worker */
 		gm_state->gm_slots[i + 1] = ExecInitExtraTupleSlot(gm_state->ps.state);
@@ -625,7 +625,7 @@ static bool
 gather_merge_readnext(GatherMergeState *gm_state, int reader, bool nowait)
 {
 	GMReaderTupleBuffer *tuple_buffer;
-	HeapTuple	tup;
+	StorageTuple tup;
 
 	/*
 	 * If we're being asked to generate a tuple from the leader, then we just
@@ -700,12 +700,12 @@ gather_merge_readnext(GatherMergeState *gm_state, int reader, bool nowait)
 /*
  * Attempt to read a tuple from given worker.
  */
-static HeapTuple
+static StorageTuple
 gm_readnext_tuple(GatherMergeState *gm_state, int nreader, bool nowait,
 				  bool *done)
 {
 	TupleQueueReader *reader;
-	HeapTuple	tup;
+	StorageTuple tup;
 
 	/* Check for async events, particularly messages from workers. */
 	CHECK_FOR_INTERRUPTS();
diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c
index 9b7f470ee2..af85b3a6b1 100644
--- a/src/backend/executor/nodeIndexonlyscan.c
+++ b/src/backend/executor/nodeIndexonlyscan.c
@@ -117,7 +117,7 @@ IndexOnlyNext(IndexOnlyScanState *node)
 	 */
 	while ((tid = index_getnext_tid(scandesc, direction)) != NULL)
 	{
-		HeapTuple	tuple = NULL;
+		StorageTuple tuple = NULL;
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -186,9 +186,9 @@ IndexOnlyNext(IndexOnlyScanState *node)
 
 		/*
 		 * Fill the scan tuple slot with data from the index.  This might be
-		 * provided in either HeapTuple or IndexTuple format.  Conceivably an
-		 * index AM might fill both fields, in which case we prefer the heap
-		 * format, since it's probably a bit cheaper to fill a slot from.
+		 * provided in either StorageTuple or IndexTuple format.  Conceivably
+		 * an index AM might fill both fields, in which case we prefer the
+		 * heap format, since it's probably a bit cheaper to fill a slot from.
 		 */
 		if (scandesc->xs_hitup)
 		{
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 54fafa5033..bdc0831d35 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -51,7 +51,7 @@
 typedef struct
 {
 	pairingheap_node ph_node;
-	HeapTuple	htup;
+	StorageTuple htup;
 	Datum	   *orderbyvals;
 	bool	   *orderbynulls;
 } ReorderTuple;
@@ -65,9 +65,9 @@ static int cmp_orderbyvals(const Datum *adist, const bool *anulls,
 				IndexScanState *node);
 static int reorderqueue_cmp(const pairingheap_node *a,
 				 const pairingheap_node *b, void *arg);
-static void reorderqueue_push(IndexScanState *node, HeapTuple tuple,
+static void reorderqueue_push(IndexScanState *node, StorageTuple tuple,
 				  Datum *orderbyvals, bool *orderbynulls);
-static HeapTuple reorderqueue_pop(IndexScanState *node);
+static StorageTuple reorderqueue_pop(IndexScanState *node);
 
 
 /* ----------------------------------------------------------------
@@ -84,7 +84,7 @@ IndexNext(IndexScanState *node)
 	ExprContext *econtext;
 	ScanDirection direction;
 	IndexScanDesc scandesc;
-	HeapTuple	tuple;
+	StorageTuple tuple;
 	TupleTableSlot *slot;
 
 	/*
@@ -185,7 +185,7 @@ IndexNextWithReorder(IndexScanState *node)
 	EState	   *estate;
 	ExprContext *econtext;
 	IndexScanDesc scandesc;
-	HeapTuple	tuple;
+	StorageTuple tuple;
 	TupleTableSlot *slot;
 	ReorderTuple *topmost = NULL;
 	bool		was_exact;
@@ -483,7 +483,7 @@ reorderqueue_cmp(const pairingheap_node *a, const pairingheap_node *b,
  * Helper function to push a tuple to the reorder queue.
  */
 static void
-reorderqueue_push(IndexScanState *node, HeapTuple tuple,
+reorderqueue_push(IndexScanState *node, StorageTuple tuple,
 				  Datum *orderbyvals, bool *orderbynulls)
 {
 	IndexScanDesc scandesc = node->iss_ScanDesc;
@@ -516,10 +516,10 @@ reorderqueue_push(IndexScanState *node, HeapTuple tuple,
 /*
  * Helper function to pop the next tuple from the reorder queue.
  */
-static HeapTuple
+static StorageTuple
 reorderqueue_pop(IndexScanState *node)
 {
-	HeapTuple	result;
+	StorageTuple result;
 	ReorderTuple *topmost;
 	int			i;
 
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index e105f6758e..4dbc67989f 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -29,10 +29,12 @@
 static void InitScanRelation(SampleScanState *node, EState *estate, int eflags);
 static TupleTableSlot *SampleNext(SampleScanState *node);
 static void tablesample_init(SampleScanState *scanstate);
-static HeapTuple tablesample_getnext(SampleScanState *scanstate);
-static bool SampleTupleVisible(HeapTuple tuple, OffsetNumber tupoffset,
+static StorageTuple tablesample_getnext(SampleScanState *scanstate);
+static bool SampleTupleVisible(StorageTuple tuple, OffsetNumber tupoffset,
 				   HeapScanDesc scan);
 
+/* hari */
+
 /* ----------------------------------------------------------------
  *						Scan Support
  * ----------------------------------------------------------------
@@ -47,7 +49,7 @@ static bool SampleTupleVisible(HeapTuple tuple, OffsetNumber tupoffset,
 static TupleTableSlot *
 SampleNext(SampleScanState *node)
 {
-	HeapTuple	tuple;
+	StorageTuple tuple;
 	TupleTableSlot *slot;
 
 	/*
@@ -244,7 +246,7 @@ ExecEndSampleScan(SampleScanState *node)
 	 * close heap scan
 	 */
 	if (node->ss.ss_currentScanDesc)
-		heap_endscan(node->ss.ss_currentScanDesc);
+		storage_endscan(node->ss.ss_currentScanDesc);
 
 	/*
 	 * close the heap relation.
@@ -349,19 +351,19 @@ tablesample_init(SampleScanState *scanstate)
 	if (scanstate->ss.ss_currentScanDesc == NULL)
 	{
 		scanstate->ss.ss_currentScanDesc =
-			heap_beginscan_sampling(scanstate->ss.ss_currentRelation,
-									scanstate->ss.ps.state->es_snapshot,
-									0, NULL,
-									scanstate->use_bulkread,
-									allow_sync,
-									scanstate->use_pagemode);
+			storage_beginscan_sampling(scanstate->ss.ss_currentRelation,
+									   scanstate->ss.ps.state->es_snapshot,
+									   0, NULL,
+									   scanstate->use_bulkread,
+									   allow_sync,
+									   scanstate->use_pagemode);
 	}
 	else
 	{
-		heap_rescan_set_params(scanstate->ss.ss_currentScanDesc, NULL,
-							   scanstate->use_bulkread,
-							   allow_sync,
-							   scanstate->use_pagemode);
+		storage_rescan_set_params(scanstate->ss.ss_currentScanDesc, NULL,
+								  scanstate->use_bulkread,
+								  allow_sync,
+								  scanstate->use_pagemode);
 	}
 
 	pfree(params);
@@ -376,7 +378,7 @@ tablesample_init(SampleScanState *scanstate)
  * Note: an awful lot of this is copied-and-pasted from heapam.c.  It would
  * perhaps be better to refactor to share more code.
  */
-static HeapTuple
+static StorageTuple
 tablesample_getnext(SampleScanState *scanstate)
 {
 	TsmRoutine *tsm = scanstate->tsmroutine;
@@ -554,7 +556,7 @@ tablesample_getnext(SampleScanState *scanstate)
  * Check visibility of the tuple.
  */
 static bool
-SampleTupleVisible(HeapTuple tuple, OffsetNumber tupoffset, HeapScanDesc scan)
+SampleTupleVisible(StorageTuple tuple, OffsetNumber tupoffset, HeapScanDesc scan) //hari
 {
 	if (scan->rs_pageatatime)
 	{
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index 58631378d5..48c7ab7268 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -28,6 +28,7 @@
 #include "postgres.h"
 
 #include "access/relscan.h"
+#include "access/storageam.h"
 #include "executor/execdebug.h"
 #include "executor/nodeSeqscan.h"
 #include "utils/rel.h"
@@ -49,8 +50,7 @@ static TupleTableSlot *SeqNext(SeqScanState *node);
 static TupleTableSlot *
 SeqNext(SeqScanState *node)
 {
-	HeapTuple	tuple;
-	HeapScanDesc scandesc;
+	StorageScanDesc scandesc;
 	EState	   *estate;
 	ScanDirection direction;
 	TupleTableSlot *slot;
@@ -69,35 +69,16 @@ SeqNext(SeqScanState *node)
 		 * We reach here if the scan is not parallel, or if we're executing a
 		 * scan that was intended to be parallel serially.
 		 */
-		scandesc = heap_beginscan(node->ss.ss_currentRelation,
-								  estate->es_snapshot,
-								  0, NULL);
+		scandesc = storage_beginscan(node->ss.ss_currentRelation,
+									 estate->es_snapshot,
+									 0, NULL);
 		node->ss.ss_currentScanDesc = scandesc;
 	}
 
 	/*
 	 * get the next tuple from the table
 	 */
-	tuple = heap_getnext(scandesc, direction);
-
-	/*
-	 * save the tuple and the buffer returned to us by the access methods in
-	 * our scan tuple slot and return the slot.  Note: we pass 'false' because
-	 * tuples returned by heap_getnext() are pointers onto disk pages and were
-	 * not created with palloc() and so should not be pfree()'d.  Note also
-	 * that ExecStoreTuple will increment the refcount of the buffer; the
-	 * refcount will not be dropped until the tuple table slot is cleared.
-	 */
-	if (tuple)
-		ExecStoreTuple(tuple,	/* tuple to store */
-					   slot,	/* slot to store in */
-					   scandesc->rs_cbuf,	/* buffer associated with this
-											 * tuple */
-					   false);	/* don't pfree this pointer */
-	else
-		ExecClearTuple(slot);
-
-	return slot;
+	return storage_getnextslot(scandesc, direction, slot);
 }
 
 /*
@@ -225,7 +206,7 @@ void
 ExecEndSeqScan(SeqScanState *node)
 {
 	Relation	relation;
-	HeapScanDesc scanDesc;
+	StorageScanDesc scanDesc;
 
 	/*
 	 * get information from node
@@ -248,7 +229,7 @@ ExecEndSeqScan(SeqScanState *node)
 	 * close heap scan
 	 */
 	if (scanDesc != NULL)
-		heap_endscan(scanDesc);
+		storage_endscan(scanDesc);
 
 	/*
 	 * close the heap relation.
@@ -270,13 +251,13 @@ ExecEndSeqScan(SeqScanState *node)
 void
 ExecReScanSeqScan(SeqScanState *node)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 
 	scan = node->ss.ss_currentScanDesc;
 
 	if (scan != NULL)
-		heap_rescan(scan,		/* scan desc */
-					NULL);		/* new scan keys */
+		storage_rescan(scan,	/* scan desc */
+					   NULL);	/* new scan keys */
 
 	ExecScanReScan((ScanState *) node);
 }
@@ -323,7 +304,7 @@ ExecSeqScanInitializeDSM(SeqScanState *node,
 								 estate->es_snapshot);
 	shm_toc_insert(pcxt->toc, node->ss.ps.plan->plan_node_id, pscan);
 	node->ss.ss_currentScanDesc =
-		heap_beginscan_parallel(node->ss.ss_currentRelation, pscan);
+		storage_beginscan_parallel(node->ss.ss_currentRelation, pscan);
 }
 
 /* ----------------------------------------------------------------
@@ -355,5 +336,5 @@ ExecSeqScanInitializeWorker(SeqScanState *node,
 
 	pscan = shm_toc_lookup(pwcxt->toc, node->ss.ps.plan->plan_node_id, false);
 	node->ss.ss_currentScanDesc =
-		heap_beginscan_parallel(node->ss.ss_currentRelation, pscan);
+		storage_beginscan_parallel(node->ss.ss_currentRelation, pscan);
 }
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 5492fb3369..27c9eeb900 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -2092,7 +2092,7 @@ initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc,
 {
 	Oid			inputTypes[FUNC_MAX_ARGS];
 	int			numArguments;
-	HeapTuple	aggTuple;
+	StorageTuple aggTuple;
 	Form_pg_aggregate aggform;
 	Oid			aggtranstype;
 	AttrNumber	initvalAttNo;
@@ -2175,7 +2175,7 @@ initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc,
 
 	/* Check that aggregate owner has permission to call component fns */
 	{
-		HeapTuple	procTuple;
+		StorageTuple procTuple;
 		Oid			aggOwner;
 
 		procTuple = SearchSysCache1(PROCOID,
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 4d9b51b947..3658d7ccbb 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -633,11 +633,11 @@ SPI_freeplan(SPIPlanPtr plan)
 	return 0;
 }
 
-HeapTuple
-SPI_copytuple(HeapTuple tuple)
+StorageTuple
+SPI_copytuple(StorageTuple tuple)
 {
 	MemoryContext oldcxt;
-	HeapTuple	ctuple;
+	StorageTuple ctuple;
 
 	if (tuple == NULL)
 	{
@@ -661,7 +661,7 @@ SPI_copytuple(HeapTuple tuple)
 }
 
 HeapTupleHeader
-SPI_returntuple(HeapTuple tuple, TupleDesc tupdesc)
+SPI_returntuple(StorageTuple tuple, TupleDesc tupdesc)
 {
 	MemoryContext oldcxt;
 	HeapTupleHeader dtup;
@@ -692,7 +692,7 @@ SPI_returntuple(HeapTuple tuple, TupleDesc tupdesc)
 	return dtup;
 }
 
-HeapTuple
+StorageTuple
 SPI_modifytuple(Relation rel, HeapTuple tuple, int natts, int *attnum,
 				Datum *Values, const char *Nulls)
 {
@@ -860,7 +860,7 @@ char *
 SPI_gettype(TupleDesc tupdesc, int fnumber)
 {
 	Oid			typoid;
-	HeapTuple	typeTuple;
+	StorageTuple typeTuple;
 	char	   *result;
 
 	SPI_result = 0;
@@ -968,7 +968,7 @@ SPI_datumTransfer(Datum value, bool typByVal, int typLen)
 }
 
 void
-SPI_freetuple(HeapTuple tuple)
+SPI_freetuple(StorageTuple tuple)
 {
 	/* No longer need to worry which context tuple was in... */
 	heap_freetuple(tuple);
@@ -1689,7 +1689,7 @@ spi_dest_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 
 	/* set up initial allocations */
 	tuptable->alloced = tuptable->free = 128;
-	tuptable->vals = (HeapTuple *) palloc(tuptable->alloced * sizeof(HeapTuple));
+	tuptable->vals = (StorageTuple *) palloc(tuptable->alloced * sizeof(StorageTuple));
 	tuptable->tupdesc = CreateTupleDescCopy(typeinfo);
 
 	MemoryContextSwitchTo(oldcxt);
@@ -1720,8 +1720,8 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self)
 		/* Double the size of the pointer array */
 		tuptable->free = tuptable->alloced;
 		tuptable->alloced += tuptable->free;
-		tuptable->vals = (HeapTuple *) repalloc_huge(tuptable->vals,
-													 tuptable->alloced * sizeof(HeapTuple));
+		tuptable->vals = (StorageTuple *) repalloc_huge(tuptable->vals,
+														tuptable->alloced * sizeof(StorageTuple));
 	}
 
 	tuptable->vals[tuptable->alloced - tuptable->free] =
diff --git a/src/backend/executor/tqueue.c b/src/backend/executor/tqueue.c
index 12b9fef894..59ebf85679 100644
--- a/src/backend/executor/tqueue.c
+++ b/src/backend/executor/tqueue.c
@@ -168,7 +168,7 @@ DestroyTupleQueueReader(TupleQueueReader *reader)
  * accumulate bytes from a partially-read message, so it's useful to call
  * this with nowait = true even if nothing is returned.
  */
-HeapTuple
+StorageTuple
 TupleQueueReaderNext(TupleQueueReader *reader, bool nowait, bool *done)
 {
 	HeapTupleData htup;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 75c2362f46..c2f8a7802c 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1883,9 +1883,9 @@ get_database_list(void)
 	(void) GetTransactionSnapshot();
 
 	rel = heap_open(DatabaseRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(rel, 0, NULL);
+	scan = storage_beginscan_catalog(rel, 0, NULL);
 
-	while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+	while (HeapTupleIsValid(tup = storage_getnext(scan, ForwardScanDirection)))
 	{
 		Form_pg_database pgdatabase = (Form_pg_database) GETSTRUCT(tup);
 		avw_dbase  *avdb;
@@ -1912,7 +1912,7 @@ get_database_list(void)
 		MemoryContextSwitchTo(oldcxt);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(rel, AccessShareLock);
 
 	CommitTransactionCommand();
@@ -2043,13 +2043,13 @@ do_autovacuum(void)
 	 * wide tables there might be proportionally much more activity in the
 	 * TOAST table than in its parent.
 	 */
-	relScan = heap_beginscan_catalog(classRel, 0, NULL);
+	relScan = storage_beginscan_catalog(classRel, 0, NULL);
 
 	/*
 	 * On the first pass, we collect main tables to vacuum, and also the main
 	 * table relid to TOAST relid mapping.
 	 */
-	while ((tuple = heap_getnext(relScan, ForwardScanDirection)) != NULL)
+	while ((tuple = storage_getnext(relScan, ForwardScanDirection)) != NULL)
 	{
 		Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
 		PgStat_StatTabEntry *tabentry;
@@ -2135,7 +2135,7 @@ do_autovacuum(void)
 		}
 	}
 
-	heap_endscan(relScan);
+	storage_endscan(relScan);
 
 	/* second pass: check TOAST tables */
 	ScanKeyInit(&key,
@@ -2143,8 +2143,8 @@ do_autovacuum(void)
 				BTEqualStrategyNumber, F_CHAREQ,
 				CharGetDatum(RELKIND_TOASTVALUE));
 
-	relScan = heap_beginscan_catalog(classRel, 1, &key);
-	while ((tuple = heap_getnext(relScan, ForwardScanDirection)) != NULL)
+	relScan = storage_beginscan_catalog(classRel, 1, &key);
+	while ((tuple = storage_getnext(relScan, ForwardScanDirection)) != NULL)
 	{
 		Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
 		PgStat_StatTabEntry *tabentry;
@@ -2190,7 +2190,7 @@ do_autovacuum(void)
 			table_oids = lappend_oid(table_oids, relid);
 	}
 
-	heap_endscan(relScan);
+	storage_endscan(relScan);
 	heap_close(classRel, AccessShareLock);
 
 	/*
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index d13011454c..20f0c3e692 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -36,6 +36,7 @@
 
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/transam.h"
 #include "access/twophase_rmgr.h"
 #include "access/xact.h"
@@ -1221,8 +1222,8 @@ pgstat_collect_oids(Oid catalogid)
 
 	rel = heap_open(catalogid, AccessShareLock);
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	scan = heap_beginscan(rel, snapshot, 0, NULL);
-	while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	scan = storage_beginscan(rel, snapshot, 0, NULL);
+	while ((tup = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Oid			thisoid = HeapTupleGetOid(tup);
 
@@ -1230,7 +1231,7 @@ pgstat_collect_oids(Oid catalogid)
 
 		(void) hash_search(htab, (void *) &thisoid, HASH_ENTER, NULL);
 	}
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 	heap_close(rel, AccessShareLock);
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 2da9129562..3a7c65c74e 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -24,6 +24,7 @@
 #include "access/heapam.h"
 #include "access/htup.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
 #include "access/xact.h"
 
 #include "catalog/pg_subscription.h"
@@ -124,9 +125,9 @@ get_subscription_list(void)
 	(void) GetTransactionSnapshot();
 
 	rel = heap_open(SubscriptionRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(rel, 0, NULL);
+	scan = storage_beginscan_catalog(rel, 0, NULL);
 
-	while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+	while (HeapTupleIsValid(tup = storage_getnext(scan, ForwardScanDirection)))
 	{
 		Form_pg_subscription subform = (Form_pg_subscription) GETSTRUCT(tup);
 		Subscription *sub;
@@ -152,7 +153,7 @@ get_subscription_list(void)
 		MemoryContextSwitchTo(oldcxt);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(rel, AccessShareLock);
 
 	CommitTransactionCommand();
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index abf45fa82b..048f8f316d 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -17,6 +17,7 @@
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/multixact.h"
+#include "access/storageam.h"
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
@@ -435,13 +436,13 @@ DefineQueryRewrite(const char *rulename,
 								RelationGetRelationName(event_relation))));
 
 			snapshot = RegisterSnapshot(GetLatestSnapshot());
-			scanDesc = heap_beginscan(event_relation, snapshot, 0, NULL);
-			if (heap_getnext(scanDesc, ForwardScanDirection) != NULL)
+			scanDesc = storage_beginscan(event_relation, snapshot, 0, NULL);
+			if (storage_getnext(scanDesc, ForwardScanDirection) != NULL)
 				ereport(ERROR,
 						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 						 errmsg("could not convert table \"%s\" to a view because it is not empty",
 								RelationGetRelationName(event_relation))));
-			heap_endscan(scanDesc);
+			storage_endscan(scanDesc);
 			UnregisterSnapshot(snapshot);
 
 			if (event_relation->rd_rel->relhastriggers)
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index f9b330998d..0949310f33 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -22,6 +22,7 @@
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/session.h"
+#include "access/storageam.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "access/xlog.h"
@@ -1212,10 +1213,10 @@ ThereIsAtLeastOneRole(void)
 
 	pg_authid_rel = heap_open(AuthIdRelationId, AccessShareLock);
 
-	scan = heap_beginscan_catalog(pg_authid_rel, 0, NULL);
-	result = (heap_getnext(scan, ForwardScanDirection) != NULL);
+	scan = storage_beginscan_catalog(pg_authid_rel, 0, NULL);
+	result = (storage_getnext(scan, ForwardScanDirection) != NULL);
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(pg_authid_rel, AccessShareLock);
 
 	return result;
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index a55d701c88..c2755cdac0 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -108,26 +108,25 @@ typedef struct ParallelHeapScanDescData *ParallelHeapScanDesc;
 #define HeapScanIsValid(scan) PointerIsValid(scan)
 
 extern HeapScanDesc heap_beginscan(Relation relation, Snapshot snapshot,
-			   int nkeys, ScanKey key);
-extern HeapScanDesc heap_beginscan_catalog(Relation relation, int nkeys,
-					   ScanKey key);
-extern HeapScanDesc heap_beginscan_strat(Relation relation, Snapshot snapshot,
-					 int nkeys, ScanKey key,
-					 bool allow_strat, bool allow_sync);
-extern HeapScanDesc heap_beginscan_bm(Relation relation, Snapshot snapshot,
-				  int nkeys, ScanKey key);
-extern HeapScanDesc heap_beginscan_sampling(Relation relation,
-						Snapshot snapshot, int nkeys, ScanKey key,
-						bool allow_strat, bool allow_sync, bool allow_pagemode);
+			   int nkeys, ScanKey key,
+			   ParallelHeapScanDesc parallel_scan,
+			   bool allow_strat,
+			   bool allow_sync,
+			   bool allow_pagemode,
+			   bool is_bitmapscan,
+			   bool is_samplescan,
+			   bool temp_snap);
 extern void heap_setscanlimits(HeapScanDesc scan, BlockNumber startBlk,
 				   BlockNumber endBlk);
 extern void heapgetpage(HeapScanDesc scan, BlockNumber page);
-extern void heap_rescan(HeapScanDesc scan, ScanKey key);
+extern void heap_rescan(HeapScanDesc scan, ScanKey key, bool set_params,
+			bool allow_strat, bool allow_sync, bool allow_pagemode);
 extern void heap_rescan_set_params(HeapScanDesc scan, ScanKey key,
 					   bool allow_strat, bool allow_sync, bool allow_pagemode);
 extern void heap_endscan(HeapScanDesc scan);
-extern HeapTuple heap_getnext(HeapScanDesc scan, ScanDirection direction);
-
+extern StorageTuple heap_getnext(HeapScanDesc scan, ScanDirection direction);
+extern TupleTableSlot *heap_getnextslot(HeapScanDesc sscan, ScanDirection direction,
+				 TupleTableSlot *slot);
 extern Size heap_parallelscan_estimate(Snapshot snapshot);
 extern void heap_parallelscan_initialize(ParallelHeapScanDesc target,
 							 Relation relation, Snapshot snapshot);
diff --git a/src/include/access/storage_common.h b/src/include/access/storage_common.h
index baba7261d4..92d311450f 100644
--- a/src/include/access/storage_common.h
+++ b/src/include/access/storage_common.h
@@ -27,6 +27,7 @@
 
 /* A physical tuple coming from a storage AM scan */
 typedef void *StorageTuple;
+typedef void *StorageScanDesc;
 
 /* Result codes for HeapTupleSatisfiesVacuum */
 typedef enum
diff --git a/src/include/access/storageam.h b/src/include/access/storageam.h
index d6500ae5f2..f7a2fcf99c 100644
--- a/src/include/access/storageam.h
+++ b/src/include/access/storageam.h
@@ -42,6 +42,34 @@ typedef List *(*InsertIndexTuples) (TupleTableSlot *slot, EState *estate, bool n
 /* Function pointer to let the index tuple delete from storage am */
 typedef void (*DeleteIndexTuples) (Relation rel, ItemPointer tid, TransactionId old_xmin);
 
+extern HeapScanDesc storage_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan);
+
+extern void storage_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
+extern HeapScanDesc storage_beginscan(Relation relation, Snapshot snapshot,
+				  int nkeys, ScanKey key);
+extern HeapScanDesc storage_beginscan_catalog(Relation relation, int nkeys, ScanKey key);
+extern HeapScanDesc storage_beginscan_strat(Relation relation, Snapshot snapshot,
+						int nkeys, ScanKey key,
+						bool allow_strat, bool allow_sync);
+extern HeapScanDesc storage_beginscan_bm(Relation relation, Snapshot snapshot,
+					 int nkeys, ScanKey key);
+extern HeapScanDesc storage_beginscan_sampling(Relation relation, Snapshot snapshot,
+						   int nkeys, ScanKey key,
+						   bool allow_strat, bool allow_sync, bool allow_pagemode);
+
+extern void storage_endscan(HeapScanDesc scan);
+extern void storage_rescan(HeapScanDesc scan, ScanKey key);
+extern void storage_rescan_set_params(HeapScanDesc scan, ScanKey key,
+						  bool allow_strat, bool allow_sync, bool allow_pagemode);
+extern void storage_update_snapshot(HeapScanDesc scan, Snapshot snapshot);
+
+extern StorageTuple storage_getnext(HeapScanDesc sscan, ScanDirection direction);
+extern TupleTableSlot *storage_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *slot);
+
+extern void storage_get_latest_tid(Relation relation,
+					   Snapshot snapshot,
+					   ItemPointer tid);
+
 extern bool storage_fetch(Relation relation,
 			  ItemPointer tid,
 			  Snapshot snapshot,
@@ -50,6 +78,13 @@ extern bool storage_fetch(Relation relation,
 			  bool keep_buf,
 			  Relation stats_relation);
 
+extern bool storage_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
+						  Snapshot snapshot, HeapTuple heapTuple,
+						  bool *all_dead, bool first_call);
+
+extern bool storage_hot_search(ItemPointer tid, Relation relation, Snapshot snapshot,
+				   bool *all_dead);
+
 extern HTSU_Result storage_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * stuple,
 				   CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 				   bool follow_updates,
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
index 03c814783f..6538b25135 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -82,6 +82,39 @@ typedef StorageTuple(*TupleFromDatum_function) (Datum data, Oid tableoid);
 
 typedef void (*RelationSync_function) (Relation relation);
 
+
+typedef HeapScanDesc (*ScanBegin_function) (Relation relation,
+											Snapshot snapshot,
+											int nkeys, ScanKey key,
+											ParallelHeapScanDesc parallel_scan,
+											bool allow_strat,
+											bool allow_sync,
+											bool allow_pagemode,
+											bool is_bitmapscan,
+											bool is_samplescan,
+											bool temp_snap);
+typedef void (*ScanSetlimits_function) (HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
+
+/* must return a TupleTableSlot? */
+typedef StorageTuple(*ScanGetnext_function) (HeapScanDesc scan,
+											 ScanDirection direction);
+
+typedef TupleTableSlot *(*ScanGetnextSlot_function) (HeapScanDesc scan,
+													 ScanDirection direction, TupleTableSlot *slot);
+
+typedef void (*ScanEnd_function) (HeapScanDesc scan);
+
+
+typedef void (*ScanGetpage_function) (HeapScanDesc scan, BlockNumber page);
+typedef void (*ScanRescan_function) (HeapScanDesc scan, ScanKey key, bool set_params,
+									 bool allow_strat, bool allow_sync, bool allow_pagemode);
+typedef void (*ScanUpdateSnapshot_function) (HeapScanDesc scan, Snapshot snapshot);
+
+typedef bool (*HotSearchBuffer_function) (ItemPointer tid, Relation relation,
+										  Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
+										  bool *all_dead, bool first_call);
+
+
 /*
  * API struct for a storage AM.  Note this must be stored in a single palloc'd
  * chunk of memory.
@@ -115,6 +148,17 @@ typedef struct StorageAmRoutine
 
 	RelationSync_function relation_sync;	/* heap_sync */
 
+	/* Operations on relation scans */
+	ScanBegin_function scan_begin;
+	ScanSetlimits_function scansetlimits;
+	ScanGetnext_function scan_getnext;
+	ScanGetnextSlot_function scan_getnextslot;
+	ScanEnd_function scan_end;
+	ScanGetpage_function scan_getpage;
+	ScanRescan_function scan_rescan;
+	ScanUpdateSnapshot_function scan_update_snapshot;
+	HotSearchBuffer_function hot_search_buffer; /* heap_hot_search_buffer */
+
 }			StorageAmRoutine;
 
 extern StorageAmRoutine * GetStorageAmRoutine(Oid amhandler);
diff --git a/src/include/executor/functions.h b/src/include/executor/functions.h
index e7454ee790..0dc23130e4 100644
--- a/src/include/executor/functions.h
+++ b/src/include/executor/functions.h
@@ -22,7 +22,7 @@ typedef struct SQLFunctionParseInfo *SQLFunctionParseInfoPtr;
 
 extern Datum fmgr_sql(PG_FUNCTION_ARGS);
 
-extern SQLFunctionParseInfoPtr prepare_sql_fn_parse_info(HeapTuple procedureTuple,
+extern SQLFunctionParseInfoPtr prepare_sql_fn_parse_info(StorageTuple procedureTuple,
 						  Node *call_expr,
 						  Oid inputCollation);
 
diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h
index 43580c5158..6a5b5bc4f9 100644
--- a/src/include/executor/spi.h
+++ b/src/include/executor/spi.h
@@ -25,7 +25,7 @@ typedef struct SPITupleTable
 	uint64		alloced;		/* # of alloced vals */
 	uint64		free;			/* # of free vals */
 	TupleDesc	tupdesc;		/* tuple descriptor */
-	HeapTuple  *vals;			/* tuples */
+	StorageTuple *vals;			/* tuples */
 	slist_node	next;			/* link for internal bookkeeping */
 	SubTransactionId subid;		/* subxact in which tuptable was created */
 } SPITupleTable;
@@ -117,10 +117,10 @@ extern const char *SPI_result_code_string(int code);
 extern List *SPI_plan_get_plan_sources(SPIPlanPtr plan);
 extern CachedPlan *SPI_plan_get_cached_plan(SPIPlanPtr plan);
 
-extern HeapTuple SPI_copytuple(HeapTuple tuple);
-extern HeapTupleHeader SPI_returntuple(HeapTuple tuple, TupleDesc tupdesc);
-extern HeapTuple SPI_modifytuple(Relation rel, HeapTuple tuple, int natts,
-				int *attnum, Datum *Values, const char *Nulls);
+extern StorageTuple SPI_copytuple(StorageTuple tuple);
+extern HeapTupleHeader SPI_returntuple(StorageTuple tuple, TupleDesc tupdesc);
+extern StorageTuple SPI_modifytuple(Relation rel, HeapTuple tuple, int natts,
+									int *attnum, Datum *Values, const char *Nulls);
 extern int	SPI_fnumber(TupleDesc tupdesc, const char *fname);
 extern char *SPI_fname(TupleDesc tupdesc, int fnumber);
 extern char *SPI_getvalue(HeapTuple tuple, TupleDesc tupdesc, int fnumber);
@@ -133,7 +133,7 @@ extern void *SPI_palloc(Size size);
 extern void *SPI_repalloc(void *pointer, Size size);
 extern void SPI_pfree(void *pointer);
 extern Datum SPI_datumTransfer(Datum value, bool typByVal, int typLen);
-extern void SPI_freetuple(HeapTuple pointer);
+extern void SPI_freetuple(StorageTuple pointer);
 extern void SPI_freetuptable(SPITupleTable *tuptable);
 
 extern Portal SPI_cursor_open(const char *name, SPIPlanPtr plan,
diff --git a/src/include/executor/tqueue.h b/src/include/executor/tqueue.h
index 0fe3639252..58d2152a5f 100644
--- a/src/include/executor/tqueue.h
+++ b/src/include/executor/tqueue.h
@@ -26,7 +26,7 @@ extern DestReceiver *CreateTupleQueueDestReceiver(shm_mq_handle *handle);
 /* Use these to receive tuples from a shm_mq. */
 extern TupleQueueReader *CreateTupleQueueReader(shm_mq_handle *handle);
 extern void DestroyTupleQueueReader(TupleQueueReader *reader);
-extern HeapTuple TupleQueueReaderNext(TupleQueueReader *reader,
-					 bool nowait, bool *done);
+extern StorageTuple TupleQueueReaderNext(TupleQueueReader *reader,
+										 bool nowait, bool *done);
 
 #endif							/* TQUEUE_H */
diff --git a/src/include/funcapi.h b/src/include/funcapi.h
index c2da2eb157..64b42c14ee 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -237,7 +237,7 @@ extern TupleDesc TypeGetTupleDesc(Oid typeoid, List *colaliases);
 /* from execTuples.c */
 extern TupleDesc BlessTupleDesc(TupleDesc tupdesc);
 extern AttInMetadata *TupleDescGetAttInMetadata(TupleDesc tupdesc);
-extern HeapTuple BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values);
+extern StorageTuple BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values);
 extern Datum HeapTupleHeaderGetDatum(HeapTupleHeader tuple);
 extern TupleTableSlot *TupleDescGetSlot(TupleDesc tupdesc);
 
-- 
2.15.0.windows.1



  [application/octet-stream] 0008-Remove-HeapScanDesc-usage-outside-heap.patch (95.7K, ../../CAJrrPGeoihBWs+BUP06q9=rCNm75m56J-EEStf3ygikNcpreYw@mail.gmail.com/11-0008-Remove-HeapScanDesc-usage-outside-heap.patch)
  download | inline diff:
From 51af500f18c9a06b11b77f747bb6f0cb02f7c665 Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Wed, 3 Jan 2018 17:32:55 +1100
Subject: [PATCH 08/12] Remove HeapScanDesc usage outside heap

HeapScanDesc is divided into two scan descriptors.
StorageScanDesc and HeapPageScanDesc.

StorageScanDesc has common members that are should
be available across all the storage routines and
HeapPageScanDesc is avaiable only for the storage
routine that supports Heap storage with page format.
The HeapPageScanDesc is used internally by the heapam
storage routine and also this is exposed to Bitmap Heap
and Sample scan's as they depend on the Heap page format.

while generating the Bitmap Heap and Sample scan's,
the planner now checks whether the storage routine
supports returning HeapPageScanDesc or not? Based on
this decision, the planner plans above two plans.
---
 contrib/pgrowlocks/pgrowlocks.c            |   4 +-
 contrib/pgstattuple/pgstattuple.c          |  10 +-
 contrib/tsm_system_rows/tsm_system_rows.c  |  18 +-
 contrib/tsm_system_time/tsm_system_time.c  |   8 +-
 src/backend/access/heap/heapam.c           | 424 +++++++++++++++--------------
 src/backend/access/heap/heapam_storage.c   |  53 ++++
 src/backend/access/index/genam.c           |   4 +-
 src/backend/access/storage/storageam.c     |  51 +++-
 src/backend/access/tablesample/system.c    |   2 +-
 src/backend/bootstrap/bootstrap.c          |   4 +-
 src/backend/catalog/aclchk.c               |   4 +-
 src/backend/catalog/index.c                |   8 +-
 src/backend/catalog/partition.c            |   2 +-
 src/backend/catalog/pg_conversion.c        |   2 +-
 src/backend/catalog/pg_db_role_setting.c   |   2 +-
 src/backend/catalog/pg_publication.c       |   2 +-
 src/backend/catalog/pg_subscription.c      |   2 +-
 src/backend/commands/cluster.c             |   4 +-
 src/backend/commands/copy.c                |   2 +-
 src/backend/commands/dbcommands.c          |   6 +-
 src/backend/commands/indexcmds.c           |   2 +-
 src/backend/commands/tablecmds.c           |  10 +-
 src/backend/commands/tablespace.c          |  10 +-
 src/backend/commands/typecmds.c            |   4 +-
 src/backend/commands/vacuum.c              |   4 +-
 src/backend/executor/nodeBitmapHeapscan.c  |  69 +++--
 src/backend/executor/nodeSamplescan.c      |  50 ++--
 src/backend/executor/nodeSeqscan.c         |   5 +-
 src/backend/optimizer/util/plancat.c       |   4 +-
 src/backend/postmaster/autovacuum.c        |   4 +-
 src/backend/postmaster/pgstat.c            |   2 +-
 src/backend/replication/logical/launcher.c |   2 +-
 src/backend/rewrite/rewriteDefine.c        |   2 +-
 src/backend/utils/init/postinit.c          |   2 +-
 src/include/access/heapam.h                |  36 +--
 src/include/access/relscan.h               |  47 ++--
 src/include/access/storage_common.h        |   1 -
 src/include/access/storageam.h             |  44 +--
 src/include/access/storageamapi.h          |  42 +--
 src/include/nodes/execnodes.h              |   4 +-
 40 files changed, 535 insertions(+), 421 deletions(-)

diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index bc8b423975..762d969ecd 100644
--- a/contrib/pgrowlocks/pgrowlocks.c
+++ b/contrib/pgrowlocks/pgrowlocks.c
@@ -56,7 +56,7 @@ PG_FUNCTION_INFO_V1(pgrowlocks);
 typedef struct
 {
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	int			ncolumns;
 } MyData;
 
@@ -71,7 +71,7 @@ Datum
 pgrowlocks(PG_FUNCTION_ARGS)
 {
 	FuncCallContext *funcctx;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	TupleDesc	tupdesc;
 	AttInMetadata *attinmeta;
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index c4b10d6efc..32ac121e92 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -314,7 +314,8 @@ pgstat_relation(Relation rel, FunctionCallInfo fcinfo)
 static Datum
 pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
+	HeapPageScanDesc pagescan;
 	HeapTuple	tuple;
 	BlockNumber nblocks;
 	BlockNumber block = 0;		/* next block to count free space in */
@@ -328,7 +329,8 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 	scan = storage_beginscan_strat(rel, SnapshotAny, 0, NULL, true, false);
 	InitDirtySnapshot(SnapshotDirty);
 
-	nblocks = scan->rs_nblocks; /* # blocks to be scanned */
+	pagescan = storageam_get_heappagescandesc(scan);
+	nblocks = pagescan->rs_nblocks; /* # blocks to be scanned */
 
 	/* scan the relation */
 	while ((tuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
@@ -364,7 +366,7 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 			CHECK_FOR_INTERRUPTS();
 
 			buffer = ReadBufferExtended(rel, MAIN_FORKNUM, block,
-										RBM_NORMAL, scan->rs_strategy);
+										RBM_NORMAL, pagescan->rs_strategy);
 			LockBuffer(buffer, BUFFER_LOCK_SHARE);
 			stat.free_space += PageGetHeapFreeSpace((Page) BufferGetPage(buffer));
 			UnlockReleaseBuffer(buffer);
@@ -377,7 +379,7 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 		CHECK_FOR_INTERRUPTS();
 
 		buffer = ReadBufferExtended(rel, MAIN_FORKNUM, block,
-									RBM_NORMAL, scan->rs_strategy);
+									RBM_NORMAL, pagescan->rs_strategy);
 		LockBuffer(buffer, BUFFER_LOCK_SHARE);
 		stat.free_space += PageGetHeapFreeSpace((Page) BufferGetPage(buffer));
 		UnlockReleaseBuffer(buffer);
diff --git a/contrib/tsm_system_rows/tsm_system_rows.c b/contrib/tsm_system_rows/tsm_system_rows.c
index 83f841f0c2..a2a1141d6f 100644
--- a/contrib/tsm_system_rows/tsm_system_rows.c
+++ b/contrib/tsm_system_rows/tsm_system_rows.c
@@ -71,7 +71,7 @@ static BlockNumber system_rows_nextsampleblock(SampleScanState *node);
 static OffsetNumber system_rows_nextsampletuple(SampleScanState *node,
 							BlockNumber blockno,
 							OffsetNumber maxoffset);
-static bool SampleOffsetVisible(OffsetNumber tupoffset, HeapScanDesc scan);
+static bool SampleOffsetVisible(OffsetNumber tupoffset, HeapPageScanDesc scan);
 static uint32 random_relative_prime(uint32 n, SamplerRandomState randstate);
 
 
@@ -209,7 +209,7 @@ static BlockNumber
 system_rows_nextsampleblock(SampleScanState *node)
 {
 	SystemRowsSamplerData *sampler = (SystemRowsSamplerData *) node->tsm_state;
-	HeapScanDesc scan = node->ss.ss_currentScanDesc;
+	HeapPageScanDesc pagescan = node->pagescan;
 
 	/* First call within scan? */
 	if (sampler->doneblocks == 0)
@@ -221,14 +221,14 @@ system_rows_nextsampleblock(SampleScanState *node)
 			SamplerRandomState randstate;
 
 			/* If relation is empty, there's nothing to scan */
-			if (scan->rs_nblocks == 0)
+			if (pagescan->rs_nblocks == 0)
 				return InvalidBlockNumber;
 
 			/* We only need an RNG during this setup step */
 			sampler_random_init_state(sampler->seed, randstate);
 
 			/* Compute nblocks/firstblock/step only once per query */
-			sampler->nblocks = scan->rs_nblocks;
+			sampler->nblocks = pagescan->rs_nblocks;
 
 			/* Choose random starting block within the relation */
 			/* (Actually this is the predecessor of the first block visited) */
@@ -258,7 +258,7 @@ system_rows_nextsampleblock(SampleScanState *node)
 	{
 		/* Advance lb, using uint64 arithmetic to forestall overflow */
 		sampler->lb = ((uint64) sampler->lb + sampler->step) % sampler->nblocks;
-	} while (sampler->lb >= scan->rs_nblocks);
+	} while (sampler->lb >= pagescan->rs_nblocks);
 
 	return sampler->lb;
 }
@@ -278,7 +278,7 @@ system_rows_nextsampletuple(SampleScanState *node,
 							OffsetNumber maxoffset)
 {
 	SystemRowsSamplerData *sampler = (SystemRowsSamplerData *) node->tsm_state;
-	HeapScanDesc scan = node->ss.ss_currentScanDesc;
+	HeapPageScanDesc pagescan = node->pagescan;
 	OffsetNumber tupoffset = sampler->lt;
 
 	/* Quit if we've returned all needed tuples */
@@ -291,7 +291,7 @@ system_rows_nextsampletuple(SampleScanState *node,
 	 */
 
 	/* We rely on the data accumulated in pagemode access */
-	Assert(scan->rs_pageatatime);
+	Assert(pagescan->rs_pageatatime);
 	for (;;)
 	{
 		/* Advance to next possible offset on page */
@@ -308,7 +308,7 @@ system_rows_nextsampletuple(SampleScanState *node,
 		}
 
 		/* Found a candidate? */
-		if (SampleOffsetVisible(tupoffset, scan))
+		if (SampleOffsetVisible(tupoffset, pagescan))
 		{
 			sampler->donetuples++;
 			break;
@@ -327,7 +327,7 @@ system_rows_nextsampletuple(SampleScanState *node,
  * so just look at the info it left in rs_vistuples[].
  */
 static bool
-SampleOffsetVisible(OffsetNumber tupoffset, HeapScanDesc scan)
+SampleOffsetVisible(OffsetNumber tupoffset, HeapPageScanDesc scan)
 {
 	int			start = 0,
 				end = scan->rs_ntuples - 1;
diff --git a/contrib/tsm_system_time/tsm_system_time.c b/contrib/tsm_system_time/tsm_system_time.c
index f0c220aa4a..f9925bb8b8 100644
--- a/contrib/tsm_system_time/tsm_system_time.c
+++ b/contrib/tsm_system_time/tsm_system_time.c
@@ -219,7 +219,7 @@ static BlockNumber
 system_time_nextsampleblock(SampleScanState *node)
 {
 	SystemTimeSamplerData *sampler = (SystemTimeSamplerData *) node->tsm_state;
-	HeapScanDesc scan = node->ss.ss_currentScanDesc;
+	HeapPageScanDesc pagescan = node->pagescan;
 	instr_time	cur_time;
 
 	/* First call within scan? */
@@ -232,14 +232,14 @@ system_time_nextsampleblock(SampleScanState *node)
 			SamplerRandomState randstate;
 
 			/* If relation is empty, there's nothing to scan */
-			if (scan->rs_nblocks == 0)
+			if (pagescan->rs_nblocks == 0)
 				return InvalidBlockNumber;
 
 			/* We only need an RNG during this setup step */
 			sampler_random_init_state(sampler->seed, randstate);
 
 			/* Compute nblocks/firstblock/step only once per query */
-			sampler->nblocks = scan->rs_nblocks;
+			sampler->nblocks = pagescan->rs_nblocks;
 
 			/* Choose random starting block within the relation */
 			/* (Actually this is the predecessor of the first block visited) */
@@ -275,7 +275,7 @@ system_time_nextsampleblock(SampleScanState *node)
 	{
 		/* Advance lb, using uint64 arithmetic to forestall overflow */
 		sampler->lb = ((uint64) sampler->lb + sampler->step) % sampler->nblocks;
-	} while (sampler->lb >= scan->rs_nblocks);
+	} while (sampler->lb >= pagescan->rs_nblocks);
 
 	return sampler->lb;
 }
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 2a46dc7ebc..e1bc52a43e 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -220,9 +220,9 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
 	 * lock that ensures the interesting tuple(s) won't change.)
 	 */
 	if (scan->rs_parallel != NULL)
-		scan->rs_nblocks = scan->rs_parallel->phs_nblocks;
+		scan->rs_pagescan.rs_nblocks = scan->rs_parallel->phs_nblocks;
 	else
-		scan->rs_nblocks = RelationGetNumberOfBlocks(scan->rs_rd);
+		scan->rs_pagescan.rs_nblocks = RelationGetNumberOfBlocks(scan->rs_scan.rs_rd);
 
 	/*
 	 * If the table is large relative to NBuffers, use a bulk-read access
@@ -236,8 +236,8 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
 	 * Note that heap_parallelscan_initialize has a very similar test; if you
 	 * change this, consider changing that one, too.
 	 */
-	if (!RelationUsesLocalBuffers(scan->rs_rd) &&
-		scan->rs_nblocks > NBuffers / 4)
+	if (!RelationUsesLocalBuffers(scan->rs_scan.rs_rd) &&
+		scan->rs_pagescan.rs_nblocks > NBuffers / 4)
 	{
 		allow_strat = scan->rs_allow_strat;
 		allow_sync = scan->rs_allow_sync;
@@ -248,20 +248,20 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
 	if (allow_strat)
 	{
 		/* During a rescan, keep the previous strategy object. */
-		if (scan->rs_strategy == NULL)
-			scan->rs_strategy = GetAccessStrategy(BAS_BULKREAD);
+		if (scan->rs_pagescan.rs_strategy == NULL)
+			scan->rs_pagescan.rs_strategy = GetAccessStrategy(BAS_BULKREAD);
 	}
 	else
 	{
-		if (scan->rs_strategy != NULL)
-			FreeAccessStrategy(scan->rs_strategy);
-		scan->rs_strategy = NULL;
+		if (scan->rs_pagescan.rs_strategy != NULL)
+			FreeAccessStrategy(scan->rs_pagescan.rs_strategy);
+		scan->rs_pagescan.rs_strategy = NULL;
 	}
 
 	if (scan->rs_parallel != NULL)
 	{
 		/* For parallel scan, believe whatever ParallelHeapScanDesc says. */
-		scan->rs_syncscan = scan->rs_parallel->phs_syncscan;
+		scan->rs_pagescan.rs_syncscan = scan->rs_parallel->phs_syncscan;
 	}
 	else if (keep_startblock)
 	{
@@ -270,25 +270,25 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
 		 * so that rewinding a cursor doesn't generate surprising results.
 		 * Reset the active syncscan setting, though.
 		 */
-		scan->rs_syncscan = (allow_sync && synchronize_seqscans);
+		scan->rs_pagescan.rs_syncscan = (allow_sync && synchronize_seqscans);
 	}
 	else if (allow_sync && synchronize_seqscans)
 	{
-		scan->rs_syncscan = true;
-		scan->rs_startblock = ss_get_location(scan->rs_rd, scan->rs_nblocks);
+		scan->rs_pagescan.rs_syncscan = true;
+		scan->rs_pagescan.rs_startblock = ss_get_location(scan->rs_scan.rs_rd, scan->rs_pagescan.rs_nblocks);
 	}
 	else
 	{
-		scan->rs_syncscan = false;
-		scan->rs_startblock = 0;
+		scan->rs_pagescan.rs_syncscan = false;
+		scan->rs_pagescan.rs_startblock = 0;
 	}
 
-	scan->rs_numblocks = InvalidBlockNumber;
-	scan->rs_inited = false;
+	scan->rs_pagescan.rs_numblocks = InvalidBlockNumber;
+	scan->rs_scan.rs_inited = false;
 	scan->rs_ctup.t_data = NULL;
 	ItemPointerSetInvalid(&scan->rs_ctup.t_self);
-	scan->rs_cbuf = InvalidBuffer;
-	scan->rs_cblock = InvalidBlockNumber;
+	scan->rs_scan.rs_cbuf = InvalidBuffer;
+	scan->rs_scan.rs_cblock = InvalidBlockNumber;
 
 	/* page-at-a-time fields are always invalid when not rs_inited */
 
@@ -296,7 +296,7 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
 	 * copy the scan key, if appropriate
 	 */
 	if (key != NULL)
-		memcpy(scan->rs_key, key, scan->rs_nkeys * sizeof(ScanKeyData));
+		memcpy(scan->rs_scan.rs_key, key, scan->rs_scan.rs_nkeys * sizeof(ScanKeyData));
 
 	/*
 	 * Currently, we don't have a stats counter for bitmap heap scans (but the
@@ -304,7 +304,7 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
 	 * update stats for tuple fetches there)
 	 */
 	if (!scan->rs_bitmapscan && !scan->rs_samplescan)
-		pgstat_count_heap_scan(scan->rs_rd);
+		pgstat_count_heap_scan(scan->rs_scan.rs_rd);
 }
 
 /*
@@ -314,16 +314,19 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
  * numBlks is number of pages to scan (InvalidBlockNumber means "all")
  */
 void
-heap_setscanlimits(HeapScanDesc scan, BlockNumber startBlk, BlockNumber numBlks)
+heap_setscanlimits(StorageScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
 {
-	Assert(!scan->rs_inited);	/* else too late to change */
-	Assert(!scan->rs_syncscan); /* else rs_startblock is significant */
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
+	Assert(!scan->rs_scan.rs_inited);	/* else too late to change */
+	Assert(!scan->rs_pagescan.rs_syncscan); /* else rs_startblock is
+											 * significant */
 
 	/* Check startBlk is valid (but allow case of zero blocks...) */
-	Assert(startBlk == 0 || startBlk < scan->rs_nblocks);
+	Assert(startBlk == 0 || startBlk < scan->rs_pagescan.rs_nblocks);
 
-	scan->rs_startblock = startBlk;
-	scan->rs_numblocks = numBlks;
+	scan->rs_pagescan.rs_startblock = startBlk;
+	scan->rs_pagescan.rs_numblocks = numBlks;
 }
 
 /*
@@ -334,8 +337,9 @@ heap_setscanlimits(HeapScanDesc scan, BlockNumber startBlk, BlockNumber numBlks)
  * which tuples on the page are visible.
  */
 void
-heapgetpage(HeapScanDesc scan, BlockNumber page)
+heapgetpage(StorageScanDesc sscan, BlockNumber page)
 {
+	HeapScanDesc scan = (HeapScanDesc) sscan;
 	Buffer		buffer;
 	Snapshot	snapshot;
 	Page		dp;
@@ -345,13 +349,13 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 	ItemId		lpp;
 	bool		all_visible;
 
-	Assert(page < scan->rs_nblocks);
+	Assert(page < scan->rs_pagescan.rs_nblocks);
 
 	/* release previous scan buffer, if any */
-	if (BufferIsValid(scan->rs_cbuf))
+	if (BufferIsValid(scan->rs_scan.rs_cbuf))
 	{
-		ReleaseBuffer(scan->rs_cbuf);
-		scan->rs_cbuf = InvalidBuffer;
+		ReleaseBuffer(scan->rs_scan.rs_cbuf);
+		scan->rs_scan.rs_cbuf = InvalidBuffer;
 	}
 
 	/*
@@ -362,20 +366,20 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 	CHECK_FOR_INTERRUPTS();
 
 	/* read page using selected strategy */
-	scan->rs_cbuf = ReadBufferExtended(scan->rs_rd, MAIN_FORKNUM, page,
-									   RBM_NORMAL, scan->rs_strategy);
-	scan->rs_cblock = page;
+	scan->rs_scan.rs_cbuf = ReadBufferExtended(scan->rs_scan.rs_rd, MAIN_FORKNUM, page,
+											   RBM_NORMAL, scan->rs_pagescan.rs_strategy);
+	scan->rs_scan.rs_cblock = page;
 
-	if (!scan->rs_pageatatime)
+	if (!scan->rs_pagescan.rs_pageatatime)
 		return;
 
-	buffer = scan->rs_cbuf;
-	snapshot = scan->rs_snapshot;
+	buffer = scan->rs_scan.rs_cbuf;
+	snapshot = scan->rs_scan.rs_snapshot;
 
 	/*
 	 * Prune and repair fragmentation for the whole page, if possible.
 	 */
-	heap_page_prune_opt(scan->rs_rd, buffer);
+	heap_page_prune_opt(scan->rs_scan.rs_rd, buffer);
 
 	/*
 	 * We must hold share lock on the buffer content while examining tuple
@@ -385,7 +389,7 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 	LockBuffer(buffer, BUFFER_LOCK_SHARE);
 
 	dp = BufferGetPage(buffer);
-	TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+	TestForOldSnapshot(snapshot, scan->rs_scan.rs_rd, dp);
 	lines = PageGetMaxOffsetNumber(dp);
 	ntup = 0;
 
@@ -420,7 +424,7 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 			HeapTupleData loctup;
 			bool		valid;
 
-			loctup.t_tableOid = RelationGetRelid(scan->rs_rd);
+			loctup.t_tableOid = RelationGetRelid(scan->rs_scan.rs_rd);
 			loctup.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
 			loctup.t_len = ItemIdGetLength(lpp);
 			ItemPointerSet(&(loctup.t_self), page, lineoff);
@@ -428,20 +432,20 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 			if (all_visible)
 				valid = true;
 			else
-				valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine, &loctup, snapshot, buffer);
+				valid = HeapTupleSatisfiesVisibility(scan->rs_scan.rs_rd->rd_stamroutine, &loctup, snapshot, buffer);
 
-			CheckForSerializableConflictOut(valid, scan->rs_rd, &loctup,
+			CheckForSerializableConflictOut(valid, scan->rs_scan.rs_rd, &loctup,
 											buffer, snapshot);
 
 			if (valid)
-				scan->rs_vistuples[ntup++] = lineoff;
+				scan->rs_pagescan.rs_vistuples[ntup++] = lineoff;
 		}
 	}
 
 	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
 
 	Assert(ntup <= MaxHeapTuplesPerPage);
-	scan->rs_ntuples = ntup;
+	scan->rs_pagescan.rs_ntuples = ntup;
 }
 
 /* ----------------
@@ -474,7 +478,7 @@ heapgettup(HeapScanDesc scan,
 		   ScanKey key)
 {
 	HeapTuple	tuple = &(scan->rs_ctup);
-	Snapshot	snapshot = scan->rs_snapshot;
+	Snapshot	snapshot = scan->rs_scan.rs_snapshot;
 	bool		backward = ScanDirectionIsBackward(dir);
 	BlockNumber page;
 	bool		finished;
@@ -489,14 +493,14 @@ heapgettup(HeapScanDesc scan,
 	 */
 	if (ScanDirectionIsForward(dir))
 	{
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
 			/*
 			 * return null immediately if relation is empty
 			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			if (scan->rs_pagescan.rs_nblocks == 0 || scan->rs_pagescan.rs_numblocks == 0)
 			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
+				Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 				tuple->t_data = NULL;
 				return;
 			}
@@ -509,29 +513,29 @@ heapgettup(HeapScanDesc scan,
 				/* Other processes might have already finished the scan. */
 				if (page == InvalidBlockNumber)
 				{
-					Assert(!BufferIsValid(scan->rs_cbuf));
+					Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 					tuple->t_data = NULL;
 					return;
 				}
 			}
 			else
-				page = scan->rs_startblock; /* first page */
-			heapgetpage(scan, page);
+				page = scan->rs_pagescan.rs_startblock; /* first page */
+			heapgetpage((StorageScanDesc) scan, page);
 			lineoff = FirstOffsetNumber;	/* first offnum */
-			scan->rs_inited = true;
+			scan->rs_scan.rs_inited = true;
 		}
 		else
 		{
 			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
+			page = scan->rs_scan.rs_cblock; /* current page */
 			lineoff =			/* next offnum */
 				OffsetNumberNext(ItemPointerGetOffsetNumber(&(tuple->t_self)));
 		}
 
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_SHARE);
 
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_scan.rs_rd, dp);
 		lines = PageGetMaxOffsetNumber(dp);
 		/* page and lineoff now reference the physically next tid */
 
@@ -542,14 +546,14 @@ heapgettup(HeapScanDesc scan,
 		/* backward parallel scan not supported */
 		Assert(scan->rs_parallel == NULL);
 
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
 			/*
 			 * return null immediately if relation is empty
 			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			if (scan->rs_pagescan.rs_nblocks == 0 || scan->rs_pagescan.rs_numblocks == 0)
 			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
+				Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 				tuple->t_data = NULL;
 				return;
 			}
@@ -560,30 +564,30 @@ heapgettup(HeapScanDesc scan,
 			 * time, and much more likely that we'll just bollix things for
 			 * forward scanners.
 			 */
-			scan->rs_syncscan = false;
+			scan->rs_pagescan.rs_syncscan = false;
 			/* start from last page of the scan */
-			if (scan->rs_startblock > 0)
-				page = scan->rs_startblock - 1;
+			if (scan->rs_pagescan.rs_startblock > 0)
+				page = scan->rs_pagescan.rs_startblock - 1;
 			else
-				page = scan->rs_nblocks - 1;
-			heapgetpage(scan, page);
+				page = scan->rs_pagescan.rs_nblocks - 1;
+			heapgetpage((StorageScanDesc) scan, page);
 		}
 		else
 		{
 			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
+			page = scan->rs_scan.rs_cblock; /* current page */
 		}
 
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_SHARE);
 
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_scan.rs_rd, dp);
 		lines = PageGetMaxOffsetNumber(dp);
 
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
 			lineoff = lines;	/* final offnum */
-			scan->rs_inited = true;
+			scan->rs_scan.rs_inited = true;
 		}
 		else
 		{
@@ -599,20 +603,20 @@ heapgettup(HeapScanDesc scan,
 		/*
 		 * ``no movement'' scan direction: refetch prior tuple
 		 */
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
-			Assert(!BufferIsValid(scan->rs_cbuf));
+			Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 			tuple->t_data = NULL;
 			return;
 		}
 
 		page = ItemPointerGetBlockNumber(&(tuple->t_self));
-		if (page != scan->rs_cblock)
-			heapgetpage(scan, page);
+		if (page != scan->rs_scan.rs_cblock)
+			heapgetpage((StorageScanDesc) scan, page);
 
 		/* Since the tuple was previously fetched, needn't lock page here */
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_scan.rs_rd, dp);
 		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
 		lpp = PageGetItemId(dp, lineoff);
 		Assert(ItemIdIsNormal(lpp));
@@ -643,21 +647,21 @@ heapgettup(HeapScanDesc scan,
 				/*
 				 * if current tuple qualifies, return it.
 				 */
-				valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine,
+				valid = HeapTupleSatisfiesVisibility(scan->rs_scan.rs_rd->rd_stamroutine,
 													 tuple,
 													 snapshot,
-													 scan->rs_cbuf);
+													 scan->rs_scan.rs_cbuf);
 
-				CheckForSerializableConflictOut(valid, scan->rs_rd, tuple,
-												scan->rs_cbuf, snapshot);
+				CheckForSerializableConflictOut(valid, scan->rs_scan.rs_rd, tuple,
+												scan->rs_scan.rs_cbuf, snapshot);
 
 				if (valid && key != NULL)
-					HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
+					HeapKeyTest(tuple, RelationGetDescr(scan->rs_scan.rs_rd),
 								nkeys, key, valid);
 
 				if (valid)
 				{
-					LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+					LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_UNLOCK);
 					return;
 				}
 			}
@@ -682,17 +686,17 @@ heapgettup(HeapScanDesc scan,
 		 * if we get here, it means we've exhausted the items on this page and
 		 * it's time to move to the next.
 		 */
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_UNLOCK);
 
 		/*
 		 * advance to next/prior page and detect end of scan
 		 */
 		if (backward)
 		{
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+			finished = (page == scan->rs_pagescan.rs_startblock) ||
+				(scan->rs_pagescan.rs_numblocks != InvalidBlockNumber ? --scan->rs_pagescan.rs_numblocks == 0 : false);
 			if (page == 0)
-				page = scan->rs_nblocks;
+				page = scan->rs_pagescan.rs_nblocks;
 			page--;
 		}
 		else if (scan->rs_parallel != NULL)
@@ -703,10 +707,10 @@ heapgettup(HeapScanDesc scan,
 		else
 		{
 			page++;
-			if (page >= scan->rs_nblocks)
+			if (page >= scan->rs_pagescan.rs_nblocks)
 				page = 0;
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+			finished = (page == scan->rs_pagescan.rs_startblock) ||
+				(scan->rs_pagescan.rs_numblocks != InvalidBlockNumber ? --scan->rs_pagescan.rs_numblocks == 0 : false);
 
 			/*
 			 * Report our new scan position for synchronization purposes. We
@@ -720,8 +724,8 @@ heapgettup(HeapScanDesc scan,
 			 * a little bit backwards on every invocation, which is confusing.
 			 * We don't guarantee any specific ordering in general, though.
 			 */
-			if (scan->rs_syncscan)
-				ss_report_location(scan->rs_rd, page);
+			if (scan->rs_pagescan.rs_syncscan)
+				ss_report_location(scan->rs_scan.rs_rd, page);
 		}
 
 		/*
@@ -729,21 +733,21 @@ heapgettup(HeapScanDesc scan,
 		 */
 		if (finished)
 		{
-			if (BufferIsValid(scan->rs_cbuf))
-				ReleaseBuffer(scan->rs_cbuf);
-			scan->rs_cbuf = InvalidBuffer;
-			scan->rs_cblock = InvalidBlockNumber;
+			if (BufferIsValid(scan->rs_scan.rs_cbuf))
+				ReleaseBuffer(scan->rs_scan.rs_cbuf);
+			scan->rs_scan.rs_cbuf = InvalidBuffer;
+			scan->rs_scan.rs_cblock = InvalidBlockNumber;
 			tuple->t_data = NULL;
-			scan->rs_inited = false;
+			scan->rs_scan.rs_inited = false;
 			return;
 		}
 
-		heapgetpage(scan, page);
+		heapgetpage((StorageScanDesc) scan, page);
 
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_SHARE);
 
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_scan.rs_rd, dp);
 		lines = PageGetMaxOffsetNumber((Page) dp);
 		linesleft = lines;
 		if (backward)
@@ -794,14 +798,14 @@ heapgettup_pagemode(HeapScanDesc scan,
 	 */
 	if (ScanDirectionIsForward(dir))
 	{
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
 			/*
 			 * return null immediately if relation is empty
 			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			if (scan->rs_pagescan.rs_nblocks == 0 || scan->rs_pagescan.rs_numblocks == 0)
 			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
+				Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 				tuple->t_data = NULL;
 				return;
 			}
@@ -814,28 +818,28 @@ heapgettup_pagemode(HeapScanDesc scan,
 				/* Other processes might have already finished the scan. */
 				if (page == InvalidBlockNumber)
 				{
-					Assert(!BufferIsValid(scan->rs_cbuf));
+					Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 					tuple->t_data = NULL;
 					return;
 				}
 			}
 			else
-				page = scan->rs_startblock; /* first page */
-			heapgetpage(scan, page);
+				page = scan->rs_pagescan.rs_startblock; /* first page */
+			heapgetpage((StorageScanDesc) scan, page);
 			lineindex = 0;
-			scan->rs_inited = true;
+			scan->rs_scan.rs_inited = true;
 		}
 		else
 		{
 			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
-			lineindex = scan->rs_cindex + 1;
+			page = scan->rs_scan.rs_cblock; /* current page */
+			lineindex = scan->rs_pagescan.rs_cindex + 1;
 		}
 
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_SHARE);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(scan->rs_scan.rs_snapshot, scan->rs_scan.rs_rd, dp);
+		lines = scan->rs_pagescan.rs_ntuples;
 		/* page and lineindex now reference the next visible tid */
 
 		linesleft = lines - lineindex;
@@ -845,14 +849,14 @@ heapgettup_pagemode(HeapScanDesc scan,
 		/* backward parallel scan not supported */
 		Assert(scan->rs_parallel == NULL);
 
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
 			/*
 			 * return null immediately if relation is empty
 			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			if (scan->rs_pagescan.rs_nblocks == 0 || scan->rs_pagescan.rs_numblocks == 0)
 			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
+				Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 				tuple->t_data = NULL;
 				return;
 			}
@@ -863,33 +867,33 @@ heapgettup_pagemode(HeapScanDesc scan,
 			 * time, and much more likely that we'll just bollix things for
 			 * forward scanners.
 			 */
-			scan->rs_syncscan = false;
+			scan->rs_pagescan.rs_syncscan = false;
 			/* start from last page of the scan */
-			if (scan->rs_startblock > 0)
-				page = scan->rs_startblock - 1;
+			if (scan->rs_pagescan.rs_startblock > 0)
+				page = scan->rs_pagescan.rs_startblock - 1;
 			else
-				page = scan->rs_nblocks - 1;
-			heapgetpage(scan, page);
+				page = scan->rs_pagescan.rs_nblocks - 1;
+			heapgetpage((StorageScanDesc) scan, page);
 		}
 		else
 		{
 			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
+			page = scan->rs_scan.rs_cblock; /* current page */
 		}
 
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_SHARE);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(scan->rs_scan.rs_snapshot, scan->rs_scan.rs_rd, dp);
+		lines = scan->rs_pagescan.rs_ntuples;
 
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
 			lineindex = lines - 1;
-			scan->rs_inited = true;
+			scan->rs_scan.rs_inited = true;
 		}
 		else
 		{
-			lineindex = scan->rs_cindex - 1;
+			lineindex = scan->rs_pagescan.rs_cindex - 1;
 		}
 		/* page and lineindex now reference the previous visible tid */
 
@@ -900,20 +904,20 @@ heapgettup_pagemode(HeapScanDesc scan,
 		/*
 		 * ``no movement'' scan direction: refetch prior tuple
 		 */
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
-			Assert(!BufferIsValid(scan->rs_cbuf));
+			Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 			tuple->t_data = NULL;
 			return;
 		}
 
 		page = ItemPointerGetBlockNumber(&(tuple->t_self));
-		if (page != scan->rs_cblock)
-			heapgetpage(scan, page);
+		if (page != scan->rs_scan.rs_cblock)
+			heapgetpage((StorageScanDesc) scan, page);
 
 		/* Since the tuple was previously fetched, needn't lock page here */
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(scan->rs_scan.rs_snapshot, scan->rs_scan.rs_rd, dp);
 		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
 		lpp = PageGetItemId(dp, lineoff);
 		Assert(ItemIdIsNormal(lpp));
@@ -922,8 +926,8 @@ heapgettup_pagemode(HeapScanDesc scan,
 		tuple->t_len = ItemIdGetLength(lpp);
 
 		/* check that rs_cindex is in sync */
-		Assert(scan->rs_cindex < scan->rs_ntuples);
-		Assert(lineoff == scan->rs_vistuples[scan->rs_cindex]);
+		Assert(scan->rs_pagescan.rs_cindex < scan->rs_pagescan.rs_ntuples);
+		Assert(lineoff == scan->rs_pagescan.rs_vistuples[scan->rs_pagescan.rs_cindex]);
 
 		return;
 	}
@@ -936,7 +940,7 @@ heapgettup_pagemode(HeapScanDesc scan,
 	{
 		while (linesleft > 0)
 		{
-			lineoff = scan->rs_vistuples[lineindex];
+			lineoff = scan->rs_pagescan.rs_vistuples[lineindex];
 			lpp = PageGetItemId(dp, lineoff);
 			Assert(ItemIdIsNormal(lpp));
 
@@ -947,7 +951,7 @@ heapgettup_pagemode(HeapScanDesc scan,
 			/*
 			 * if current tuple qualifies, return it.
 			 */
-			if (HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine, tuple, scan->rs_snapshot, scan->rs_cbuf))
+			if (HeapTupleSatisfiesVisibility(scan->rs_scan.rs_rd->rd_stamroutine, tuple, scan->rs_scan.rs_snapshot, scan->rs_scan.rs_cbuf))
 			{
 				/*
 				 * if current tuple qualifies, return it.
@@ -956,19 +960,19 @@ heapgettup_pagemode(HeapScanDesc scan,
 				{
 					bool		valid;
 
-					HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
+					HeapKeyTest(tuple, RelationGetDescr(scan->rs_scan.rs_rd),
 								nkeys, key, valid);
 					if (valid)
 					{
-						scan->rs_cindex = lineindex;
-						LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+						scan->rs_pagescan.rs_cindex = lineindex;
+						LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_UNLOCK);
 						return;
 					}
 				}
 				else
 				{
-					scan->rs_cindex = lineindex;
-					LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+					scan->rs_pagescan.rs_cindex = lineindex;
+					LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_UNLOCK);
 					return;
 				}
 			}
@@ -987,7 +991,7 @@ heapgettup_pagemode(HeapScanDesc scan,
 		 * if we get here, it means we've exhausted the items on this page and
 		 * it's time to move to the next.
 		 */
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_UNLOCK);
 
 		/*
 		 * if we get here, it means we've exhausted the items on this page and
@@ -995,10 +999,10 @@ heapgettup_pagemode(HeapScanDesc scan,
 		 */
 		if (backward)
 		{
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+			finished = (page == scan->rs_pagescan.rs_startblock) ||
+				(scan->rs_pagescan.rs_numblocks != InvalidBlockNumber ? --scan->rs_pagescan.rs_numblocks == 0 : false);
 			if (page == 0)
-				page = scan->rs_nblocks;
+				page = scan->rs_pagescan.rs_nblocks;
 			page--;
 		}
 		else if (scan->rs_parallel != NULL)
@@ -1009,10 +1013,10 @@ heapgettup_pagemode(HeapScanDesc scan,
 		else
 		{
 			page++;
-			if (page >= scan->rs_nblocks)
+			if (page >= scan->rs_pagescan.rs_nblocks)
 				page = 0;
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+			finished = (page == scan->rs_pagescan.rs_startblock) ||
+				(scan->rs_pagescan.rs_numblocks != InvalidBlockNumber ? --scan->rs_pagescan.rs_numblocks == 0 : false);
 
 			/*
 			 * Report our new scan position for synchronization purposes. We
@@ -1026,8 +1030,8 @@ heapgettup_pagemode(HeapScanDesc scan,
 			 * a little bit backwards on every invocation, which is confusing.
 			 * We don't guarantee any specific ordering in general, though.
 			 */
-			if (scan->rs_syncscan)
-				ss_report_location(scan->rs_rd, page);
+			if (scan->rs_pagescan.rs_syncscan)
+				ss_report_location(scan->rs_scan.rs_rd, page);
 		}
 
 		/*
@@ -1035,21 +1039,21 @@ heapgettup_pagemode(HeapScanDesc scan,
 		 */
 		if (finished)
 		{
-			if (BufferIsValid(scan->rs_cbuf))
-				ReleaseBuffer(scan->rs_cbuf);
-			scan->rs_cbuf = InvalidBuffer;
-			scan->rs_cblock = InvalidBlockNumber;
+			if (BufferIsValid(scan->rs_scan.rs_cbuf))
+				ReleaseBuffer(scan->rs_scan.rs_cbuf);
+			scan->rs_scan.rs_cbuf = InvalidBuffer;
+			scan->rs_scan.rs_cblock = InvalidBlockNumber;
 			tuple->t_data = NULL;
-			scan->rs_inited = false;
+			scan->rs_scan.rs_inited = false;
 			return;
 		}
 
-		heapgetpage(scan, page);
+		heapgetpage((StorageScanDesc) scan, page);
 
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_SHARE);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(scan->rs_scan.rs_snapshot, scan->rs_scan.rs_rd, dp);
+		lines = scan->rs_pagescan.rs_ntuples;
 		linesleft = lines;
 		if (backward)
 			lineindex = lines - 1;
@@ -1376,7 +1380,7 @@ heap_openrv_extended(const RangeVar *relation, LOCKMODE lockmode,
 	return r;
 }
 
-HeapScanDesc
+StorageScanDesc
 heap_beginscan(Relation relation, Snapshot snapshot,
 			   int nkeys, ScanKey key,
 			   ParallelHeapScanDesc parallel_scan,
@@ -1403,12 +1407,12 @@ heap_beginscan(Relation relation, Snapshot snapshot,
 	 */
 	scan = (HeapScanDesc) palloc(sizeof(HeapScanDescData));
 
-	scan->rs_rd = relation;
-	scan->rs_snapshot = snapshot;
-	scan->rs_nkeys = nkeys;
+	scan->rs_scan.rs_rd = relation;
+	scan->rs_scan.rs_snapshot = snapshot;
+	scan->rs_scan.rs_nkeys = nkeys;
 	scan->rs_bitmapscan = is_bitmapscan;
 	scan->rs_samplescan = is_samplescan;
-	scan->rs_strategy = NULL;	/* set in initscan */
+	scan->rs_pagescan.rs_strategy = NULL;	/* set in initscan */
 	scan->rs_allow_strat = allow_strat;
 	scan->rs_allow_sync = allow_sync;
 	scan->rs_temp_snap = temp_snap;
@@ -1417,7 +1421,7 @@ heap_beginscan(Relation relation, Snapshot snapshot,
 	/*
 	 * we can use page-at-a-time mode if it's an MVCC-safe snapshot
 	 */
-	scan->rs_pageatatime = allow_pagemode && IsMVCCSnapshot(snapshot);
+	scan->rs_pagescan.rs_pageatatime = allow_pagemode && IsMVCCSnapshot(snapshot);
 
 	/*
 	 * For a seqscan in a serializable transaction, acquire a predicate lock
@@ -1441,13 +1445,13 @@ heap_beginscan(Relation relation, Snapshot snapshot,
 	 * initscan() and we don't want to allocate memory again
 	 */
 	if (nkeys > 0)
-		scan->rs_key = (ScanKey) palloc(sizeof(ScanKeyData) * nkeys);
+		scan->rs_scan.rs_key = (ScanKey) palloc(sizeof(ScanKeyData) * nkeys);
 	else
-		scan->rs_key = NULL;
+		scan->rs_scan.rs_key = NULL;
 
 	initscan(scan, key, false);
 
-	return scan;
+	return (StorageScanDesc) scan;
 }
 
 /* ----------------
@@ -1455,21 +1459,23 @@ heap_beginscan(Relation relation, Snapshot snapshot,
  * ----------------
  */
 void
-heap_rescan(HeapScanDesc scan, ScanKey key, bool set_params,
+heap_rescan(StorageScanDesc sscan, ScanKey key, bool set_params,
 			bool allow_strat, bool allow_sync, bool allow_pagemode)
 {
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
 	if (set_params)
 	{
 		scan->rs_allow_strat = allow_strat;
 		scan->rs_allow_sync = allow_sync;
-		scan->rs_pageatatime = allow_pagemode && IsMVCCSnapshot(scan->rs_snapshot);
+		scan->rs_pagescan.rs_pageatatime = allow_pagemode && IsMVCCSnapshot(scan->rs_scan.rs_snapshot);
 	}
 
 	/*
 	 * unpin scan buffers
 	 */
-	if (BufferIsValid(scan->rs_cbuf))
-		ReleaseBuffer(scan->rs_cbuf);
+	if (BufferIsValid(scan->rs_scan.rs_cbuf))
+		ReleaseBuffer(scan->rs_scan.rs_cbuf);
 
 	/*
 	 * reinitialize scan descriptor
@@ -1500,29 +1506,31 @@ heap_rescan(HeapScanDesc scan, ScanKey key, bool set_params,
  * ----------------
  */
 void
-heap_endscan(HeapScanDesc scan)
+heap_endscan(StorageScanDesc sscan)
 {
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
 	/* Note: no locking manipulations needed */
 
 	/*
 	 * unpin scan buffers
 	 */
-	if (BufferIsValid(scan->rs_cbuf))
-		ReleaseBuffer(scan->rs_cbuf);
+	if (BufferIsValid(scan->rs_scan.rs_cbuf))
+		ReleaseBuffer(scan->rs_scan.rs_cbuf);
 
 	/*
 	 * decrement relation reference count and free scan descriptor storage
 	 */
-	RelationDecrementReferenceCount(scan->rs_rd);
+	RelationDecrementReferenceCount(scan->rs_scan.rs_rd);
 
-	if (scan->rs_key)
-		pfree(scan->rs_key);
+	if (scan->rs_scan.rs_key)
+		pfree(scan->rs_scan.rs_key);
 
-	if (scan->rs_strategy != NULL)
-		FreeAccessStrategy(scan->rs_strategy);
+	if (scan->rs_pagescan.rs_strategy != NULL)
+		FreeAccessStrategy(scan->rs_pagescan.rs_strategy);
 
 	if (scan->rs_temp_snap)
-		UnregisterSnapshot(scan->rs_snapshot);
+		UnregisterSnapshot(scan->rs_scan.rs_snapshot);
 
 	pfree(scan);
 }
@@ -1618,7 +1626,7 @@ retry:
 		else
 		{
 			SpinLockRelease(&parallel_scan->phs_mutex);
-			sync_startpage = ss_get_location(scan->rs_rd, scan->rs_nblocks);
+			sync_startpage = ss_get_location(scan->rs_scan.rs_rd, scan->rs_pagescan.rs_nblocks);
 			goto retry;
 		}
 	}
@@ -1660,10 +1668,10 @@ heap_parallelscan_nextpage(HeapScanDesc scan)
 	 * starting block number, modulo nblocks.
 	 */
 	nallocated = pg_atomic_fetch_add_u64(&parallel_scan->phs_nallocated, 1);
-	if (nallocated >= scan->rs_nblocks)
+	if (nallocated >= scan->rs_pagescan.rs_nblocks)
 		page = InvalidBlockNumber;	/* all blocks have been allocated */
 	else
-		page = (nallocated + parallel_scan->phs_startblock) % scan->rs_nblocks;
+		page = (nallocated + parallel_scan->phs_startblock) % scan->rs_pagescan.rs_nblocks;
 
 	/*
 	 * Report scan location.  Normally, we report the current page number.
@@ -1672,12 +1680,12 @@ heap_parallelscan_nextpage(HeapScanDesc scan)
 	 * doesn't slew backwards.  We only report the position at the end of the
 	 * scan once, though: subsequent callers will report nothing.
 	 */
-	if (scan->rs_syncscan)
+	if (scan->rs_pagescan.rs_syncscan)
 	{
 		if (page != InvalidBlockNumber)
-			ss_report_location(scan->rs_rd, page);
-		else if (nallocated == scan->rs_nblocks)
-			ss_report_location(scan->rs_rd, parallel_scan->phs_startblock);
+			ss_report_location(scan->rs_scan.rs_rd, page);
+		else if (nallocated == scan->rs_pagescan.rs_nblocks)
+			ss_report_location(scan->rs_scan.rs_rd, parallel_scan->phs_startblock);
 	}
 
 	return page;
@@ -1690,12 +1698,14 @@ heap_parallelscan_nextpage(HeapScanDesc scan)
  * ----------------
  */
 void
-heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
+heap_update_snapshot(StorageScanDesc sscan, Snapshot snapshot)
 {
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
 	Assert(IsMVCCSnapshot(snapshot));
 
 	RegisterSnapshot(snapshot);
-	scan->rs_snapshot = snapshot;
+	scan->rs_scan.rs_snapshot = snapshot;
 	scan->rs_temp_snap = true;
 }
 
@@ -1723,17 +1733,19 @@ heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
 #endif							/* !defined(HEAPDEBUGALL) */
 
 StorageTuple
-heap_getnext(HeapScanDesc scan, ScanDirection direction)
+heap_getnext(StorageScanDesc sscan, ScanDirection direction)
 {
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
 	/* Note: no locking manipulations needed */
 
 	HEAPDEBUG_1;				/* heap_getnext( info ) */
 
-	if (scan->rs_pageatatime)
+	if (scan->rs_pagescan.rs_pageatatime)
 		heapgettup_pagemode(scan, direction,
-							scan->rs_nkeys, scan->rs_key);
+							scan->rs_scan.rs_nkeys, scan->rs_scan.rs_key);
 	else
-		heapgettup(scan, direction, scan->rs_nkeys, scan->rs_key);
+		heapgettup(scan, direction, scan->rs_scan.rs_nkeys, scan->rs_scan.rs_key);
 
 	if (scan->rs_ctup.t_data == NULL)
 	{
@@ -1747,7 +1759,7 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 	 */
 	HEAPDEBUG_3;				/* heap_getnext returning tuple */
 
-	pgstat_count_heap_getnext(scan->rs_rd);
+	pgstat_count_heap_getnext(scan->rs_scan.rs_rd);
 
 	return heap_copytuple(&(scan->rs_ctup));
 }
@@ -1755,7 +1767,7 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 #ifdef HEAPAMSLOTDEBUGALL
 #define HEAPAMSLOTDEBUG_1 \
 	elog(DEBUG2, "heapam_getnext([%s,nkeys=%d],dir=%d) called", \
-		 RelationGetRelationName(scan->rs_rd), scan->rs_nkeys, (int) direction)
+		 RelationGetRelationName(scan->rs_scan.rs_rd), scan->rs_scan.rs_nkeys, (int) direction)
 #define HEAPAMSLOTDEBUG_2 \
 	elog(DEBUG2, "heapam_getnext returning EOS")
 #define HEAPAMSLOTDEBUG_3 \
@@ -1767,7 +1779,7 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 #endif
 
 TupleTableSlot *
-heap_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
+heap_getnextslot(StorageScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
 {
 	HeapScanDesc scan = (HeapScanDesc) sscan;
 
@@ -1775,11 +1787,11 @@ heap_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *sl
 
 	HEAPAMSLOTDEBUG_1;			/* heap_getnext( info ) */
 
-	if (scan->rs_pageatatime)
+	if (scan->rs_pagescan.rs_pageatatime)
 		heapgettup_pagemode(scan, direction,
-							scan->rs_nkeys, scan->rs_key);
+							scan->rs_scan.rs_nkeys, scan->rs_scan.rs_key);
 	else
-		heapgettup(scan, direction, scan->rs_nkeys, scan->rs_key);
+		heapgettup(scan, direction, scan->rs_scan.rs_nkeys, scan->rs_scan.rs_key);
 
 	if (scan->rs_ctup.t_data == NULL)
 	{
@@ -1794,7 +1806,7 @@ heap_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *sl
 	 */
 	HEAPAMSLOTDEBUG_3;			/* heap_getnext returning tuple */
 
-	pgstat_count_heap_getnext(scan->rs_rd);
+	pgstat_count_heap_getnext(scan->rs_scan.rs_rd);
 	return ExecStoreTuple(heap_copytuple(&(scan->rs_ctup)),
 						  slot, InvalidBuffer, true);
 }
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index dd0e05edb7..a1c2407a3e 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -21,7 +21,9 @@
 #include "postgres.h"
 
 #include "access/heapam.h"
+#include "access/relscan.h"
 #include "access/storageamapi.h"
+#include "pgstat.h"
 #include "utils/builtins.h"
 #include "utils/rel.h"
 
@@ -246,6 +248,44 @@ heapam_form_tuple_by_datum(Datum data, Oid tableoid)
 	return heap_form_tuple_by_datum(data, tableoid);
 }
 
+static ParallelHeapScanDesc
+heapam_get_parallelheapscandesc(StorageScanDesc sscan)
+{
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
+	return scan->rs_parallel;
+}
+
+static HeapPageScanDesc
+heapam_get_heappagescandesc(StorageScanDesc sscan)
+{
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
+	return &scan->rs_pagescan;
+}
+
+static StorageTuple
+heapam_fetch_tuple_from_offset(StorageScanDesc sscan, BlockNumber blkno, OffsetNumber offset)
+{
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+	Page		dp;
+	ItemId		lp;
+
+	dp = (Page) BufferGetPage(scan->rs_scan.rs_cbuf);
+	lp = PageGetItemId(dp, offset);
+	Assert(ItemIdIsNormal(lp));
+
+	scan->rs_ctup.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lp);
+	scan->rs_ctup.t_len = ItemIdGetLength(lp);
+	scan->rs_ctup.t_tableOid = scan->rs_scan.rs_rd->rd_id;
+	ItemPointerSet(&scan->rs_ctup.t_self, blkno, offset);
+
+	pgstat_count_heap_fetch(scan->rs_scan.rs_rd);
+
+	return &(scan->rs_ctup);
+}
+
+
 Datum
 heapam_storage_handler(PG_FUNCTION_ARGS)
 {
@@ -266,6 +306,19 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 	amroutine->scan_rescan = heap_rescan;
 	amroutine->scan_update_snapshot = heap_update_snapshot;
 	amroutine->hot_search_buffer = heap_hot_search_buffer;
+	amroutine->scan_fetch_tuple_from_offset = heapam_fetch_tuple_from_offset;
+
+	/*
+	 * The following routine needs to be provided when the storage support
+	 * parallel sequential scan
+	 */
+	amroutine->scan_get_parallelheapscandesc = heapam_get_parallelheapscandesc;
+
+	/*
+	 * The following routine needs to be provided when the storage support
+	 * BitmapHeap and Sample Scans
+	 */
+	amroutine->scan_get_heappagescandesc = heapam_get_heappagescandesc;
 
 	amroutine->tuple_fetch = heapam_fetch;
 	amroutine->tuple_insert = heapam_heap_insert;
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 02c88bf1f3..1f49d684e5 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -478,10 +478,10 @@ systable_recheck_tuple(SysScanDesc sysscan, HeapTuple tup)
 	}
 	else
 	{
-		HeapScanDesc scan = sysscan->scan;
+		StorageScanDesc scan = sysscan->scan;
 
 		Assert(IsMVCCSnapshot(scan->rs_snapshot));
-		Assert(tup == &scan->rs_ctup);
+		/* hari Assert(tup == &scan->rs_ctup); */
 		Assert(BufferIsValid(scan->rs_cbuf));
 		/* must hold a buffer lock to call HeapTupleSatisfiesVisibility */
 		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
diff --git a/src/backend/access/storage/storageam.c b/src/backend/access/storage/storageam.c
index e56dc189ed..8bc09c39ab 100644
--- a/src/backend/access/storage/storageam.c
+++ b/src/backend/access/storage/storageam.c
@@ -56,7 +56,7 @@ storage_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * stuple,
  *		Caller must hold a suitable lock on the correct relation.
  * ----------------
  */
-HeapScanDesc
+StorageScanDesc
 storage_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan)
 {
 	Snapshot	snapshot;
@@ -69,6 +69,25 @@ storage_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan
 												true, true, true, false, false, true);
 }
 
+ParallelHeapScanDesc
+storageam_get_parallelheapscandesc(StorageScanDesc sscan)
+{
+	return sscan->rs_rd->rd_stamroutine->scan_get_parallelheapscandesc(sscan);
+}
+
+HeapPageScanDesc
+storageam_get_heappagescandesc(StorageScanDesc sscan)
+{
+	/*
+	 * Planner should have already validated whether the current storage
+	 * supports Page scans are not? This function will be called only from
+	 * Bitmap Heap scan and sample scan
+	 */
+	Assert(sscan->rs_rd->rd_stamroutine->scan_get_heappagescandesc != NULL);
+
+	return sscan->rs_rd->rd_stamroutine->scan_get_heappagescandesc(sscan);
+}
+
 /*
  * heap_setscanlimits - restrict range of a heapscan
  *
@@ -76,7 +95,7 @@ storage_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan
  * numBlks is number of pages to scan (InvalidBlockNumber means "all")
  */
 void
-storage_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
+storage_setscanlimits(StorageScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
 {
 	sscan->rs_rd->rd_stamroutine->scansetlimits(sscan, startBlk, numBlks);
 }
@@ -106,7 +125,7 @@ storage_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numB
  * also allows control of whether page-mode visibility checking is used.
  * ----------------
  */
-HeapScanDesc
+StorageScanDesc
 storage_beginscan(Relation relation, Snapshot snapshot,
 				  int nkeys, ScanKey key)
 {
@@ -114,7 +133,7 @@ storage_beginscan(Relation relation, Snapshot snapshot,
 												true, true, true, false, false, false);
 }
 
-HeapScanDesc
+StorageScanDesc
 storage_beginscan_catalog(Relation relation, int nkeys, ScanKey key)
 {
 	Oid			relid = RelationGetRelid(relation);
@@ -124,7 +143,7 @@ storage_beginscan_catalog(Relation relation, int nkeys, ScanKey key)
 												true, true, true, false, false, true);
 }
 
-HeapScanDesc
+StorageScanDesc
 storage_beginscan_strat(Relation relation, Snapshot snapshot,
 						int nkeys, ScanKey key,
 						bool allow_strat, bool allow_sync)
@@ -134,7 +153,7 @@ storage_beginscan_strat(Relation relation, Snapshot snapshot,
 												false, false, false);
 }
 
-HeapScanDesc
+StorageScanDesc
 storage_beginscan_bm(Relation relation, Snapshot snapshot,
 					 int nkeys, ScanKey key)
 {
@@ -142,7 +161,7 @@ storage_beginscan_bm(Relation relation, Snapshot snapshot,
 												false, false, true, true, false, false);
 }
 
-HeapScanDesc
+StorageScanDesc
 storage_beginscan_sampling(Relation relation, Snapshot snapshot,
 						   int nkeys, ScanKey key,
 						   bool allow_strat, bool allow_sync, bool allow_pagemode)
@@ -157,7 +176,7 @@ storage_beginscan_sampling(Relation relation, Snapshot snapshot,
  * ----------------
  */
 void
-storage_rescan(HeapScanDesc scan,
+storage_rescan(StorageScanDesc scan,
 			   ScanKey key)
 {
 	scan->rs_rd->rd_stamroutine->scan_rescan(scan, key, false, false, false, false);
@@ -173,7 +192,7 @@ storage_rescan(HeapScanDesc scan,
  * ----------------
  */
 void
-storage_rescan_set_params(HeapScanDesc scan, ScanKey key,
+storage_rescan_set_params(StorageScanDesc scan, ScanKey key,
 						  bool allow_strat, bool allow_sync, bool allow_pagemode)
 {
 	scan->rs_rd->rd_stamroutine->scan_rescan(scan, key, true,
@@ -188,7 +207,7 @@ storage_rescan_set_params(HeapScanDesc scan, ScanKey key,
  * ----------------
  */
 void
-storage_endscan(HeapScanDesc scan)
+storage_endscan(StorageScanDesc scan)
 {
 	scan->rs_rd->rd_stamroutine->scan_end(scan);
 }
@@ -201,23 +220,29 @@ storage_endscan(HeapScanDesc scan)
  * ----------------
  */
 void
-storage_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
+storage_update_snapshot(StorageScanDesc scan, Snapshot snapshot)
 {
 	scan->rs_rd->rd_stamroutine->scan_update_snapshot(scan, snapshot);
 }
 
 StorageTuple
-storage_getnext(HeapScanDesc sscan, ScanDirection direction)
+storage_getnext(StorageScanDesc sscan, ScanDirection direction)
 {
 	return sscan->rs_rd->rd_stamroutine->scan_getnext(sscan, direction);
 }
 
 TupleTableSlot *
-storage_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
+storage_getnextslot(StorageScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
 {
 	return sscan->rs_rd->rd_stamroutine->scan_getnextslot(sscan, direction, slot);
 }
 
+StorageTuple
+storage_fetch_tuple_from_offset(StorageScanDesc sscan, BlockNumber blkno, OffsetNumber offset)
+{
+	return sscan->rs_rd->rd_stamroutine->scan_fetch_tuple_from_offset(sscan, blkno, offset);
+}
+
 /*
  * Insert a tuple from a slot into storage AM routine
  */
diff --git a/src/backend/access/tablesample/system.c b/src/backend/access/tablesample/system.c
index f888e04f40..8a9e7056eb 100644
--- a/src/backend/access/tablesample/system.c
+++ b/src/backend/access/tablesample/system.c
@@ -183,7 +183,7 @@ static BlockNumber
 system_nextsampleblock(SampleScanState *node)
 {
 	SystemSamplerData *sampler = (SystemSamplerData *) node->tsm_state;
-	HeapScanDesc scan = node->ss.ss_currentScanDesc;
+	HeapPageScanDesc scan = node->pagescan;
 	BlockNumber nextblock = sampler->nextblock;
 	uint32		hashinput[2];
 
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index dd190a959f..9b8e41b7b8 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -577,7 +577,7 @@ boot_openrel(char *relname)
 	int			i;
 	struct typmap **app;
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tup;
 
 	if (strlen(relname) >= NAMEDATALEN)
@@ -893,7 +893,7 @@ gettype(char *type)
 {
 	int			i;
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tup;
 	struct typmap **app;
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 0614ff202f..d5caa936cd 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -822,7 +822,7 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 					ScanKeyData key[2];
 					int			keycount;
 					Relation	rel;
-					HeapScanDesc scan;
+					StorageScanDesc scan;
 					HeapTuple	tuple;
 
 					keycount = 0;
@@ -880,7 +880,7 @@ getRelationsInNamespace(Oid namespaceId, char relkind)
 	List	   *relations = NIL;
 	ScanKeyData key[2];
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 
 	ScanKeyInit(&key[0],
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index a96b5ab7e4..54dadac1dc 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1900,7 +1900,7 @@ index_update_stats(Relation rel,
 		ReindexIsProcessingHeap(RelationRelationId))
 	{
 		/* don't assume syscache will work */
-		HeapScanDesc pg_class_scan;
+		StorageScanDesc pg_class_scan;
 		ScanKeyData key[1];
 
 		ScanKeyInit(&key[0],
@@ -2213,7 +2213,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 {
 	bool		is_system_catalog;
 	bool		checking_uniqueness;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	heapTuple;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
@@ -2652,7 +2652,7 @@ IndexCheckExclusion(Relation heapRelation,
 					Relation indexRelation,
 					IndexInfo *indexInfo)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	heapTuple;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
@@ -2966,7 +2966,7 @@ validate_index_heapscan(Relation heapRelation,
 						Snapshot snapshot,
 						v_i_state *state)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	heapTuple;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 081c23aa1b..ddf8d5ae3f 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1266,7 +1266,7 @@ check_default_allows_bound(Relation parent, Relation default_rel,
 		Snapshot	snapshot;
 		TupleDesc	tupdesc;
 		ExprContext *econtext;
-		HeapScanDesc scan;
+		StorageScanDesc scan;
 		MemoryContext oldCxt;
 		TupleTableSlot *tupslot;
 
diff --git a/src/backend/catalog/pg_conversion.c b/src/backend/catalog/pg_conversion.c
index 2a0cdca0b8..6beeae4b3f 100644
--- a/src/backend/catalog/pg_conversion.c
+++ b/src/backend/catalog/pg_conversion.c
@@ -151,7 +151,7 @@ RemoveConversionById(Oid conversionOid)
 {
 	Relation	rel;
 	HeapTuple	tuple;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	ScanKeyData scanKeyData;
 
 	ScanKeyInit(&scanKeyData,
diff --git a/src/backend/catalog/pg_db_role_setting.c b/src/backend/catalog/pg_db_role_setting.c
index 84ce11f75e..91466e3afc 100644
--- a/src/backend/catalog/pg_db_role_setting.c
+++ b/src/backend/catalog/pg_db_role_setting.c
@@ -171,7 +171,7 @@ void
 DropSetting(Oid databaseid, Oid roleid)
 {
 	Relation	relsetting;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	ScanKeyData keys[2];
 	HeapTuple	tup;
 	int			numkeys = 0;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 93d6a84ac0..dce108f358 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -314,7 +314,7 @@ GetAllTablesPublicationRelations(void)
 {
 	Relation	classRel;
 	ScanKeyData key[1];
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	List	   *result = NIL;
 
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index 607692bb09..7658020a8f 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -377,7 +377,7 @@ void
 RemoveSubscriptionRel(Oid subid, Oid relid)
 {
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	ScanKeyData skey[2];
 	HeapTuple	tup;
 	int			nkeys = 0;
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 7e40532b1d..d6b01da629 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -749,7 +749,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	Datum	   *values;
 	bool	   *isnull;
 	IndexScanDesc indexScan;
-	HeapScanDesc heapScan;
+	StorageScanDesc heapScan;
 	bool		use_wal;
 	bool		is_system_catalog;
 	TransactionId OldestXmin;
@@ -1668,7 +1668,7 @@ static List *
 get_tables_to_cluster(MemoryContext cluster_context)
 {
 	Relation	indRelation;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	ScanKeyData entry;
 	HeapTuple	indexTuple;
 	Form_pg_index index;
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 901894c90a..41ea9a017b 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2032,7 +2032,7 @@ CopyTo(CopyState cstate)
 	{
 		Datum	   *values;
 		bool	   *nulls;
-		HeapScanDesc scandesc;
+		StorageScanDesc scandesc;
 		HeapTuple	tuple;
 
 		values = (Datum *) palloc(num_phys_attrs * sizeof(Datum));
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 91732f911f..779ebf8a35 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -99,7 +99,7 @@ static int	errdetail_busy_db(int notherbackends, int npreparedxacts);
 Oid
 createdb(ParseState *pstate, const CreatedbStmt *stmt)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	Relation	rel;
 	Oid			src_dboid;
 	Oid			src_owner;
@@ -1872,7 +1872,7 @@ static void
 remove_dbtablespaces(Oid db_id)
 {
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 
 	rel = heap_open(TableSpaceRelationId, AccessShareLock);
@@ -1939,7 +1939,7 @@ check_db_file_conflict(Oid db_id)
 {
 	bool		result = false;
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 
 	rel = heap_open(TableSpaceRelationId, AccessShareLock);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 22c0201481..a40e9eb142 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1894,7 +1894,7 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 {
 	Oid			objectOid;
 	Relation	relationRelation;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	ScanKeyData scan_keys[1];
 	HeapTuple	tuple;
 	MemoryContext private_context;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 13ea3a2938..dd126e8e44 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4492,7 +4492,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		bool	   *isnull;
 		TupleTableSlot *oldslot;
 		TupleTableSlot *newslot;
-		HeapScanDesc scan;
+		StorageScanDesc scan;
 		HeapTuple	tuple;
 		MemoryContext oldCxt;
 		List	   *dropped_attrs = NIL;
@@ -5065,7 +5065,7 @@ find_typed_table_dependencies(Oid typeOid, const char *typeName, DropBehavior be
 {
 	Relation	classRel;
 	ScanKeyData key[1];
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	List	   *result = NIL;
 
@@ -8219,7 +8219,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	Expr	   *origexpr;
 	ExprState  *exprstate;
 	TupleDesc	tupdesc;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	ExprContext *econtext;
 	MemoryContext oldcxt;
@@ -8302,7 +8302,7 @@ validateForeignKeyConstraint(char *conname,
 							 Oid pkindOid,
 							 Oid constraintOid)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	Trigger		trig;
 	Snapshot	snapshot;
@@ -10809,7 +10809,7 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 	ListCell   *l;
 	ScanKeyData key[1];
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	Oid			orig_tablespaceoid;
 	Oid			new_tablespaceoid;
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 67648db528..9a131f4e5f 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -402,7 +402,7 @@ DropTableSpace(DropTableSpaceStmt *stmt)
 {
 #ifdef HAVE_SYMLINK
 	char	   *tablespacename = stmt->tablespacename;
-	HeapScanDesc scandesc;
+	StorageScanDesc scandesc;
 	Relation	rel;
 	HeapTuple	tuple;
 	ScanKeyData entry[1];
@@ -913,7 +913,7 @@ RenameTableSpace(const char *oldname, const char *newname)
 	Oid			tspId;
 	Relation	rel;
 	ScanKeyData entry[1];
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tup;
 	HeapTuple	newtuple;
 	Form_pg_tablespace newform;
@@ -988,7 +988,7 @@ AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt)
 {
 	Relation	rel;
 	ScanKeyData entry[1];
-	HeapScanDesc scandesc;
+	StorageScanDesc scandesc;
 	HeapTuple	tup;
 	Oid			tablespaceoid;
 	Datum		datum;
@@ -1382,7 +1382,7 @@ get_tablespace_oid(const char *tablespacename, bool missing_ok)
 {
 	Oid			result;
 	Relation	rel;
-	HeapScanDesc scandesc;
+	StorageScanDesc scandesc;
 	HeapTuple	tuple;
 	ScanKeyData entry[1];
 
@@ -1428,7 +1428,7 @@ get_tablespace_name(Oid spc_oid)
 {
 	char	   *result;
 	Relation	rel;
-	HeapScanDesc scandesc;
+	StorageScanDesc scandesc;
 	HeapTuple	tuple;
 	ScanKeyData entry[1];
 
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 4d7833276c..eba22aa424 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2382,7 +2382,7 @@ AlterDomainNotNull(List *names, bool notNull)
 			RelToCheck *rtc = (RelToCheck *) lfirst(rt);
 			Relation	testrel = rtc->rel;
 			TupleDesc	tupdesc = RelationGetDescr(testrel);
-			HeapScanDesc scan;
+			StorageScanDesc scan;
 			HeapTuple	tuple;
 			Snapshot	snapshot;
 
@@ -2778,7 +2778,7 @@ validateDomainConstraint(Oid domainoid, char *ccbin)
 		RelToCheck *rtc = (RelToCheck *) lfirst(rt);
 		Relation	testrel = rtc->rel;
 		TupleDesc	tupdesc = RelationGetDescr(testrel);
-		HeapScanDesc scan;
+		StorageScanDesc scan;
 		HeapTuple	tuple;
 		Snapshot	snapshot;
 
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index c742753743..fffb67ae6b 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -529,7 +529,7 @@ get_all_vacuum_rels(void)
 {
 	List	   *vacrels = NIL;
 	Relation	pgclass;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 
 	pgclass = heap_open(RelationRelationId, AccessShareLock);
@@ -1184,7 +1184,7 @@ vac_truncate_clog(TransactionId frozenXID,
 {
 	TransactionId nextXID = ReadNewTransactionId();
 	Relation	relation;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	Oid			oldestxid_datoid;
 	Oid			minmulti_datoid;
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index 5c1da5c5df..dc07f3002e 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -55,14 +55,14 @@
 
 
 static TupleTableSlot *BitmapHeapNext(BitmapHeapScanState *node);
-static void bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres);
+static void bitgetpage(BitmapHeapScanState *node, TBMIterateResult *tbmres);
 static inline void BitmapDoneInitializingSharedState(
 								  ParallelBitmapHeapState *pstate);
 static inline void BitmapAdjustPrefetchIterator(BitmapHeapScanState *node,
 							 TBMIterateResult *tbmres);
 static inline void BitmapAdjustPrefetchTarget(BitmapHeapScanState *node);
 static inline void BitmapPrefetch(BitmapHeapScanState *node,
-			   HeapScanDesc scan);
+			   StorageScanDesc scan);
 static bool BitmapShouldInitializeSharedState(
 								  ParallelBitmapHeapState *pstate);
 
@@ -77,7 +77,8 @@ static TupleTableSlot *
 BitmapHeapNext(BitmapHeapScanState *node)
 {
 	ExprContext *econtext;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
+	HeapPageScanDesc pagescan;
 	TIDBitmap  *tbm;
 	TBMIterator *tbmiterator = NULL;
 	TBMSharedIterator *shared_tbmiterator = NULL;
@@ -93,6 +94,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 	econtext = node->ss.ps.ps_ExprContext;
 	slot = node->ss.ss_ScanTupleSlot;
 	scan = node->ss.ss_currentScanDesc;
+	pagescan = node->pagescan;
 	tbm = node->tbm;
 	if (pstate == NULL)
 		tbmiterator = node->tbmiterator;
@@ -192,8 +194,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 
 	for (;;)
 	{
-		Page		dp;
-		ItemId		lp;
+		StorageTuple tuple;
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -220,7 +221,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 			 * least AccessShareLock on the table before performing any of the
 			 * indexscans, but let's be safe.)
 			 */
-			if (tbmres->blockno >= scan->rs_nblocks)
+			if (tbmres->blockno >= pagescan->rs_nblocks)
 			{
 				node->tbmres = tbmres = NULL;
 				continue;
@@ -243,14 +244,14 @@ BitmapHeapNext(BitmapHeapScanState *node)
 				 * The number of tuples on this page is put into
 				 * scan->rs_ntuples; note we don't fill scan->rs_vistuples.
 				 */
-				scan->rs_ntuples = tbmres->ntuples;
+				pagescan->rs_ntuples = tbmres->ntuples;
 			}
 			else
 			{
 				/*
 				 * Fetch the current heap page and identify candidate tuples.
 				 */
-				bitgetpage(scan, tbmres);
+				bitgetpage(node, tbmres);
 			}
 
 			if (tbmres->ntuples >= 0)
@@ -261,7 +262,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 			/*
 			 * Set rs_cindex to first slot to examine
 			 */
-			scan->rs_cindex = 0;
+			pagescan->rs_cindex = 0;
 
 			/* Adjust the prefetch target */
 			BitmapAdjustPrefetchTarget(node);
@@ -271,7 +272,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 			/*
 			 * Continuing in previously obtained page; advance rs_cindex
 			 */
-			scan->rs_cindex++;
+			pagescan->rs_cindex++;
 
 #ifdef USE_PREFETCH
 
@@ -298,7 +299,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 		/*
 		 * Out of range?  If so, nothing more to look at on this page
 		 */
-		if (scan->rs_cindex < 0 || scan->rs_cindex >= scan->rs_ntuples)
+		if (pagescan->rs_cindex < 0 || pagescan->rs_cindex >= pagescan->rs_ntuples)
 		{
 			node->tbmres = tbmres = NULL;
 			continue;
@@ -325,23 +326,14 @@ BitmapHeapNext(BitmapHeapScanState *node)
 			/*
 			 * Okay to fetch the tuple.
 			 */
-			targoffset = scan->rs_vistuples[scan->rs_cindex];
-			dp = (Page) BufferGetPage(scan->rs_cbuf);
-			lp = PageGetItemId(dp, targoffset);
-			Assert(ItemIdIsNormal(lp));
-
-			scan->rs_ctup.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lp);
-			scan->rs_ctup.t_len = ItemIdGetLength(lp);
-			scan->rs_ctup.t_tableOid = scan->rs_rd->rd_id;
-			ItemPointerSet(&scan->rs_ctup.t_self, tbmres->blockno, targoffset);
-
-			pgstat_count_heap_fetch(scan->rs_rd);
+			targoffset = pagescan->rs_vistuples[pagescan->rs_cindex];
+			tuple = storage_fetch_tuple_from_offset(scan, tbmres->blockno, targoffset);
 
 			/*
 			 * Set up the result slot to point to this tuple.  Note that the
 			 * slot acquires a pin on the buffer.
 			 */
-			ExecStoreTuple(&scan->rs_ctup,
+			ExecStoreTuple(tuple,
 						   slot,
 						   scan->rs_cbuf,
 						   false);
@@ -383,8 +375,10 @@ BitmapHeapNext(BitmapHeapScanState *node)
  * interesting according to the bitmap, and visible according to the snapshot.
  */
 static void
-bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
+bitgetpage(BitmapHeapScanState *node, TBMIterateResult *tbmres)
 {
+	StorageScanDesc scan = node->ss.ss_currentScanDesc;
+	HeapPageScanDesc pagescan = node->pagescan;
 	BlockNumber page = tbmres->blockno;
 	Buffer		buffer;
 	Snapshot	snapshot;
@@ -393,7 +387,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 	/*
 	 * Acquire pin on the target heap page, trading in any pin we held before.
 	 */
-	Assert(page < scan->rs_nblocks);
+	Assert(page < pagescan->rs_nblocks);
 
 	scan->rs_cbuf = ReleaseAndReadBuffer(scan->rs_cbuf,
 										 scan->rs_rd,
@@ -436,7 +430,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 			ItemPointerSet(&tid, page, offnum);
 			if (storage_hot_search_buffer(&tid, scan->rs_rd, buffer, snapshot,
 										  &heapTuple, NULL, true))
-				scan->rs_vistuples[ntup++] = ItemPointerGetOffsetNumber(&tid);
+				pagescan->rs_vistuples[ntup++] = ItemPointerGetOffsetNumber(&tid);
 		}
 	}
 	else
@@ -452,23 +446,20 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 		for (offnum = FirstOffsetNumber; offnum <= maxoff; offnum = OffsetNumberNext(offnum))
 		{
 			ItemId		lp;
-			HeapTupleData loctup;
+			StorageTuple loctup;
 			bool		valid;
 
 			lp = PageGetItemId(dp, offnum);
 			if (!ItemIdIsNormal(lp))
 				continue;
-			loctup.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lp);
-			loctup.t_len = ItemIdGetLength(lp);
-			loctup.t_tableOid = scan->rs_rd->rd_id;
-			ItemPointerSet(&loctup.t_self, page, offnum);
-			valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine, &loctup, snapshot, buffer);
+			loctup = storage_fetch_tuple_from_offset(scan, page, offnum);
+			valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine, loctup, snapshot, buffer);
 			if (valid)
 			{
-				scan->rs_vistuples[ntup++] = offnum;
-				PredicateLockTuple(scan->rs_rd, &loctup, snapshot);
+				pagescan->rs_vistuples[ntup++] = offnum;
+				PredicateLockTuple(scan->rs_rd, loctup, snapshot);
 			}
-			CheckForSerializableConflictOut(valid, scan->rs_rd, &loctup,
+			CheckForSerializableConflictOut(valid, scan->rs_rd, loctup,
 											buffer, snapshot);
 		}
 	}
@@ -476,7 +467,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
 
 	Assert(ntup <= MaxHeapTuplesPerPage);
-	scan->rs_ntuples = ntup;
+	pagescan->rs_ntuples = ntup;
 }
 
 /*
@@ -602,7 +593,7 @@ BitmapAdjustPrefetchTarget(BitmapHeapScanState *node)
  * BitmapPrefetch - Prefetch, if prefetch_pages are behind prefetch_target
  */
 static inline void
-BitmapPrefetch(BitmapHeapScanState *node, HeapScanDesc scan)
+BitmapPrefetch(BitmapHeapScanState *node, StorageScanDesc scan)
 {
 #ifdef USE_PREFETCH
 	ParallelBitmapHeapState *pstate = node->pstate;
@@ -793,7 +784,7 @@ void
 ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 {
 	Relation	relation;
-	HeapScanDesc scanDesc;
+	StorageScanDesc scanDesc;
 
 	/*
 	 * extract information from the node
@@ -958,6 +949,8 @@ ExecInitBitmapHeapScan(BitmapHeapScan *node, EState *estate, int eflags)
 															0,
 															NULL);
 
+	scanstate->pagescan = storageam_get_heappagescandesc(scanstate->ss.ss_currentScanDesc);
+
 	/*
 	 * get the scan type from the relation descriptor.
 	 */
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index 4dbc67989f..e85abd1555 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -31,9 +31,7 @@ static TupleTableSlot *SampleNext(SampleScanState *node);
 static void tablesample_init(SampleScanState *scanstate);
 static StorageTuple tablesample_getnext(SampleScanState *scanstate);
 static bool SampleTupleVisible(StorageTuple tuple, OffsetNumber tupoffset,
-				   HeapScanDesc scan);
-
-/* hari */
+				   SampleScanState *scanstate);
 
 /* ----------------------------------------------------------------
  *						Scan Support
@@ -357,6 +355,7 @@ tablesample_init(SampleScanState *scanstate)
 									   scanstate->use_bulkread,
 									   allow_sync,
 									   scanstate->use_pagemode);
+		scanstate->pagescan = storageam_get_heappagescandesc(scanstate->ss.ss_currentScanDesc);
 	}
 	else
 	{
@@ -382,10 +381,11 @@ static StorageTuple
 tablesample_getnext(SampleScanState *scanstate)
 {
 	TsmRoutine *tsm = scanstate->tsmroutine;
-	HeapScanDesc scan = scanstate->ss.ss_currentScanDesc;
-	HeapTuple	tuple = &(scan->rs_ctup);
+	StorageScanDesc scan = scanstate->ss.ss_currentScanDesc;
+	HeapPageScanDesc pagescan = scanstate->pagescan;
+	StorageTuple tuple;
 	Snapshot	snapshot = scan->rs_snapshot;
-	bool		pagemode = scan->rs_pageatatime;
+	bool		pagemode = pagescan->rs_pageatatime;
 	BlockNumber blockno;
 	Page		page;
 	bool		all_visible;
@@ -396,10 +396,9 @@ tablesample_getnext(SampleScanState *scanstate)
 		/*
 		 * return null immediately if relation is empty
 		 */
-		if (scan->rs_nblocks == 0)
+		if (pagescan->rs_nblocks == 0)
 		{
 			Assert(!BufferIsValid(scan->rs_cbuf));
-			tuple->t_data = NULL;
 			return NULL;
 		}
 		if (tsm->NextSampleBlock)
@@ -407,13 +406,12 @@ tablesample_getnext(SampleScanState *scanstate)
 			blockno = tsm->NextSampleBlock(scanstate);
 			if (!BlockNumberIsValid(blockno))
 			{
-				tuple->t_data = NULL;
 				return NULL;
 			}
 		}
 		else
-			blockno = scan->rs_startblock;
-		Assert(blockno < scan->rs_nblocks);
+			blockno = pagescan->rs_startblock;
+		Assert(blockno < pagescan->rs_nblocks);
 		heapgetpage(scan, blockno);
 		scan->rs_inited = true;
 	}
@@ -456,14 +454,12 @@ tablesample_getnext(SampleScanState *scanstate)
 			if (!ItemIdIsNormal(itemid))
 				continue;
 
-			tuple->t_data = (HeapTupleHeader) PageGetItem(page, itemid);
-			tuple->t_len = ItemIdGetLength(itemid);
-			ItemPointerSet(&(tuple->t_self), blockno, tupoffset);
+			tuple = storage_fetch_tuple_from_offset(scan, blockno, tupoffset);
 
 			if (all_visible)
 				visible = true;
 			else
-				visible = SampleTupleVisible(tuple, tupoffset, scan);
+				visible = SampleTupleVisible(tuple, tupoffset, scanstate);
 
 			/* in pagemode, heapgetpage did this for us */
 			if (!pagemode)
@@ -494,14 +490,14 @@ tablesample_getnext(SampleScanState *scanstate)
 		if (tsm->NextSampleBlock)
 		{
 			blockno = tsm->NextSampleBlock(scanstate);
-			Assert(!scan->rs_syncscan);
+			Assert(!pagescan->rs_syncscan);
 			finished = !BlockNumberIsValid(blockno);
 		}
 		else
 		{
 			/* Without NextSampleBlock, just do a plain forward seqscan. */
 			blockno++;
-			if (blockno >= scan->rs_nblocks)
+			if (blockno >= pagescan->rs_nblocks)
 				blockno = 0;
 
 			/*
@@ -514,10 +510,10 @@ tablesample_getnext(SampleScanState *scanstate)
 			 * a little bit backwards on every invocation, which is confusing.
 			 * We don't guarantee any specific ordering in general, though.
 			 */
-			if (scan->rs_syncscan)
+			if (pagescan->rs_syncscan)
 				ss_report_location(scan->rs_rd, blockno);
 
-			finished = (blockno == scan->rs_startblock);
+			finished = (blockno == pagescan->rs_startblock);
 		}
 
 		/*
@@ -529,12 +525,11 @@ tablesample_getnext(SampleScanState *scanstate)
 				ReleaseBuffer(scan->rs_cbuf);
 			scan->rs_cbuf = InvalidBuffer;
 			scan->rs_cblock = InvalidBlockNumber;
-			tuple->t_data = NULL;
 			scan->rs_inited = false;
 			return NULL;
 		}
 
-		Assert(blockno < scan->rs_nblocks);
+		Assert(blockno < pagescan->rs_nblocks);
 		heapgetpage(scan, blockno);
 
 		/* Re-establish state for new page */
@@ -549,16 +544,19 @@ tablesample_getnext(SampleScanState *scanstate)
 	/* Count successfully-fetched tuples as heap fetches */
 	pgstat_count_heap_getnext(scan->rs_rd);
 
-	return &(scan->rs_ctup);
+	return tuple;
 }
 
 /*
  * Check visibility of the tuple.
  */
 static bool
-SampleTupleVisible(StorageTuple tuple, OffsetNumber tupoffset, HeapScanDesc scan) //hari
+SampleTupleVisible(StorageTuple tuple, OffsetNumber tupoffset, SampleScanState *scanstate)
 {
-	if (scan->rs_pageatatime)
+	StorageScanDesc scan = scanstate->ss.ss_currentScanDesc;
+	HeapPageScanDesc pagescan = scanstate->pagescan;
+
+	if (pagescan->rs_pageatatime)
 	{
 		/*
 		 * In pageatatime mode, heapgetpage() already did visibility checks,
@@ -570,12 +568,12 @@ SampleTupleVisible(StorageTuple tuple, OffsetNumber tupoffset, HeapScanDesc scan
 		 * gain to justify the restriction.
 		 */
 		int			start = 0,
-					end = scan->rs_ntuples - 1;
+					end = pagescan->rs_ntuples - 1;
 
 		while (start <= end)
 		{
 			int			mid = (start + end) / 2;
-			OffsetNumber curoffset = scan->rs_vistuples[mid];
+			OffsetNumber curoffset = pagescan->rs_vistuples[mid];
 
 			if (tupoffset == curoffset)
 				return true;
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index 48c7ab7268..e07ac1b232 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -317,9 +317,10 @@ void
 ExecSeqScanReInitializeDSM(SeqScanState *node,
 						   ParallelContext *pcxt)
 {
-	HeapScanDesc scan = node->ss.ss_currentScanDesc;
+	ParallelHeapScanDesc pscan;
 
-	heap_parallelscan_reinitialize(scan->rs_parallel);
+	pscan = storageam_get_parallelheapscandesc(node->ss.ss_currentScanDesc);
+	heap_parallelscan_reinitialize(pscan);
 }
 
 /* ----------------------------------------------------------------
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 8c60b35068..c9b018ef85 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -21,6 +21,7 @@
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/nbtree.h"
+#include "access/storageamapi.h"
 #include "access/sysattr.h"
 #include "access/transam.h"
 #include "access/xlog.h"
@@ -253,7 +254,8 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 			info->amsearchnulls = amroutine->amsearchnulls;
 			info->amcanparallel = amroutine->amcanparallel;
 			info->amhasgettuple = (amroutine->amgettuple != NULL);
-			info->amhasgetbitmap = (amroutine->amgetbitmap != NULL);
+			info->amhasgetbitmap = ((amroutine->amgetbitmap != NULL)
+									&& (relation->rd_stamroutine->scan_get_heappagescandesc != NULL));
 			info->amcostestimate = amroutine->amcostestimate;
 			Assert(info->amcostestimate != NULL);
 
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index c2f8a7802c..e9d8a0752f 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1865,7 +1865,7 @@ get_database_list(void)
 {
 	List	   *dblist = NIL;
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tup;
 	MemoryContext resultcxt;
 
@@ -1931,7 +1931,7 @@ do_autovacuum(void)
 {
 	Relation	classRel;
 	HeapTuple	tuple;
-	HeapScanDesc relScan;
+	StorageScanDesc relScan;
 	Form_pg_database dbForm;
 	List	   *table_oids = NIL;
 	List	   *orphan_oids = NIL;
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 20f0c3e692..a69740e89e 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -1207,7 +1207,7 @@ pgstat_collect_oids(Oid catalogid)
 	HTAB	   *htab;
 	HASHCTL		hash_ctl;
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tup;
 	Snapshot	snapshot;
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 3a7c65c74e..6be49c773f 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -107,7 +107,7 @@ get_subscription_list(void)
 {
 	List	   *res = NIL;
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tup;
 	MemoryContext resultcxt;
 
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 048f8f316d..42fc493125 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -420,7 +420,7 @@ DefineQueryRewrite(const char *rulename,
 		if (event_relation->rd_rel->relkind != RELKIND_VIEW &&
 			event_relation->rd_rel->relkind != RELKIND_MATVIEW)
 		{
-			HeapScanDesc scanDesc;
+			StorageScanDesc scanDesc;
 			Snapshot	snapshot;
 
 			if (event_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 0949310f33..3ed0143ef2 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -1208,7 +1208,7 @@ static bool
 ThereIsAtLeastOneRole(void)
 {
 	Relation	pg_authid_rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	bool		result;
 
 	pg_authid_rel = heap_open(AuthIdRelationId, AccessShareLock);
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index c2755cdac0..cf13c401ca 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -98,6 +98,8 @@ extern Relation heap_openrv_extended(const RangeVar *relation,
 #define heap_close(r,l)  relation_close(r,l)
 
 /* struct definitions appear in relscan.h */
+typedef struct HeapPageScanDescData *HeapPageScanDesc;
+typedef struct StorageScanDescData *StorageScanDesc;
 typedef struct HeapScanDescData *HeapScanDesc;
 typedef struct ParallelHeapScanDescData *ParallelHeapScanDesc;
 
@@ -107,25 +109,25 @@ typedef struct ParallelHeapScanDescData *ParallelHeapScanDesc;
  */
 #define HeapScanIsValid(scan) PointerIsValid(scan)
 
-extern HeapScanDesc heap_beginscan(Relation relation, Snapshot snapshot,
-			   int nkeys, ScanKey key,
-			   ParallelHeapScanDesc parallel_scan,
-			   bool allow_strat,
-			   bool allow_sync,
-			   bool allow_pagemode,
-			   bool is_bitmapscan,
-			   bool is_samplescan,
-			   bool temp_snap);
-extern void heap_setscanlimits(HeapScanDesc scan, BlockNumber startBlk,
+extern StorageScanDesc heap_beginscan(Relation relation, Snapshot snapshot,
+									  int nkeys, ScanKey key,
+									  ParallelHeapScanDesc parallel_scan,
+									  bool allow_strat,
+									  bool allow_sync,
+									  bool allow_pagemode,
+									  bool is_bitmapscan,
+									  bool is_samplescan,
+									  bool temp_snap);
+extern void heap_setscanlimits(StorageScanDesc scan, BlockNumber startBlk,
 				   BlockNumber endBlk);
-extern void heapgetpage(HeapScanDesc scan, BlockNumber page);
-extern void heap_rescan(HeapScanDesc scan, ScanKey key, bool set_params,
+extern void heapgetpage(StorageScanDesc scan, BlockNumber page);
+extern void heap_rescan(StorageScanDesc scan, ScanKey key, bool set_params,
 			bool allow_strat, bool allow_sync, bool allow_pagemode);
-extern void heap_rescan_set_params(HeapScanDesc scan, ScanKey key,
+extern void heap_rescan_set_params(StorageScanDesc scan, ScanKey key,
 					   bool allow_strat, bool allow_sync, bool allow_pagemode);
-extern void heap_endscan(HeapScanDesc scan);
-extern StorageTuple heap_getnext(HeapScanDesc scan, ScanDirection direction);
-extern TupleTableSlot *heap_getnextslot(HeapScanDesc sscan, ScanDirection direction,
+extern void heap_endscan(StorageScanDesc scan);
+extern StorageTuple heap_getnext(StorageScanDesc scan, ScanDirection direction);
+extern TupleTableSlot *heap_getnextslot(StorageScanDesc sscan, ScanDirection direction,
 				 TupleTableSlot *slot);
 extern Size heap_parallelscan_estimate(Snapshot snapshot);
 extern void heap_parallelscan_initialize(ParallelHeapScanDesc target,
@@ -181,7 +183,7 @@ extern void simple_heap_update(Relation relation, ItemPointer otid,
 				   HeapTuple tup);
 
 extern void heap_sync(Relation relation);
-extern void heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot);
+extern void heap_update_snapshot(StorageScanDesc scan, Snapshot snapshot);
 
 /* in heap/heapam_visibility.c */
 extern HTSU_Result HeapTupleSatisfiesUpdate(StorageTuple stup, CommandId curcid,
diff --git a/src/include/access/relscan.h b/src/include/access/relscan.h
index 9c603ca637..ef4cb9e30c 100644
--- a/src/include/access/relscan.h
+++ b/src/include/access/relscan.h
@@ -16,6 +16,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/storageam.h"
 #include "access/htup_details.h"
 #include "access/itup.h"
 #include "access/tupdesc.h"
@@ -42,40 +43,54 @@ typedef struct ParallelHeapScanDescData
 	char		phs_snapshot_data[FLEXIBLE_ARRAY_MEMBER];
 }			ParallelHeapScanDescData;
 
-typedef struct HeapScanDescData
+typedef struct StorageScanDescData
 {
 	/* scan parameters */
 	Relation	rs_rd;			/* heap relation descriptor */
 	Snapshot	rs_snapshot;	/* snapshot to see */
 	int			rs_nkeys;		/* number of scan keys */
 	ScanKey		rs_key;			/* array of scan key descriptors */
-	bool		rs_bitmapscan;	/* true if this is really a bitmap scan */
-	bool		rs_samplescan;	/* true if this is really a sample scan */
+
+	/* scan current state */
+	bool		rs_inited;		/* false = scan not init'd yet */
+	BlockNumber rs_cblock;		/* current block # in scan, if any */
+	Buffer		rs_cbuf;		/* current buffer in scan, if any */
+}			StorageScanDescData;
+
+typedef struct HeapPageScanDescData
+{
 	bool		rs_pageatatime; /* verify visibility page-at-a-time? */
-	bool		rs_allow_strat; /* allow or disallow use of access strategy */
-	bool		rs_allow_sync;	/* allow or disallow use of syncscan */
-	bool		rs_temp_snap;	/* unregister snapshot at scan end? */
 
 	/* state set up at initscan time */
 	BlockNumber rs_nblocks;		/* total number of blocks in rel */
 	BlockNumber rs_startblock;	/* block # to start at */
 	BlockNumber rs_numblocks;	/* max number of blocks to scan */
+
 	/* rs_numblocks is usually InvalidBlockNumber, meaning "scan whole rel" */
 	BufferAccessStrategy rs_strategy;	/* access strategy for reads */
 	bool		rs_syncscan;	/* report location to syncscan logic? */
 
-	/* scan current state */
-	bool		rs_inited;		/* false = scan not init'd yet */
-	HeapTupleData rs_ctup;		/* current tuple in scan, if any */
-	BlockNumber rs_cblock;		/* current block # in scan, if any */
-	Buffer		rs_cbuf;		/* current buffer in scan, if any */
-	/* NB: if rs_cbuf is not InvalidBuffer, we hold a pin on that buffer */
-	ParallelHeapScanDesc rs_parallel;	/* parallel scan information */
-
 	/* these fields only used in page-at-a-time mode and for bitmap scans */
 	int			rs_cindex;		/* current tuple's index in vistuples */
 	int			rs_ntuples;		/* number of visible tuples on page */
 	OffsetNumber rs_vistuples[MaxHeapTuplesPerPage];	/* their offsets */
+}			HeapPageScanDescData;
+
+typedef struct HeapScanDescData
+{
+	/* scan parameters */
+	StorageScanDescData rs_scan;	/* */
+	HeapPageScanDescData rs_pagescan;
+	bool		rs_bitmapscan;	/* true if this is really a bitmap scan */
+	bool		rs_samplescan;	/* true if this is really a sample scan */
+	bool		rs_allow_strat; /* allow or disallow use of access strategy */
+	bool		rs_allow_sync;	/* allow or disallow use of syncscan */
+	bool		rs_temp_snap;	/* unregister snapshot at scan end? */
+
+	HeapTupleData rs_ctup;		/* current tuple in scan, if any */
+
+	/* NB: if rs_cbuf is not InvalidBuffer, we hold a pin on that buffer */
+	ParallelHeapScanDesc rs_parallel;	/* parallel scan information */
 }			HeapScanDescData;
 
 /*
@@ -149,12 +164,12 @@ typedef struct ParallelIndexScanDescData
 	char		ps_snapshot_data[FLEXIBLE_ARRAY_MEMBER];
 }			ParallelIndexScanDescData;
 
-/* Struct for heap-or-index scans of system tables */
+/* Struct for storage-or-index scans of system tables */
 typedef struct SysScanDescData
 {
 	Relation	heap_rel;		/* catalog being scanned */
 	Relation	irel;			/* NULL if doing heap scan */
-	HeapScanDesc scan;			/* only valid in heap-scan case */
+	StorageScanDesc scan;		/* only valid in storage-scan case */
 	IndexScanDesc iscan;		/* only valid in index-scan case */
 	Snapshot	snapshot;		/* snapshot to unregister at end of scan */
 }			SysScanDescData;
diff --git a/src/include/access/storage_common.h b/src/include/access/storage_common.h
index 92d311450f..baba7261d4 100644
--- a/src/include/access/storage_common.h
+++ b/src/include/access/storage_common.h
@@ -27,7 +27,6 @@
 
 /* A physical tuple coming from a storage AM scan */
 typedef void *StorageTuple;
-typedef void *StorageScanDesc;
 
 /* Result codes for HeapTupleSatisfiesVacuum */
 typedef enum
diff --git a/src/include/access/storageam.h b/src/include/access/storageam.h
index f7a2fcf99c..7ef52e4f78 100644
--- a/src/include/access/storageam.h
+++ b/src/include/access/storageam.h
@@ -42,29 +42,31 @@ typedef List *(*InsertIndexTuples) (TupleTableSlot *slot, EState *estate, bool n
 /* Function pointer to let the index tuple delete from storage am */
 typedef void (*DeleteIndexTuples) (Relation rel, ItemPointer tid, TransactionId old_xmin);
 
-extern HeapScanDesc storage_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan);
-
-extern void storage_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
-extern HeapScanDesc storage_beginscan(Relation relation, Snapshot snapshot,
-				  int nkeys, ScanKey key);
-extern HeapScanDesc storage_beginscan_catalog(Relation relation, int nkeys, ScanKey key);
-extern HeapScanDesc storage_beginscan_strat(Relation relation, Snapshot snapshot,
-						int nkeys, ScanKey key,
-						bool allow_strat, bool allow_sync);
-extern HeapScanDesc storage_beginscan_bm(Relation relation, Snapshot snapshot,
-					 int nkeys, ScanKey key);
-extern HeapScanDesc storage_beginscan_sampling(Relation relation, Snapshot snapshot,
-						   int nkeys, ScanKey key,
-						   bool allow_strat, bool allow_sync, bool allow_pagemode);
-
-extern void storage_endscan(HeapScanDesc scan);
-extern void storage_rescan(HeapScanDesc scan, ScanKey key);
-extern void storage_rescan_set_params(HeapScanDesc scan, ScanKey key,
+extern StorageScanDesc storage_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan);
+extern ParallelHeapScanDesc storageam_get_parallelheapscandesc(StorageScanDesc sscan);
+extern HeapPageScanDesc storageam_get_heappagescandesc(StorageScanDesc sscan);
+extern void storage_setscanlimits(StorageScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
+extern StorageScanDesc storage_beginscan(Relation relation, Snapshot snapshot,
+										 int nkeys, ScanKey key);
+extern StorageScanDesc storage_beginscan_catalog(Relation relation, int nkeys, ScanKey key);
+extern StorageScanDesc storage_beginscan_strat(Relation relation, Snapshot snapshot,
+											   int nkeys, ScanKey key,
+											   bool allow_strat, bool allow_sync);
+extern StorageScanDesc storage_beginscan_bm(Relation relation, Snapshot snapshot,
+											int nkeys, ScanKey key);
+extern StorageScanDesc storage_beginscan_sampling(Relation relation, Snapshot snapshot,
+												  int nkeys, ScanKey key,
+												  bool allow_strat, bool allow_sync, bool allow_pagemode);
+
+extern void storage_endscan(StorageScanDesc scan);
+extern void storage_rescan(StorageScanDesc scan, ScanKey key);
+extern void storage_rescan_set_params(StorageScanDesc scan, ScanKey key,
 						  bool allow_strat, bool allow_sync, bool allow_pagemode);
-extern void storage_update_snapshot(HeapScanDesc scan, Snapshot snapshot);
+extern void storage_update_snapshot(StorageScanDesc scan, Snapshot snapshot);
 
-extern StorageTuple storage_getnext(HeapScanDesc sscan, ScanDirection direction);
-extern TupleTableSlot *storage_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *slot);
+extern StorageTuple storage_getnext(StorageScanDesc sscan, ScanDirection direction);
+extern TupleTableSlot *storage_getnextslot(StorageScanDesc sscan, ScanDirection direction, TupleTableSlot *slot);
+extern StorageTuple storage_fetch_tuple_from_offset(StorageScanDesc sscan, BlockNumber blkno, OffsetNumber offset);
 
 extern void storage_get_latest_tid(Relation relation,
 					   Snapshot snapshot,
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
index 6538b25135..2cb97c791a 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -83,32 +83,37 @@ typedef StorageTuple(*TupleFromDatum_function) (Datum data, Oid tableoid);
 typedef void (*RelationSync_function) (Relation relation);
 
 
-typedef HeapScanDesc (*ScanBegin_function) (Relation relation,
-											Snapshot snapshot,
-											int nkeys, ScanKey key,
-											ParallelHeapScanDesc parallel_scan,
-											bool allow_strat,
-											bool allow_sync,
-											bool allow_pagemode,
-											bool is_bitmapscan,
-											bool is_samplescan,
-											bool temp_snap);
-typedef void (*ScanSetlimits_function) (HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
+typedef StorageScanDesc(*ScanBegin_function) (Relation relation,
+											  Snapshot snapshot,
+											  int nkeys, ScanKey key,
+											  ParallelHeapScanDesc parallel_scan,
+											  bool allow_strat,
+											  bool allow_sync,
+											  bool allow_pagemode,
+											  bool is_bitmapscan,
+											  bool is_samplescan,
+											  bool temp_snap);
+typedef ParallelHeapScanDesc (*ScanGetParallelheapscandesc_function) (StorageScanDesc scan);
+typedef HeapPageScanDesc(*ScanGetHeappagescandesc_function) (StorageScanDesc scan);
+
+typedef void (*ScanSetlimits_function) (StorageScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
 
 /* must return a TupleTableSlot? */
-typedef StorageTuple(*ScanGetnext_function) (HeapScanDesc scan,
+typedef StorageTuple(*ScanGetnext_function) (StorageScanDesc scan,
 											 ScanDirection direction);
 
-typedef TupleTableSlot *(*ScanGetnextSlot_function) (HeapScanDesc scan,
+typedef TupleTableSlot *(*ScanGetnextSlot_function) (StorageScanDesc scan,
 													 ScanDirection direction, TupleTableSlot *slot);
+typedef StorageTuple(*ScanFetchTupleFromOffset_function) (StorageScanDesc scan,
+														  BlockNumber blkno, OffsetNumber offset);
 
-typedef void (*ScanEnd_function) (HeapScanDesc scan);
+typedef void (*ScanEnd_function) (StorageScanDesc scan);
 
 
-typedef void (*ScanGetpage_function) (HeapScanDesc scan, BlockNumber page);
-typedef void (*ScanRescan_function) (HeapScanDesc scan, ScanKey key, bool set_params,
+typedef void (*ScanGetpage_function) (StorageScanDesc scan, BlockNumber page);
+typedef void (*ScanRescan_function) (StorageScanDesc scan, ScanKey key, bool set_params,
 									 bool allow_strat, bool allow_sync, bool allow_pagemode);
-typedef void (*ScanUpdateSnapshot_function) (HeapScanDesc scan, Snapshot snapshot);
+typedef void (*ScanUpdateSnapshot_function) (StorageScanDesc scan, Snapshot snapshot);
 
 typedef bool (*HotSearchBuffer_function) (ItemPointer tid, Relation relation,
 										  Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
@@ -150,9 +155,12 @@ typedef struct StorageAmRoutine
 
 	/* Operations on relation scans */
 	ScanBegin_function scan_begin;
+	ScanGetParallelheapscandesc_function scan_get_parallelheapscandesc;
+	ScanGetHeappagescandesc_function scan_get_heappagescandesc;
 	ScanSetlimits_function scansetlimits;
 	ScanGetnext_function scan_getnext;
 	ScanGetnextSlot_function scan_getnextslot;
+	ScanFetchTupleFromOffset_function scan_fetch_tuple_from_offset;
 	ScanEnd_function scan_end;
 	ScanGetpage_function scan_getpage;
 	ScanRescan_function scan_rescan;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 55b8a64506..b602b0a538 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1121,7 +1121,7 @@ typedef struct ScanState
 {
 	PlanState	ps;				/* its first field is NodeTag */
 	Relation	ss_currentRelation;
-	HeapScanDesc ss_currentScanDesc;
+	StorageScanDesc ss_currentScanDesc;
 	TupleTableSlot *ss_ScanTupleSlot;
 } ScanState;
 
@@ -1142,6 +1142,7 @@ typedef struct SeqScanState
 typedef struct SampleScanState
 {
 	ScanState	ss;
+	HeapPageScanDesc pagescan;
 	List	   *args;			/* expr states for TABLESAMPLE params */
 	ExprState  *repeatable;		/* expr state for REPEATABLE expr */
 	/* use struct pointer to avoid including tsmapi.h here */
@@ -1370,6 +1371,7 @@ typedef struct ParallelBitmapHeapState
 typedef struct BitmapHeapScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
+	HeapPageScanDesc pagescan;
 	ExprState  *bitmapqualorig;
 	TIDBitmap  *tbm;
 	TBMIterator *tbmiterator;
-- 
2.15.0.windows.1



  [application/octet-stream] 0009-BulkInsertState-is-moved-into-storage.patch (10.8K, ../../CAJrrPGeoihBWs+BUP06q9=rCNm75m56J-EEStf3ygikNcpreYw@mail.gmail.com/12-0009-BulkInsertState-is-moved-into-storage.patch)
  download | inline diff:
From 8baa289995861beb23c930f782377b8aaeaf1906 Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Wed, 3 Jan 2018 17:33:30 +1100
Subject: [PATCH 09/12] BulkInsertState is moved into storage

Added Get, free and release bulkinsertstate functions
to operate with bulkinsertstate that is provided by
the storage.
---
 src/backend/access/heap/heapam.c         |  2 +-
 src/backend/access/heap/heapam_storage.c |  4 ++++
 src/backend/access/storage/storageam.c   | 23 +++++++++++++++++++++++
 src/backend/commands/copy.c              |  8 ++++----
 src/backend/commands/createas.c          |  6 +++---
 src/backend/commands/matview.c           |  6 +++---
 src/backend/commands/tablecmds.c         |  6 +++---
 src/include/access/heapam.h              |  4 +---
 src/include/access/storage_common.h      |  3 +++
 src/include/access/storageam.h           |  4 ++++
 src/include/access/storageamapi.h        |  7 +++++++
 11 files changed, 56 insertions(+), 17 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index e1bc52a43e..fe083b6bb5 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2315,7 +2315,7 @@ GetBulkInsertState(void)
 	bistate = (BulkInsertState) palloc(sizeof(BulkInsertStateData));
 	bistate->strategy = GetAccessStrategy(BAS_BULKWRITE);
 	bistate->current_buf = InvalidBuffer;
-	return bistate;
+	return (void *)bistate;
 }
 
 /*
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index a1c2407a3e..e914088be0 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -332,5 +332,9 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 	amroutine->tuple_get_latest_tid = heap_get_latest_tid;
 	amroutine->relation_sync = heap_sync;
 
+	amroutine->getbulkinsertstate = GetBulkInsertState;
+	amroutine->freebulkinsertstate = FreeBulkInsertState;
+	amroutine->releasebulkinsertstate = ReleaseBulkInsertStatePin;
+
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/storage/storageam.c b/src/backend/access/storage/storageam.c
index 8bc09c39ab..9554e9a902 100644
--- a/src/backend/access/storage/storageam.c
+++ b/src/backend/access/storage/storageam.c
@@ -378,3 +378,26 @@ storage_sync(Relation rel)
 {
 	rel->rd_stamroutine->relation_sync(rel);
 }
+
+/*
+ * -------------------
+ * storage Bulk Insert functions
+ * -------------------
+ */
+BulkInsertState
+storage_getbulkinsertstate(Relation rel)
+{
+	return rel->rd_stamroutine->getbulkinsertstate();
+}
+
+void
+storage_freebulkinsertstate(Relation rel, BulkInsertState bistate)
+{
+	rel->rd_stamroutine->freebulkinsertstate(bistate);
+}
+
+void
+storage_releasebulkinsertstate(Relation rel, BulkInsertState bistate)
+{
+	rel->rd_stamroutine->releasebulkinsertstate(bistate);
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 41ea9a017b..1e1ed59010 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2297,7 +2297,7 @@ CopyFrom(CopyState cstate)
 	ErrorContextCallback errcallback;
 	CommandId	mycid = GetCurrentCommandId(true);
 	int			hi_options = 0; /* start with default heap_insert options */
-	BulkInsertState bistate;
+	void       *bistate;
 	uint64		processed = 0;
 	bool		useHeapMultiInsert;
 	int			nBufferedTuples = 0;
@@ -2553,7 +2553,7 @@ CopyFrom(CopyState cstate)
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	bistate = GetBulkInsertState();
+	bistate = storage_getbulkinsertstate(resultRelInfo->ri_RelationDesc);
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2633,7 +2633,7 @@ CopyFrom(CopyState cstate)
 			 */
 			if (prev_leaf_part_index != leaf_part_index)
 			{
-				ReleaseBulkInsertStatePin(bistate);
+				storage_releasebulkinsertstate(resultRelInfo->ri_RelationDesc, bistate);
 				prev_leaf_part_index = leaf_part_index;
 			}
 
@@ -2818,7 +2818,7 @@ CopyFrom(CopyState cstate)
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	FreeBulkInsertState(bistate);
+	storage_freebulkinsertstate(resultRelInfo->ri_RelationDesc, bistate);
 
 	MemoryContextSwitchTo(oldcontext);
 
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index f39551ac83..f3fa304bbc 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -60,7 +60,7 @@ typedef struct
 	ObjectAddress reladdr;		/* address of rel, for ExecCreateTableAs */
 	CommandId	output_cid;		/* cmin to insert in output tuples */
 	int			hi_options;		/* heap_insert performance options */
-	BulkInsertState bistate;	/* bulk insert state */
+	void       *bistate;		/* bulk insert state */
 } DR_intorel;
 
 /* utility functions for CTAS definition creation */
@@ -570,7 +570,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	 */
 	myState->hi_options = HEAP_INSERT_SKIP_FSM |
 		(XLogIsNeeded() ? 0 : HEAP_INSERT_SKIP_WAL);
-	myState->bistate = GetBulkInsertState();
+	myState->bistate = storage_getbulkinsertstate(intoRelationDesc);
 
 	/* Not using WAL requires smgr_targblock be initially invalid */
 	Assert(RelationGetTargetBlock(intoRelationDesc) == InvalidBlockNumber);
@@ -619,7 +619,7 @@ intorel_shutdown(DestReceiver *self)
 {
 	DR_intorel *myState = (DR_intorel *) self;
 
-	FreeBulkInsertState(myState->bistate);
+	storage_freebulkinsertstate(myState->rel, myState->bistate);
 
 	/* If we skipped using WAL, must heap_sync before commit */
 	if (myState->hi_options & HEAP_INSERT_SKIP_WAL)
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index b681f4ef5d..65ec255ff1 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -52,7 +52,7 @@ typedef struct
 	Relation	transientrel;	/* relation to write to */
 	CommandId	output_cid;		/* cmin to insert in output tuples */
 	int			hi_options;		/* heap_insert performance options */
-	BulkInsertState bistate;	/* bulk insert state */
+	void       *bistate;		/* bulk insert state */
 } DR_transientrel;
 
 static int	matview_maintenance_depth = 0;
@@ -479,7 +479,7 @@ transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	myState->hi_options = HEAP_INSERT_SKIP_FSM | HEAP_INSERT_FROZEN;
 	if (!XLogIsNeeded())
 		myState->hi_options |= HEAP_INSERT_SKIP_WAL;
-	myState->bistate = GetBulkInsertState();
+	myState->bistate = storage_getbulkinsertstate(transientrel);
 
 	/* Not using WAL requires smgr_targblock be initially invalid */
 	Assert(RelationGetTargetBlock(transientrel) == InvalidBlockNumber);
@@ -522,7 +522,7 @@ transientrel_shutdown(DestReceiver *self)
 {
 	DR_transientrel *myState = (DR_transientrel *) self;
 
-	FreeBulkInsertState(myState->bistate);
+	storage_freebulkinsertstate(myState->transientrel, myState->bistate);
 
 	/* If we skipped using WAL, must heap_sync before commit */
 	if (myState->hi_options & HEAP_INSERT_SKIP_WAL)
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dd126e8e44..03a9e45823 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4384,7 +4384,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	ListCell   *l;
 	EState	   *estate;
 	CommandId	mycid;
-	BulkInsertState bistate;
+	void       *bistate;
 	int			hi_options;
 	ExprState  *partqualstate = NULL;
 
@@ -4410,7 +4410,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	if (newrel)
 	{
 		mycid = GetCurrentCommandId(true);
-		bistate = GetBulkInsertState();
+		bistate = storage_getbulkinsertstate(newrel);
 
 		hi_options = HEAP_INSERT_SKIP_FSM;
 		if (!XLogIsNeeded())
@@ -4685,7 +4685,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	heap_close(oldrel, NoLock);
 	if (newrel)
 	{
-		FreeBulkInsertState(bistate);
+		storage_freebulkinsertstate(newrel, bistate);
 
 		/* If we skipped writing WAL, then we need to sync the heap. */
 		if (hi_options & HEAP_INSERT_SKIP_WAL)
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index cf13c401ca..abc36fb554 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -31,8 +31,6 @@
 #define HEAP_INSERT_FROZEN		0x0004
 #define HEAP_INSERT_SPECULATIVE 0x0008
 
-typedef struct BulkInsertStateData *BulkInsertState;
-
 /*
  * Possible lock modes for a tuple.
  */
@@ -148,7 +146,7 @@ extern void heap_get_latest_tid(Relation relation, Snapshot snapshot,
 extern void setLastTid(const ItemPointer tid);
 
 extern BulkInsertState GetBulkInsertState(void);
-extern void FreeBulkInsertState(BulkInsertState);
+extern void FreeBulkInsertState(BulkInsertState bistate);
 extern void ReleaseBulkInsertStatePin(BulkInsertState bistate);
 
 extern Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid,
diff --git a/src/include/access/storage_common.h b/src/include/access/storage_common.h
index baba7261d4..0487758665 100644
--- a/src/include/access/storage_common.h
+++ b/src/include/access/storage_common.h
@@ -38,6 +38,9 @@ typedef enum
 	HEAPTUPLE_DELETE_IN_PROGRESS	/* deleting xact is still in progress */
 } HTSV_Result;
 
+typedef struct BulkInsertStateData *BulkInsertState;
+
+
 /*
  * slot storage routine functions
  */
diff --git a/src/include/access/storageam.h b/src/include/access/storageam.h
index 7ef52e4f78..a0d588c7b0 100644
--- a/src/include/access/storageam.h
+++ b/src/include/access/storageam.h
@@ -118,4 +118,8 @@ extern void storage_get_latest_tid(Relation relation,
 
 extern void storage_sync(Relation rel);
 
+extern BulkInsertState storage_getbulkinsertstate(Relation rel);
+extern void storage_freebulkinsertstate(Relation rel, BulkInsertState bistate);
+extern void storage_releasebulkinsertstate(Relation rel, BulkInsertState bistate);
+
 #endif
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
index 2cb97c791a..d5eb893341 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -82,6 +82,9 @@ typedef StorageTuple(*TupleFromDatum_function) (Datum data, Oid tableoid);
 
 typedef void (*RelationSync_function) (Relation relation);
 
+typedef BulkInsertState (*GetBulkInsertState_function) (void);
+typedef void (*FreeBulkInsertState_function) (BulkInsertState bistate);
+typedef void (*ReleaseBulkInsertState_function) (BulkInsertState bistate);
 
 typedef StorageScanDesc(*ScanBegin_function) (Relation relation,
 											  Snapshot snapshot,
@@ -153,6 +156,10 @@ typedef struct StorageAmRoutine
 
 	RelationSync_function relation_sync;	/* heap_sync */
 
+	GetBulkInsertState_function getbulkinsertstate;
+	FreeBulkInsertState_function freebulkinsertstate;
+	ReleaseBulkInsertState_function releasebulkinsertstate;
+
 	/* Operations on relation scans */
 	ScanBegin_function scan_begin;
 	ScanGetParallelheapscandesc_function scan_get_parallelheapscandesc;
-- 
2.15.0.windows.1



  [application/octet-stream] 0010-storage-rewrite-functionality.patch (11.1K, ../../CAJrrPGeoihBWs+BUP06q9=rCNm75m56J-EEStf3ygikNcpreYw@mail.gmail.com/13-0010-storage-rewrite-functionality.patch)
  download | inline diff:
From 940342d009a753251d2c94ed5e594062b5cd925e Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Wed, 3 Jan 2018 17:34:59 +1100
Subject: [PATCH 10/12] storage rewrite functionality

All the heap rewrite functionality is moved into heap storage
and exposed them with storage API. Currenlty these API's are used
only by the cluster command operation.

The logical rewrite mapping code is currently left as it is,
this needs to be handled seperately.
---
 src/backend/access/heap/heapam_storage.c |  6 ++++++
 src/backend/access/storage/storageam.c   | 33 ++++++++++++++++++++++++++++++++
 src/backend/commands/cluster.c           | 32 +++++++++++++++----------------
 src/include/access/heapam.h              |  9 +++++++++
 src/include/access/rewriteheap.h         | 11 -----------
 src/include/access/storage_common.h      |  2 ++
 src/include/access/storageam.h           |  8 ++++++++
 src/include/access/storageamapi.h        | 13 +++++++++++++
 8 files changed, 86 insertions(+), 28 deletions(-)

diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index e914088be0..8621015846 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -22,6 +22,7 @@
 
 #include "access/heapam.h"
 #include "access/relscan.h"
+#include "access/rewriteheap.h"
 #include "access/storageamapi.h"
 #include "pgstat.h"
 #include "utils/builtins.h"
@@ -336,5 +337,10 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 	amroutine->freebulkinsertstate = FreeBulkInsertState;
 	amroutine->releasebulkinsertstate = ReleaseBulkInsertStatePin;
 
+	amroutine->begin_heap_rewrite = begin_heap_rewrite;
+	amroutine->end_heap_rewrite = end_heap_rewrite;
+	amroutine->rewrite_heap_tuple = rewrite_heap_tuple;
+	amroutine->rewrite_heap_dead_tuple = rewrite_heap_dead_tuple;
+
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/storage/storageam.c b/src/backend/access/storage/storageam.c
index 9554e9a902..239339c033 100644
--- a/src/backend/access/storage/storageam.c
+++ b/src/backend/access/storage/storageam.c
@@ -401,3 +401,36 @@ storage_releasebulkinsertstate(Relation rel, BulkInsertState bistate)
 {
 	rel->rd_stamroutine->releasebulkinsertstate(bistate);
 }
+
+/*
+ * -------------------
+ * storage tuple rewrite functions
+ * -------------------
+ */
+RewriteState
+storage_begin_rewrite(Relation OldHeap, Relation NewHeap,
+				   TransactionId OldestXmin, TransactionId FreezeXid,
+				   MultiXactId MultiXactCutoff, bool use_wal)
+{
+	return NewHeap->rd_stamroutine->begin_heap_rewrite(OldHeap, NewHeap,
+			OldestXmin, FreezeXid, MultiXactCutoff, use_wal);
+}
+
+void
+storage_end_rewrite(Relation rel, RewriteState state)
+{
+	rel->rd_stamroutine->end_heap_rewrite(state);
+}
+
+void
+storage_rewrite_tuple(Relation rel, RewriteState state, HeapTuple oldTuple,
+				   HeapTuple newTuple)
+{
+	rel->rd_stamroutine->rewrite_heap_tuple(state, oldTuple, newTuple);
+}
+
+bool
+storage_rewrite_dead_tuple(Relation rel, RewriteState state, HeapTuple oldTuple)
+{
+	return rel->rd_stamroutine->rewrite_heap_dead_tuple(state, oldTuple);
+}
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index d6b01da629..9b286e932d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -75,9 +75,8 @@ static void copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 			   TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
 static void reform_and_rewrite_tuple(HeapTuple tuple,
-						 TupleDesc oldTupDesc, TupleDesc newTupDesc,
-						 Datum *values, bool *isnull,
-						 bool newRelHasOids, RewriteState rwstate);
+						 Relation OldHeap, Relation NewHeap,
+						 Datum *values, bool *isnull, RewriteState rwstate);
 
 
 /*---------------------------------------------------------------------------
@@ -879,7 +878,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	is_system_catalog = IsSystemRelation(OldHeap);
 
 	/* Initialize the rewrite operation */
-	rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin, FreezeXid,
+	rwstate = storage_begin_rewrite(OldHeap, NewHeap, OldestXmin, FreezeXid,
 								 MultiXactCutoff, use_wal);
 
 	/*
@@ -1028,7 +1027,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 		{
 			tups_vacuumed += 1;
 			/* heap rewrite module still needs to see it... */
-			if (rewrite_heap_dead_tuple(rwstate, tuple))
+			if (storage_rewrite_dead_tuple(NewHeap, rwstate, tuple))
 			{
 				/* A previous recently-dead tuple is now known dead */
 				tups_vacuumed += 1;
@@ -1042,9 +1041,8 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 			tuplesort_putheaptuple(tuplesort, tuple);
 		else
 			reform_and_rewrite_tuple(tuple,
-									 oldTupDesc, newTupDesc,
-									 values, isnull,
-									 NewHeap->rd_rel->relhasoids, rwstate);
+									 OldHeap, NewHeap,
+									 values, isnull, rwstate);
 	}
 
 	if (indexScan != NULL)
@@ -1071,16 +1069,15 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 				break;
 
 			reform_and_rewrite_tuple(tuple,
-									 oldTupDesc, newTupDesc,
-									 values, isnull,
-									 NewHeap->rd_rel->relhasoids, rwstate);
+									 OldHeap, NewHeap,
+									 values, isnull, rwstate);
 		}
 
 		tuplesort_end(tuplesort);
 	}
 
 	/* Write out any remaining tuples, and fsync if needed */
-	end_heap_rewrite(rwstate);
+	storage_end_rewrite(NewHeap, rwstate);
 
 	/* Reset rd_toastoid just to be tidy --- it shouldn't be looked at again */
 	NewHeap->rd_toastoid = InvalidOid;
@@ -1734,10 +1731,11 @@ get_tables_to_cluster(MemoryContext cluster_context)
  */
 static void
 reform_and_rewrite_tuple(HeapTuple tuple,
-						 TupleDesc oldTupDesc, TupleDesc newTupDesc,
-						 Datum *values, bool *isnull,
-						 bool newRelHasOids, RewriteState rwstate)
+						 Relation OldHeap, Relation NewHeap,
+						 Datum *values, bool *isnull, RewriteState rwstate)
 {
+	TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
+	TupleDesc newTupDesc = RelationGetDescr(NewHeap);
 	HeapTuple	copiedTuple;
 	int			i;
 
@@ -1753,11 +1751,11 @@ reform_and_rewrite_tuple(HeapTuple tuple,
 	copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
 
 	/* Preserve OID, if any */
-	if (newRelHasOids)
+	if (NewHeap->rd_rel->relhasoids)
 		HeapTupleSetOid(copiedTuple, HeapTupleGetOid(tuple));
 
 	/* The heap rewrite module does the rest */
-	rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+	storage_rewrite_tuple(NewHeap, rwstate, tuple, copiedTuple);
 
 	heap_freetuple(copiedTuple);
 }
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index abc36fb554..58a955be4f 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -216,4 +216,13 @@ extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
 extern bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
 extern bool HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin);
 
+/* in heap/rewriteheap.c */
+extern RewriteState begin_heap_rewrite(Relation OldHeap, Relation NewHeap,
+				   TransactionId OldestXmin, TransactionId FreezeXid,
+				   MultiXactId MultiXactCutoff, bool use_wal);
+extern void end_heap_rewrite(RewriteState state);
+extern void rewrite_heap_tuple(RewriteState state, HeapTuple oldTuple,
+				   HeapTuple newTuple);
+extern bool rewrite_heap_dead_tuple(RewriteState state, HeapTuple oldTuple);
+
 #endif							/* HEAPAM_H */
diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h
index 6d7f669cbc..c610914133 100644
--- a/src/include/access/rewriteheap.h
+++ b/src/include/access/rewriteheap.h
@@ -18,17 +18,6 @@
 #include "storage/relfilenode.h"
 #include "utils/relcache.h"
 
-/* struct definition is private to rewriteheap.c */
-typedef struct RewriteStateData *RewriteState;
-
-extern RewriteState begin_heap_rewrite(Relation OldHeap, Relation NewHeap,
-				   TransactionId OldestXmin, TransactionId FreezeXid,
-				   MultiXactId MultiXactCutoff, bool use_wal);
-extern void end_heap_rewrite(RewriteState state);
-extern void rewrite_heap_tuple(RewriteState state, HeapTuple oldTuple,
-				   HeapTuple newTuple);
-extern bool rewrite_heap_dead_tuple(RewriteState state, HeapTuple oldTuple);
-
 /*
  * On-Disk data format for an individual logical rewrite mapping.
  */
diff --git a/src/include/access/storage_common.h b/src/include/access/storage_common.h
index 0487758665..04280a7380 100644
--- a/src/include/access/storage_common.h
+++ b/src/include/access/storage_common.h
@@ -40,6 +40,8 @@ typedef enum
 
 typedef struct BulkInsertStateData *BulkInsertState;
 
+/* struct definition is private to rewriteheap.c */
+typedef struct RewriteStateData *RewriteState;
 
 /*
  * slot storage routine functions
diff --git a/src/include/access/storageam.h b/src/include/access/storageam.h
index a0d588c7b0..c7cd01cd10 100644
--- a/src/include/access/storageam.h
+++ b/src/include/access/storageam.h
@@ -122,4 +122,12 @@ extern BulkInsertState storage_getbulkinsertstate(Relation rel);
 extern void storage_freebulkinsertstate(Relation rel, BulkInsertState bistate);
 extern void storage_releasebulkinsertstate(Relation rel, BulkInsertState bistate);
 
+extern RewriteState storage_begin_rewrite(Relation OldHeap, Relation NewHeap,
+				   TransactionId OldestXmin, TransactionId FreezeXid,
+				   MultiXactId MultiXactCutoff, bool use_wal);
+extern void storage_end_rewrite(Relation rel, RewriteState state);
+extern void storage_rewrite_tuple(Relation rel, RewriteState state, HeapTuple oldTuple,
+				   HeapTuple newTuple);
+extern bool storage_rewrite_dead_tuple(Relation rel, RewriteState state, HeapTuple oldTuple);
+
 #endif
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
index d5eb893341..b7a0d5fb07 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -86,6 +86,14 @@ typedef BulkInsertState (*GetBulkInsertState_function) (void);
 typedef void (*FreeBulkInsertState_function) (BulkInsertState bistate);
 typedef void (*ReleaseBulkInsertState_function) (BulkInsertState bistate);
 
+typedef RewriteState (*BeginHeapRewrite_function) (Relation OldHeap, Relation NewHeap,
+				   TransactionId OldestXmin, TransactionId FreezeXid,
+				   MultiXactId MultiXactCutoff, bool use_wal);
+typedef void (*EndHeapRewrite_function) (RewriteState state);
+typedef void (*RewriteHeapTuple_function) (RewriteState state, HeapTuple oldTuple,
+				   HeapTuple newTuple);
+typedef bool (*RewriteHeapDeadTuple_function) (RewriteState state, HeapTuple oldTuple);
+
 typedef StorageScanDesc(*ScanBegin_function) (Relation relation,
 											  Snapshot snapshot,
 											  int nkeys, ScanKey key,
@@ -160,6 +168,11 @@ typedef struct StorageAmRoutine
 	FreeBulkInsertState_function freebulkinsertstate;
 	ReleaseBulkInsertState_function releasebulkinsertstate;
 
+	BeginHeapRewrite_function begin_heap_rewrite;
+	EndHeapRewrite_function end_heap_rewrite;
+	RewriteHeapTuple_function rewrite_heap_tuple;
+	RewriteHeapDeadTuple_function rewrite_heap_dead_tuple;
+
 	/* Operations on relation scans */
 	ScanBegin_function scan_begin;
 	ScanGetParallelheapscandesc_function scan_get_parallelheapscandesc;
-- 
2.15.0.windows.1



  [application/octet-stream] 0011-Improve-tuple-locking-interface.patch (58.5K, ../../CAJrrPGeoihBWs+BUP06q9=rCNm75m56J-EEStf3ygikNcpreYw@mail.gmail.com/14-0011-Improve-tuple-locking-interface.patch)
  download | inline diff:
From ef7898c6512c91352c68eaca7b10e7811449861c Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Wed, 3 Jan 2018 17:36:11 +1100
Subject: [PATCH 11/12] 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 storage 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 storage access methods.  Similar changes are
upcoming to tuple_update() and tuple_delete() interface methods.
---
 src/backend/access/heap/heapam.c            | 125 +++++++------
 src/backend/access/heap/heapam_storage.c    | 217 +++++++++++++++++++++-
 src/backend/access/heap/heapam_visibility.c |  23 ++-
 src/backend/access/storage/storageam.c      |  11 +-
 src/backend/commands/trigger.c              |  67 +++----
 src/backend/executor/execMain.c             | 278 +---------------------------
 src/backend/executor/execReplication.c      |  36 ++--
 src/backend/executor/nodeLockRows.c         |  67 +++----
 src/backend/executor/nodeModifyTable.c      | 152 ++++++++++-----
 src/include/access/heapam.h                 |   9 +-
 src/include/access/storageam.h              |   8 +-
 src/include/access/storageamapi.h           |   6 +-
 src/include/executor/executor.h             |   6 +-
 src/include/nodes/lockoptions.h             |   5 +
 src/include/utils/snapshot.h                |   1 +
 15 files changed, 516 insertions(+), 495 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index fe083b6bb5..0df5ff8b20 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3191,6 +3191,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;
@@ -3204,6 +3205,8 @@ l1:
 			UnlockTupleTuplock(relation, &(tp.t_self), LockTupleExclusive);
 		if (vmbuffer != InvalidBuffer)
 			ReleaseBuffer(vmbuffer);
+		if (result == HeapTupleUpdated && ItemPointerEquals(tid, &hufd->ctid))
+			result = HeapTupleDeleted;
 		return result;
 	}
 
@@ -3413,6 +3416,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;
@@ -3832,6 +3839,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;
@@ -3850,6 +3858,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;
 	}
 
@@ -4468,6 +4478,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;
@@ -4520,6 +4534,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
@@ -4532,12 +4547,13 @@ get_mxact_status_for_lock(LockTupleMode mode, bool is_update)
  * See README.tuplock for a thorough explanation of this mechanism.
  */
 HTSU_Result
-heap_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * stuple,
+heap_lock_tuple(Relation relation, HeapTuple tuple,
 				CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 				bool follow_updates,
 				Buffer *buffer, HeapUpdateFailureData *hufd)
 {
 	HTSU_Result result;
+	ItemPointer tid = &(tuple->t_self);
 	ItemId		lp;
 	Page		page;
 	Buffer		vmbuffer = InvalidBuffer;
@@ -4550,9 +4566,6 @@ heap_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * stuple,
 	bool		first_time = true;
 	bool		have_tuple_lock = false;
 	bool		cleared_all_frozen = false;
-	HeapTupleData tuple;
-
-	Assert(stuple != NULL);
 
 	*buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
 	block = ItemPointerGetBlockNumber(tid);
@@ -4572,13 +4585,12 @@ heap_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * stuple,
 	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid));
 	Assert(ItemIdIsNormal(lp));
 
-	tuple.t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	tuple.t_len = ItemIdGetLength(lp);
-	tuple.t_tableOid = RelationGetRelid(relation);
-	ItemPointerCopy(tid, &tuple.t_self);
+	tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
+	tuple->t_len = ItemIdGetLength(lp);
+	tuple->t_tableOid = RelationGetRelid(relation);
 
 l3:
-	result = HeapTupleSatisfiesUpdate(&tuple, cid, *buffer);
+	result = HeapTupleSatisfiesUpdate(tuple, cid, *buffer);
 
 	if (result == HeapTupleInvisible)
 	{
@@ -4591,7 +4603,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;
@@ -4600,10 +4612,10 @@ l3:
 		ItemPointerData t_ctid;
 
 		/* must copy state data before unlocking buffer */
-		xwait = HeapTupleHeaderGetRawXmax(tuple.t_data);
-		infomask = tuple.t_data->t_infomask;
-		infomask2 = tuple.t_data->t_infomask2;
-		ItemPointerCopy(&tuple.t_data->t_ctid, &t_ctid);
+		xwait = HeapTupleHeaderGetRawXmax(tuple->t_data);
+		infomask = tuple->t_data->t_infomask;
+		infomask2 = tuple->t_data->t_infomask2;
+		ItemPointerCopy(&tuple->t_data->t_ctid, &t_ctid);
 
 		LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
 
@@ -4735,7 +4747,7 @@ l3:
 				{
 					HTSU_Result res;
 
-					res = heap_lock_updated_tuple(relation, &tuple, &t_ctid,
+					res = heap_lock_updated_tuple(relation, tuple, &t_ctid,
 												  GetCurrentTransactionId(),
 												  mode);
 					if (res != HeapTupleMayBeUpdated)
@@ -4756,8 +4768,8 @@ l3:
 				 * now need to follow the update chain to lock the new
 				 * versions.
 				 */
-				if (!HeapTupleHeaderIsOnlyLocked(tuple.t_data) &&
-					((tuple.t_data->t_infomask2 & HEAP_KEYS_UPDATED) ||
+				if (!HeapTupleHeaderIsOnlyLocked(tuple->t_data) &&
+					((tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED) ||
 					 !updated))
 					goto l3;
 
@@ -4788,8 +4800,8 @@ l3:
 				 * Make sure it's still an appropriate lock, else start over.
 				 * See above about allowing xmax to change.
 				 */
-				if (!HEAP_XMAX_IS_LOCKED_ONLY(tuple.t_data->t_infomask) ||
-					HEAP_XMAX_IS_EXCL_LOCKED(tuple.t_data->t_infomask))
+				if (!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask) ||
+					HEAP_XMAX_IS_EXCL_LOCKED(tuple->t_data->t_infomask))
 					goto l3;
 				require_sleep = false;
 			}
@@ -4811,8 +4823,8 @@ l3:
 					 * meantime, start over.
 					 */
 					LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-					if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
-						!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple.t_data),
+					if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
+						!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple->t_data),
 											 xwait))
 						goto l3;
 
@@ -4825,9 +4837,9 @@ l3:
 				LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
 
 				/* if the xmax changed in the meantime, start over */
-				if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
+				if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
 					!TransactionIdEquals(
-										 HeapTupleHeaderGetRawXmax(tuple.t_data),
+										 HeapTupleHeaderGetRawXmax(tuple->t_data),
 										 xwait))
 					goto l3;
 				/* otherwise, we're good */
@@ -4852,11 +4864,11 @@ l3:
 		{
 			/* ... but if the xmax changed in the meantime, start over */
 			LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-			if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
-				!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple.t_data),
+			if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
+				!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple->t_data),
 									 xwait))
 				goto l3;
-			Assert(HEAP_XMAX_IS_LOCKED_ONLY(tuple.t_data->t_infomask));
+			Assert(HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask));
 			require_sleep = false;
 		}
 
@@ -4871,7 +4883,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;
@@ -4913,7 +4925,7 @@ l3:
 				{
 					case LockWaitBlock:
 						MultiXactIdWait((MultiXactId) xwait, status, infomask,
-										relation, &tuple.t_self, XLTW_Lock, NULL);
+										relation, &tuple->t_self, XLTW_Lock, NULL);
 						break;
 					case LockWaitSkip:
 						if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
@@ -4954,7 +4966,7 @@ l3:
 				switch (wait_policy)
 				{
 					case LockWaitBlock:
-						XactLockTableWait(xwait, relation, &tuple.t_self,
+						XactLockTableWait(xwait, relation, &tuple->t_self,
 										  XLTW_Lock);
 						break;
 					case LockWaitSkip:
@@ -4981,7 +4993,7 @@ l3:
 			{
 				HTSU_Result res;
 
-				res = heap_lock_updated_tuple(relation, &tuple, &t_ctid,
+				res = heap_lock_updated_tuple(relation, tuple, &t_ctid,
 											  GetCurrentTransactionId(),
 											  mode);
 				if (res != HeapTupleMayBeUpdated)
@@ -5000,8 +5012,8 @@ l3:
 			 * other xact could update this tuple before we get to this point.
 			 * Check for xmax change, and start over if so.
 			 */
-			if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
-				!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple.t_data),
+			if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
+				!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple->t_data),
 									 xwait))
 				goto l3;
 
@@ -5015,7 +5027,7 @@ l3:
 				 * don't check for this in the multixact case, because some
 				 * locker transactions might still be running.
 				 */
-				UpdateXmaxHintBits(tuple.t_data, *buffer, xwait);
+				UpdateXmaxHintBits(tuple->t_data, *buffer, xwait);
 			}
 		}
 
@@ -5027,10 +5039,12 @@ l3:
 		 * at all for whatever reason.
 		 */
 		if (!require_sleep ||
-			(tuple.t_data->t_infomask & HEAP_XMAX_INVALID) ||
-			HEAP_XMAX_IS_LOCKED_ONLY(tuple.t_data->t_infomask) ||
-			HeapTupleHeaderIsOnlyLocked(tuple.t_data))
+			(tuple->t_data->t_infomask & HEAP_XMAX_INVALID) ||
+			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;
 	}
@@ -5039,12 +5053,12 @@ failed:
 	if (result != HeapTupleMayBeUpdated)
 	{
 		Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated ||
-			   result == HeapTupleWouldBlock);
-		Assert(!(tuple.t_data->t_infomask & HEAP_XMAX_INVALID));
-		hufd->ctid = tuple.t_data->t_ctid;
-		hufd->xmax = HeapTupleHeaderGetUpdateXid(tuple.t_data);
+			   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);
 		if (result == HeapTupleSelfUpdated)
-			hufd->cmax = HeapTupleHeaderGetCmax(tuple.t_data);
+			hufd->cmax = HeapTupleHeaderGetCmax(tuple->t_data);
 		else
 			hufd->cmax = InvalidCommandId;
 		goto out_locked;
@@ -5067,8 +5081,8 @@ failed:
 		goto l3;
 	}
 
-	xmax = HeapTupleHeaderGetRawXmax(tuple.t_data);
-	old_infomask = tuple.t_data->t_infomask;
+	xmax = HeapTupleHeaderGetRawXmax(tuple->t_data);
+	old_infomask = tuple->t_data->t_infomask;
 
 	/*
 	 * If this is the first possibly-multixact-able operation in the current
@@ -5085,7 +5099,7 @@ failed:
 	 * not modify the tuple just yet, because that would leave it in the wrong
 	 * state if multixact.c elogs.
 	 */
-	compute_new_xmax_infomask(xmax, old_infomask, tuple.t_data->t_infomask2,
+	compute_new_xmax_infomask(xmax, old_infomask, tuple->t_data->t_infomask2,
 							  GetCurrentTransactionId(), mode, false,
 							  &xid, &new_infomask, &new_infomask2);
 
@@ -5101,13 +5115,13 @@ failed:
 	 * Also reset the HOT UPDATE bit, but only if there's no update; otherwise
 	 * we would break the HOT chain.
 	 */
-	tuple.t_data->t_infomask &= ~HEAP_XMAX_BITS;
-	tuple.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
-	tuple.t_data->t_infomask |= new_infomask;
-	tuple.t_data->t_infomask2 |= new_infomask2;
+	tuple->t_data->t_infomask &= ~HEAP_XMAX_BITS;
+	tuple->t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+	tuple->t_data->t_infomask |= new_infomask;
+	tuple->t_data->t_infomask2 |= new_infomask2;
 	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
-		HeapTupleHeaderClearHotUpdated(tuple.t_data);
-	HeapTupleHeaderSetXmax(tuple.t_data, xid);
+		HeapTupleHeaderClearHotUpdated(tuple->t_data);
+	HeapTupleHeaderSetXmax(tuple->t_data, xid);
 
 	/*
 	 * Make sure there is no forward chain link in t_ctid.  Note that in the
@@ -5117,7 +5131,7 @@ failed:
 	 * the tuple as well.
 	 */
 	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
-		tuple.t_data->t_ctid = *tid;
+		tuple->t_data->t_ctid = *tid;
 
 	/* Clear only the all-frozen bit on visibility map if needed */
 	if (PageIsAllVisible(page) &&
@@ -5148,10 +5162,10 @@ failed:
 		XLogBeginInsert();
 		XLogRegisterBuffer(0, *buffer, REGBUF_STANDARD);
 
-		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple.t_self);
+		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self);
 		xlrec.locking_xid = xid;
 		xlrec.infobits_set = compute_infobits(new_infomask,
-											  tuple.t_data->t_infomask2);
+											  tuple->t_data->t_infomask2);
 		xlrec.flags = cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
 		XLogRegisterData((char *) &xlrec, SizeOfHeapLock);
 
@@ -5185,7 +5199,6 @@ out_unlocked:
 	if (have_tuple_lock)
 		UnlockTupleTuplock(relation, tid, mode);
 
-	*stuple = heap_copytuple(&tuple);
 	return result;
 }
 
@@ -5919,6 +5932,10 @@ next:
 	result = HeapTupleMayBeUpdated;
 
 out_locked:
+
+	if (result == HeapTupleUpdated && ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid))
+		result = HeapTupleDeleted;
+
 	UnlockReleaseBuffer(buf);
 
 	if (vmbuffer != InvalidBuffer)
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index 8621015846..f8846519d6 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -25,8 +25,10 @@
 #include "access/rewriteheap.h"
 #include "access/storageamapi.h"
 #include "pgstat.h"
+#include "storage/lmgr.h"
 #include "utils/builtins.h"
 #include "utils/rel.h"
+#include "utils/tqual.h"
 
 
 /* ----------------------------------------------------------------
@@ -286,6 +288,219 @@ heapam_fetch_tuple_from_offset(StorageScanDesc sscan, BlockNumber blkno, OffsetN
 	return &(scan->rs_ctup);
 }
 
+/*
+ * Locks tuple and fetches its newest version and TID.
+ *
+ *	relation - table containing tuple
+ *	*tid - TID of tuple to lock (rest of struct need not be valid)
+ *	snapshot - snapshot indentifying required version (used for assert check only)
+ *	*stuple - tuple to be returned
+ *	cid - current command ID (used for visibility test, and stored into
+ *		  tuple's cmax if lock is successful)
+ *	mode - indicates if shared or exclusive tuple lock is desired
+ *	wait_policy - what to do if tuple lock is not available
+ *	flags – indicating how do we handle updated tuples
+ *	*hufd - filled in failure cases
+ *
+ * Function result may be:
+ *	HeapTupleMayBeUpdated: lock was successfully acquired
+ *	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
+ * *hufd with the tuple's t_ctid, t_xmax (resolving a possible MultiXact,
+ * if necessary), and t_cmax (the last only for HeapTupleSelfUpdated,
+ * since we cannot obtain cmax from a combocid generated by another
+ * transaction).
+ * See comments for struct HeapUpdateFailureData for additional info.
+ */
+static HTSU_Result
+heapam_lock_tuple(Relation relation, ItemPointer tid, Snapshot snapshot,
+				StorageTuple *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,
+		(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 (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((StorageTuple) &tuple, snapshot, InvalidBuffer));
+
+	*stuple = heap_copytuple(&tuple);
+	ReleaseBuffer(buffer);
+
+	return result;
+}
+
 
 Datum
 heapam_storage_handler(PG_FUNCTION_ARGS)
@@ -325,7 +540,7 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 	amroutine->tuple_insert = heapam_heap_insert;
 	amroutine->tuple_delete = heapam_heap_delete;
 	amroutine->tuple_update = heapam_heap_update;
-	amroutine->tuple_lock = heap_lock_tuple;
+	amroutine->tuple_lock = heapam_lock_tuple;
 	amroutine->multi_insert = heap_multi_insert;
 
 	amroutine->get_tuple_data = heapam_get_tuple_data;
diff --git a/src/backend/access/heap/heapam_visibility.c b/src/backend/access/heap/heapam_visibility.c
index daeb9bdb29..20f7d908e7 100644
--- a/src/backend/access/heap/heapam_visibility.c
+++ b/src/backend/access/heap/heapam_visibility.c
@@ -115,6 +115,9 @@ static inline void
 SetHintBits(HeapTupleHeader tuple, Buffer buffer,
 			uint16 infomask, TransactionId xid)
 {
+	if (!BufferIsValid(buffer))
+		return;
+
 	if (TransactionIdIsValid(xid))
 	{
 		/* NB: xid must be known committed here! */
@@ -613,7 +616,11 @@ HeapTupleSatisfiesUpdate(StorageTuple 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)
@@ -654,7 +661,12 @@ HeapTupleSatisfiesUpdate(StorageTuple 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
@@ -710,7 +722,12 @@ HeapTupleSatisfiesUpdate(StorageTuple 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/storage/storageam.c b/src/backend/access/storage/storageam.c
index 239339c033..39aa55f80c 100644
--- a/src/backend/access/storage/storageam.c
+++ b/src/backend/access/storage/storageam.c
@@ -41,13 +41,14 @@ storage_fetch(Relation relation,
  *	storage_lock_tuple - lock a tuple in shared or exclusive mode
  */
 HTSU_Result
-storage_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * stuple,
-				   CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
-				   bool follow_updates, Buffer *buffer, HeapUpdateFailureData *hufd)
+storage_lock_tuple(Relation relation, ItemPointer tid, Snapshot snapshot,
+				   StorageTuple *stuple, CommandId cid, LockTupleMode mode,
+				   LockWaitPolicy wait_policy, uint8 flags,
+				   HeapUpdateFailureData *hufd)
 {
-	return relation->rd_stamroutine->tuple_lock(relation, tid, stuple,
+	return relation->rd_stamroutine->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 ca413df263..80639a1ed7 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -3019,8 +3019,6 @@ GetTupleForTrigger(EState *estate,
 	Relation	relation = relinfo->ri_RelationDesc;
 	StorageTuple tuple;
 	HeapTuple	result;
-	Buffer		buffer;
-	tuple_data	t_data;
 
 	if (newSlot != NULL)
 	{
@@ -3035,11 +3033,11 @@ GetTupleForTrigger(EState *estate,
 		/*
 		 * lock tuple for update
 		 */
-ltrmark:;
-		test = storage_lock_tuple(relation, tid, &tuple,
+		test = storage_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)
 		{
@@ -3060,63 +3058,54 @@ 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")));
-				t_data = relation->rd_stamroutine->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;
@@ -3146,9 +3135,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 db9196924b..82f1d7d95b 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -2462,9 +2462,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)
@@ -2477,32 +2475,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, StorageTuple tuple)
 {
 	TupleTableSlot *slot;
-	StorageTuple 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 = storage_tuple_get_data(relation, copyTuple, TID);
-	*tid = t_data.tid;
-
 	/*
 	 * Need to run a recheck subquery.  Initialize or reinitialize EPQ state.
 	 */
@@ -2512,7 +2490,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
@@ -2544,256 +2522,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.
- */
-StorageTuple
-EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
-				  LockWaitPolicy wait_policy,
-				  ItemPointer tid, TransactionId priorXmax)
-{
-	StorageTuple 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 (storage_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 = storage_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 = storage_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 0 //hari
-					/* Should not encounter speculative tuple on recheck */
-					Assert(!HeapTupleHeaderIsSpeculative(tuple.t_data));
-#endif
-					t_data = storage_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.
-		 */
-		t_data = storage_tuple_get_data(relation, tuple, CTID);
-		ctid = t_data.tid;
-		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 = storage_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 8a658109d8..9a5e4703be 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;
 		StorageTuple locktup;
 
 		PushActiveSnapshot(GetLatestSnapshot());
 
-		res = storage_lock_tuple(rel, &(outslot->tts_tid), &locktup, GetCurrentCommandId(false),
+		res = storage_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();
@@ -196,6 +194,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:
@@ -274,21 +278,19 @@ retry:
 	/* Found tuple, try to lock it in the lockmode. */
 	if (found)
 	{
-		Buffer		buf;
 		HeapUpdateFailureData hufd;
 		HTSU_Result res;
 		StorageTuple locktup;
 
 		PushActiveSnapshot(GetLatestSnapshot());
 
-		res = storage_lock_tuple(rel, &(outslot->tts_tid), &locktup, GetCurrentCommandId(false),
+		res = storage_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);
 
@@ -304,6 +306,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 c8327ee1ce..f325500743 100644
--- a/src/backend/executor/nodeLockRows.c
+++ b/src/backend/executor/nodeLockRows.c
@@ -79,13 +79,11 @@ lnext:
 		Datum		datum;
 		bool		isNull;
 		StorageTuple tuple;
-		Buffer		buffer;
 		HeapUpdateFailureData hufd;
 		LockTupleMode lockmode;
 		HTSU_Result test;
 		StorageTuple copyTuple;
 		ItemPointerData tid;
-		tuple_data	t_data;
 
 		/* clear any leftover test tuple for this rel */
 		testTuple = (StorageTuple) (&(node->lr_curtuples[erm->rti - 1]));
@@ -183,12 +181,12 @@ lnext:
 				break;
 		}
 
-		test = storage_lock_tuple(erm->relation, &tid, &tuple,
-								  estate->es_output_cid,
-								  lockmode, erm->waitPolicy, true,
-								  &buffer, &hufd);
-		if (BufferIsValid(buffer))
-			ReleaseBuffer(buffer);
+		test = storage_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,38 +231,19 @@ lnext:
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("could not serialize access due to concurrent update")));
-				t_data = erm->relation->rd_stamroutine->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_stamroutine->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;
+				/* skip lock */
+				goto lnext;
 
-				/* Continue loop until we have all target tuples */
-				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; 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 c8cc1e0f12..5352cf903a 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -203,7 +203,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_stamroutine, tuple, estate->es_snapshot, buffer))
 	{
 		tuple_data	t_data = storage_tuple_get_data(rel, tuple, XMIN);
@@ -219,7 +220,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);
 }
 
 /*
@@ -678,6 +680,7 @@ ExecDelete(ModifyTableState *mtstate,
 	HTSU_Result result;
 	HeapUpdateFailureData hufd;
 	TupleTableSlot *slot = NULL;
+	StorageTuple	tuple;
 
 	/*
 	 * get information on the (current) result relation
@@ -760,6 +763,35 @@ ldelete:;
 								true /* wait for commit */ ,
 								NULL,
 								&hufd);
+
+		if (result == HeapTupleUpdated && !IsolationUsesXactSnapshot())
+		{
+			result = storage_lock_tuple(resultRelationDesc, tupleid,
+										estate->es_snapshot,
+										&tuple, estate->es_output_cid,
+										LockTupleExclusive, LockWaitBlock,
+										TUPLE_LOCK_FLAG_FIND_LAST_VERSION,
+										&hufd);
+
+			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:
@@ -805,23 +837,16 @@ ldelete:;
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("could not serialize access 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;
-					}
-				}
+				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("could not serialize access due to concurrent delete")));
 				/* tuple already deleted; nothing to do */
 				return NULL;
 
@@ -1060,6 +1085,37 @@ lreplace:;
 								&hufd, &lockmode,
 								ExecInsertIndexTuples,
 								&recheckIndexes);
+
+		if (result == HeapTupleUpdated && !IsolationUsesXactSnapshot())
+		{
+			result = storage_lock_tuple(resultRelationDesc, tupleid,
+										estate->es_snapshot,
+										&tuple, estate->es_output_cid,
+										lockmode, LockWaitBlock,
+										TUPLE_LOCK_FLAG_FIND_LAST_VERSION,
+										&hufd);
+
+			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:
@@ -1104,25 +1160,16 @@ lreplace:;
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("could not serialize access 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;
-					}
-				}
+				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("could not serialize access due to concurrent delete")));
 				/* tuple already deleted; nothing to do */
 				return NULL;
 
@@ -1191,8 +1238,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);
@@ -1203,8 +1250,12 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	 * previous conclusion that the tuple is conclusively committed is not
 	 * true anymore.
 	 */
-	test = storage_lock_tuple(relation, conflictTid, &tuple, estate->es_output_cid,
-							  lockmode, LockWaitBlock, false, &buffer, &hufd);
+	InitDirtySnapshot(snapshot);
+	test = storage_lock_tuple(relation, conflictTid,
+							  &snapshot,
+							  /*estate->es_snapshot,*/
+							  &tuple, estate->es_output_cid,
+							  lockmode, LockWaitBlock, 0, &hufd);
 	switch (test)
 	{
 		case HeapTupleMayBeUpdated:
@@ -1261,8 +1312,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;
 
@@ -1291,10 +1349,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
@@ -1309,8 +1367,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 */
@@ -1356,8 +1412,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 58a955be4f..27399cf817 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;
 
 
@@ -162,10 +163,10 @@ extern HTSU_Result heap_update(Relation relation, ItemPointer otid,
 			HeapTuple newtup,
 			CommandId cid, Snapshot crosscheck, bool wait,
 			HeapUpdateFailureData *hufd, LockTupleMode *lockmode);
-extern HTSU_Result heap_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * tuple,
-				CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
-				bool follow_update,
-				Buffer *buffer, HeapUpdateFailureData *hufd);
+extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
+			CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
+			bool follow_updates,
+			Buffer *buffer, HeapUpdateFailureData *hufd);
 
 extern void heap_inplace_update(Relation relation, HeapTuple tuple);
 extern bool heap_freeze_tuple(HeapTupleHeader tuple,
diff --git a/src/include/access/storageam.h b/src/include/access/storageam.h
index c7cd01cd10..3e151a70ca 100644
--- a/src/include/access/storageam.h
+++ b/src/include/access/storageam.h
@@ -87,10 +87,10 @@ extern bool storage_hot_search_buffer(ItemPointer tid, Relation relation, Buffer
 extern bool storage_hot_search(ItemPointer tid, Relation relation, Snapshot snapshot,
 				   bool *all_dead);
 
-extern HTSU_Result storage_lock_tuple(Relation relation, ItemPointer tid, StorageTuple * stuple,
-				   CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
-				   bool follow_updates,
-				   Buffer *buffer, HeapUpdateFailureData *hufd);
+extern HTSU_Result storage_lock_tuple(Relation relation, ItemPointer tid, Snapshot snapshot,
+				   StorageTuple *stuple, CommandId cid, LockTupleMode mode,
+				   LockWaitPolicy wait_policy, uint8 flags,
+				   HeapUpdateFailureData *hufd);
 
 extern Oid storage_insert(Relation relation, TupleTableSlot *slot, CommandId cid,
 			   int options, BulkInsertState bistate, InsertIndexTuples IndexFunc,
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
index b7a0d5fb07..4c39de6f31 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -61,12 +61,12 @@ typedef bool (*TupleFetch_function) (Relation relation,
 
 typedef HTSU_Result (*TupleLock_function) (Relation relation,
 										   ItemPointer tid,
-										   StorageTuple * tuple,
+										   Snapshot snapshot,
+										   StorageTuple *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 612d468f1f..b4cb83dd25 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -196,11 +196,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 StorageTuple EvalPlanQualFetch(EState *estate, Relation relation,
-									  int lockmode, LockWaitPolicy wait_policy, ItemPointer tid,
-									  TransactionId priorXmax);
+			 Relation relation, Index rti, StorageTuple 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;
-- 
2.15.0.windows.1



^ permalink  raw  reply  [nested|flat] 21+ messages in thread

* Re: [HACKERS] Pluggable storage
  2017-12-12 04:06 Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
  2017-12-27 03:54 ` Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
  2017-12-27 12:33   ` Re: [HACKERS] Pluggable storage Alexander Korotkov <[email protected]>
  2018-01-03 07:08     ` Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
@ 2018-01-03 23:00       ` Alexander Korotkov <[email protected]>
  2018-01-04 05:03         ` Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
  1 sibling, 1 reply; 21+ messages in thread

From: Alexander Korotkov @ 2018-01-03 23:00 UTC (permalink / raw)
  To: Haribabu Kommi <[email protected]>; +Cc: Michael Paquier <[email protected]>; Robert Haas <[email protected]>; Amit Kapila <[email protected]>; Thomas Munro <[email protected]>; Alvaro Herrera <[email protected]>; pgsql-hackers

On Wed, Jan 3, 2018 at 10:08 AM, Haribabu Kommi <[email protected]>
wrote:

>
> On Wed, Dec 27, 2017 at 11:33 PM, Alexander Korotkov <
> [email protected]> wrote:
>
>>
>> Also, I appreciate that now tuple_insert() and tuple_update() methods are
>> responsible for inserting index tuples.  This unleash pluggable storages to
>> implement another way of interaction with indexes.  However, I didn't get
>> the point of passing InsertIndexTuples IndexFunc to them.  Now, we're
>> always passing ExecInsertIndexTuples() to this argument.  As I
>> understood storage is free to either call ExecInsertIndexTuples() or
>> implement its own logic of interaction with indexes.  But, I don't
>> understand why do we need a callback when tuple_insert() and tuple_update()
>> can call ExecInsertIndexTuples() directly if needed.  Another thing is that
>> tuple_delete() could also interact with indexes (especially when we will
>> enhance index access method API), and we need to pass meta-information
>> about indexes to tuple_delete() too.
>>
>
> The main reason for which I added the callback function to not to
> introduce the
> dependency of storage on executor functions. This way storage can call the
> function that is passed to it without any knowledge. I added the function
> pointer
> for tuple_delete also in the new patches, currently it is passed as NULL
> for heap.
> These API's can be enhanced later.
>

Understood, but in order to implement alternative behavior with indexes
(for example,
insert index tuples to only some of indexes), storage am will still have to
call executor
functions.  So, yes this needs to be enhanced.  Probably, we just need to
implement
nicer executor API for storage am.


> Apart from rebase, Added storage shared memory API, currently this API is
> used
> only by the syncscan. And also all the exposed functions of syncscan usage
> is
> removed outside the heap.
>

This makes me uneasy.  You introduce two new hooks for size estimation and
initialization
of shared memory needed by storage am's.  But if storage am is implemented
in shared library,
then this shared library can use our generic method for allocation of
shared memory
(including memory needed by storage am).  If storage am is builtin, then
hooks are also not
needed, because we know all our builtin storage am's in advance.  For me,
it would be
nice to encapsulate heap am requirements in shared memory into functions
like
HeapAmShmemSize() and HeapAmShmemInit(), and don't explicitly show outside
that
this memory is needed for synchronized scan.  But separate hooks don't look
justified for me.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company


^ permalink  raw  reply  [nested|flat] 21+ messages in thread

* Re: [HACKERS] Pluggable storage
  2017-12-12 04:06 Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
  2017-12-27 03:54 ` Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
  2017-12-27 12:33   ` Re: [HACKERS] Pluggable storage Alexander Korotkov <[email protected]>
  2018-01-03 07:08     ` Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
  2018-01-03 23:00       ` Re: [HACKERS] Pluggable storage Alexander Korotkov <[email protected]>
@ 2018-01-04 05:03         ` Haribabu Kommi <[email protected]>
  2018-01-04 22:55           ` Re: [HACKERS] Pluggable storage Alexander Korotkov <[email protected]>
  0 siblings, 1 reply; 21+ messages in thread

From: Haribabu Kommi @ 2018-01-04 05:03 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Michael Paquier <[email protected]>; Robert Haas <[email protected]>; Amit Kapila <[email protected]>; Thomas Munro <[email protected]>; Alvaro Herrera <[email protected]>; pgsql-hackers

On Thu, Jan 4, 2018 at 10:00 AM, Alexander Korotkov <
[email protected]> wrote:

> On Wed, Jan 3, 2018 at 10:08 AM, Haribabu Kommi <[email protected]>
> wrote:
>
>>
>> On Wed, Dec 27, 2017 at 11:33 PM, Alexander Korotkov <
>> [email protected]> wrote:
>>
>>>
>>> Also, I appreciate that now tuple_insert() and tuple_update() methods
>>> are responsible for inserting index tuples.  This unleash pluggable
>>> storages to implement another way of interaction with indexes.  However, I
>>> didn't get the point of passing InsertIndexTuples IndexFunc to them.  Now,
>>> we're always passing ExecInsertIndexTuples() to this argument.  As I
>>> understood storage is free to either call ExecInsertIndexTuples() or
>>> implement its own logic of interaction with indexes.  But, I don't
>>> understand why do we need a callback when tuple_insert() and tuple_update()
>>> can call ExecInsertIndexTuples() directly if needed.  Another thing is that
>>> tuple_delete() could also interact with indexes (especially when we will
>>> enhance index access method API), and we need to pass meta-information
>>> about indexes to tuple_delete() too.
>>>
>>
>> The main reason for which I added the callback function to not to
>> introduce the
>> dependency of storage on executor functions. This way storage can call the
>> function that is passed to it without any knowledge. I added the function
>> pointer
>> for tuple_delete also in the new patches, currently it is passed as NULL
>> for heap.
>> These API's can be enhanced later.
>>
>
> Understood, but in order to implement alternative behavior with indexes
> (for example,
> insert index tuples to only some of indexes), storage am will still have
> to call executor
> functions.  So, yes this needs to be enhanced.  Probably, we just need to
> implement
> nicer executor API for storage am.
>

OK.


> Apart from rebase, Added storage shared memory API, currently this API is
>> used
>> only by the syncscan. And also all the exposed functions of syncscan
>> usage is
>> removed outside the heap.
>>
>
> This makes me uneasy.  You introduce two new hooks for size estimation and
> initialization
> of shared memory needed by storage am's.  But if storage am is implemented
> in shared library,
> then this shared library can use our generic method for allocation of
> shared memory
> (including memory needed by storage am).  If storage am is builtin, then
> hooks are also not
> needed, because we know all our builtin storage am's in advance.  For me,
> it would be
> nice to encapsulate heap am requirements in shared memory into functions
> like
> HeapAmShmemSize() and HeapAmShmemInit(), and don't explicitly show outside
> that
> this memory is needed for synchronized scan.  But separate hooks don't
> look justified for me.
>

Yes, I agree that for the builtin storage's there is no need of hooks. But
in future,
if we want to support multiple storage's in an instance, we may need hooks
for shared memory
registration. I am fine to change it.

Regards,
Hari Babu
Fujitsu Australia


^ permalink  raw  reply  [nested|flat] 21+ messages in thread

* Re: [HACKERS] Pluggable storage
  2017-12-12 04:06 Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
  2017-12-27 03:54 ` Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
  2017-12-27 12:33   ` Re: [HACKERS] Pluggable storage Alexander Korotkov <[email protected]>
  2018-01-03 07:08     ` Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
  2018-01-03 23:00       ` Re: [HACKERS] Pluggable storage Alexander Korotkov <[email protected]>
  2018-01-04 05:03         ` Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
@ 2018-01-04 22:55           ` Alexander Korotkov <[email protected]>
  2018-01-04 23:20             ` Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
  0 siblings, 1 reply; 21+ messages in thread

From: Alexander Korotkov @ 2018-01-04 22:55 UTC (permalink / raw)
  To: Haribabu Kommi <[email protected]>; +Cc: Michael Paquier <[email protected]>; Robert Haas <[email protected]>; Amit Kapila <[email protected]>; Thomas Munro <[email protected]>; Alvaro Herrera <[email protected]>; pgsql-hackers

On Thu, Jan 4, 2018 at 8:03 AM, Haribabu Kommi <[email protected]>
wrote:

> On Thu, Jan 4, 2018 at 10:00 AM, Alexander Korotkov <
> [email protected]> wrote:
>
>> On Wed, Jan 3, 2018 at 10:08 AM, Haribabu Kommi <[email protected]
>> > wrote:
>>
>>> Apart from rebase, Added storage shared memory API, currently this API
>>> is used
>>>
>> only by the syncscan. And also all the exposed functions of syncscan
>>> usage is
>>> removed outside the heap.
>>>
>>
>> This makes me uneasy.  You introduce two new hooks for size estimation
>> and initialization
>> of shared memory needed by storage am's.  But if storage am is
>> implemented in shared library,
>> then this shared library can use our generic method for allocation of
>> shared memory
>> (including memory needed by storage am).  If storage am is builtin, then
>> hooks are also not
>> needed, because we know all our builtin storage am's in advance.  For me,
>> it would be
>> nice to encapsulate heap am requirements in shared memory into functions
>> like
>> HeapAmShmemSize() and HeapAmShmemInit(), and don't explicitly show
>> outside that
>> this memory is needed for synchronized scan.  But separate hooks don't
>> look justified for me.
>>
>
> Yes, I agree that for the builtin storage's there is no need of hooks. But
> in future,
> if we want to support multiple storage's in an instance, we may need hooks
> for shared memory
> registration. I am fine to change it.
>

Yes, but we already have hooks for shared memory registration in shared
modules.  I don't see the point for another hooks for the same purpose.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company


^ permalink  raw  reply  [nested|flat] 21+ messages in thread

* Re: [HACKERS] Pluggable storage
  2017-12-12 04:06 Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
  2017-12-27 03:54 ` Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
  2017-12-27 12:33   ` Re: [HACKERS] Pluggable storage Alexander Korotkov <[email protected]>
  2018-01-03 07:08     ` Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
  2018-01-03 23:00       ` Re: [HACKERS] Pluggable storage Alexander Korotkov <[email protected]>
  2018-01-04 05:03         ` Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
  2018-01-04 22:55           ` Re: [HACKERS] Pluggable storage Alexander Korotkov <[email protected]>
@ 2018-01-04 23:20             ` Haribabu Kommi <[email protected]>
  0 siblings, 0 replies; 21+ messages in thread

From: Haribabu Kommi @ 2018-01-04 23:20 UTC (permalink / raw)
  To: Alexander Korotkov <[email protected]>; +Cc: Michael Paquier <[email protected]>; Robert Haas <[email protected]>; Amit Kapila <[email protected]>; Thomas Munro <[email protected]>; Alvaro Herrera <[email protected]>; pgsql-hackers

On Fri, Jan 5, 2018 at 9:55 AM, Alexander Korotkov <
[email protected]> wrote:

> On Thu, Jan 4, 2018 at 8:03 AM, Haribabu Kommi <[email protected]>
> wrote:
>
>> On Thu, Jan 4, 2018 at 10:00 AM, Alexander Korotkov <
>> [email protected]> wrote:
>>
>>> On Wed, Jan 3, 2018 at 10:08 AM, Haribabu Kommi <
>>> [email protected]> wrote:
>>>
>>>> Apart from rebase, Added storage shared memory API, currently this API
>>>> is used
>>>>
>>> only by the syncscan. And also all the exposed functions of syncscan
>>>> usage is
>>>> removed outside the heap.
>>>>
>>>
>>> This makes me uneasy.  You introduce two new hooks for size estimation
>>> and initialization
>>> of shared memory needed by storage am's.  But if storage am is
>>> implemented in shared library,
>>> then this shared library can use our generic method for allocation of
>>> shared memory
>>> (including memory needed by storage am).  If storage am is builtin, then
>>> hooks are also not
>>> needed, because we know all our builtin storage am's in advance.  For
>>> me, it would be
>>> nice to encapsulate heap am requirements in shared memory into functions
>>> like
>>> HeapAmShmemSize() and HeapAmShmemInit(), and don't explicitly show
>>> outside that
>>> this memory is needed for synchronized scan.  But separate hooks don't
>>> look justified for me.
>>>
>>
>> Yes, I agree that for the builtin storage's there is no need of hooks.
>> But in future,
>> if we want to support multiple storage's in an instance, we may need
>> hooks for shared memory
>> registration. I am fine to change it.
>>
>
> Yes, but we already have hooks for shared memory registration in shared
> modules.  I don't see the point for another hooks for the same purpose.
>

Oh, yes, I missed it. I will update the patch and share it later.


Regards,
Hari Babu
Fujitsu Australia


^ permalink  raw  reply  [nested|flat] 21+ messages in thread

* Re: [HACKERS] Pluggable storage
  2017-12-12 04:06 Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
  2017-12-27 03:54 ` Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
  2017-12-27 12:33   ` Re: [HACKERS] Pluggable storage Alexander Korotkov <[email protected]>
  2018-01-03 07:08     ` Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
@ 2018-01-05 16:20       ` Robert Haas <[email protected]>
  2018-01-05 19:31         ` Re: [HACKERS] Pluggable storage Alexander Korotkov <[email protected]>
  1 sibling, 1 reply; 21+ messages in thread

From: Robert Haas @ 2018-01-05 16:20 UTC (permalink / raw)
  To: Haribabu Kommi <[email protected]>; +Cc: Alexander Korotkov <[email protected]>; Michael Paquier <[email protected]>; Amit Kapila <[email protected]>; Thomas Munro <[email protected]>; Alvaro Herrera <[email protected]>; pgsql-hackers

I do not like the way that this patch set uses the word "storage".  In
current usage, storage is a thing that certain kinds of relations
have.  Plain relations (a.k.a. heap tables) have storage, indexes have
storage, materialized views have storage, TOAST tables have storage,
and sequences have storage.  This patch set wants to use "storage AM"
to mean a replacement for a plain heap table, but I think that's going
to create a lot of confusion, because it overlaps heavily with the
existing meaning yet is different.  My suggestion is to call these
"table access methods" rather than "storage access methods".  Then,
the default table AM can be heap.  This has the nice property that you
create an index using CREATE INDEX and the support functions arrive
via an IndexAmRoutine, so correspondingly you would create a table
using CREATE TABLE and the support functions would arrive via a
TableAmRoutine -- so all the names match.

An alternative would be to call the new thing a "heap AM" with
HeapAmRoutine as the support function structure.  That's also not
unreasonable.  In that case, we're deciding that "heap" is not just
the name of the current implementation, but the name of the kind of
thing that backs a table at the storage level.  I don't like that
quite as well because then we've got a class of things called a heap
of which the current and only implementation is called heap, which is
a bit confusing in my view.  But we could probably make it work.

If we adopt the first proposal, it leads to another nice parallel: we
can have src/backend/access/table for those things which are generic
to table AMs, just as we have src/backend/access/index for things
which are generic to index AMs, and then src/backend/access/<am-name>
for things which are specific to a particular AM.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company




^ permalink  raw  reply  [nested|flat] 21+ messages in thread

* Re: [HACKERS] Pluggable storage
  2017-12-12 04:06 Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
  2017-12-27 03:54 ` Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
  2017-12-27 12:33   ` Re: [HACKERS] Pluggable storage Alexander Korotkov <[email protected]>
  2018-01-03 07:08     ` Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
  2018-01-05 16:20       ` Re: [HACKERS] Pluggable storage Robert Haas <[email protected]>
@ 2018-01-05 19:31         ` Alexander Korotkov <[email protected]>
  2018-01-09 12:42           ` Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
  0 siblings, 1 reply; 21+ messages in thread

From: Alexander Korotkov @ 2018-01-05 19:31 UTC (permalink / raw)
  To: Robert Haas <[email protected]>; +Cc: Haribabu Kommi <[email protected]>; Michael Paquier <[email protected]>; Amit Kapila <[email protected]>; Thomas Munro <[email protected]>; Alvaro Herrera <[email protected]>; pgsql-hackers

On Fri, Jan 5, 2018 at 7:20 PM, Robert Haas <[email protected]> wrote:

> I do not like the way that this patch set uses the word "storage".  In
> current usage, storage is a thing that certain kinds of relations
> have.  Plain relations (a.k.a. heap tables) have storage, indexes have
> storage, materialized views have storage, TOAST tables have storage,
> and sequences have storage.  This patch set wants to use "storage AM"
> to mean a replacement for a plain heap table, but I think that's going
> to create a lot of confusion, because it overlaps heavily with the
> existing meaning yet is different.


Good point, thank you for noticing that.  Name "storage" is really confusing
for this purpose.


> My suggestion is to call these
> "table access methods" rather than "storage access methods".  Then,
> the default table AM can be heap.  This has the nice property that you
> create an index using CREATE INDEX and the support functions arrive
> via an IndexAmRoutine, so correspondingly you would create a table
> using CREATE TABLE and the support functions would arrive via a
> TableAmRoutine -- so all the names match.
>
> An alternative would be to call the new thing a "heap AM" with
> HeapAmRoutine as the support function structure.  That's also not
> unreasonable.  In that case, we're deciding that "heap" is not just
> the name of the current implementation, but the name of the kind of
> thing that backs a table at the storage level.  I don't like that
> quite as well because then we've got a class of things called a heap
> of which the current and only implementation is called heap, which is
> a bit confusing in my view.  But we could probably make it work.
>
> If we adopt the first proposal, it leads to another nice parallel: we
> can have src/backend/access/table for those things which are generic
> to table AMs, just as we have src/backend/access/index for things
> which are generic to index AMs, and then src/backend/access/<am-name>
> for things which are specific to a particular AM.


I would vote for the first proposal: table AM.  Because we eventually
might get index-organized tables whose don't have something like heap.
So, it would be nice to avoid hardcoding "heap" name.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company


^ permalink  raw  reply  [nested|flat] 21+ messages in thread

* Re: [HACKERS] Pluggable storage
  2017-12-12 04:06 Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
  2017-12-27 03:54 ` Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
  2017-12-27 12:33   ` Re: [HACKERS] Pluggable storage Alexander Korotkov <[email protected]>
  2018-01-03 07:08     ` Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
  2018-01-05 16:20       ` Re: [HACKERS] Pluggable storage Robert Haas <[email protected]>
  2018-01-05 19:31         ` Re: [HACKERS] Pluggable storage Alexander Korotkov <[email protected]>
@ 2018-01-09 12:42           ` Haribabu Kommi <[email protected]>
  0 siblings, 0 replies; 21+ messages in thread

From: Haribabu Kommi @ 2018-01-09 12:42 UTC (permalink / raw)
  To: Robert Haas <[email protected]>; Alexander Korotkov <[email protected]>; +Cc: Michael Paquier <[email protected]>; Amit Kapila <[email protected]>; Thomas Munro <[email protected]>; Alvaro Herrera <[email protected]>; pgsql-hackers

On Sat, Jan 6, 2018 at 6:31 AM, Alexander Korotkov <
[email protected]> wrote:

> On Fri, Jan 5, 2018 at 7:20 PM, Robert Haas <[email protected]> wrote:
>
>> I do not like the way that this patch set uses the word "storage".  In
>> current usage, storage is a thing that certain kinds of relations
>> have.  Plain relations (a.k.a. heap tables) have storage, indexes have
>> storage, materialized views have storage, TOAST tables have storage,
>> and sequences have storage.  This patch set wants to use "storage AM"
>> to mean a replacement for a plain heap table, but I think that's going
>> to create a lot of confusion, because it overlaps heavily with the
>> existing meaning yet is different.
>
>
> Good point, thank you for noticing that.  Name "storage" is really
> confusing
> for this purpose.
>

Thanks for the review and suggestion.


> My suggestion is to call these
>> "table access methods" rather than "storage access methods".  Then,
>> the default table AM can be heap.  This has the nice property that you
>> create an index using CREATE INDEX and the support functions arrive
>> via an IndexAmRoutine, so correspondingly you would create a table
>> using CREATE TABLE and the support functions would arrive via a
>> TableAmRoutine -- so all the names match.
>>
>
I changed the patches to use Table instead of storage. Changed all the
structures and exposed functions also for better readability

Updated patches are attached.

Regards,
Hari Babu
Fujitsu Australia


Attachments:

  [application/octet-stream] 0010-table-rewrite-functionality.patch (11.1K, ../../CAJrrPGdY_hgvu06R_vCibUktKRLp=b8nBfeZkh=KRc95tq4euA@mail.gmail.com/3-0010-table-rewrite-functionality.patch)
  download | inline diff:
From 3240f10aa16ff60c5d8b4137be7ca859f9f0611e Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Tue, 9 Jan 2018 18:29:56 +1100
Subject: [PATCH 10/12] table rewrite functionality

All the heap rewrite functionality is moved into heap table AM
and exposed them with table AM API. Currenlty these API's are used
only by the cluster command operation.

The logical rewrite mapping code is currently left as it is,
this needs to be handled separately.
---
 src/backend/access/heap/heapam_handler.c |  6 ++++++
 src/backend/access/table/tableam.c       | 33 ++++++++++++++++++++++++++++++++
 src/backend/commands/cluster.c           | 32 +++++++++++++++----------------
 src/include/access/heapam.h              |  9 +++++++++
 src/include/access/rewriteheap.h         | 11 -----------
 src/include/access/tableam.h             |  8 ++++++++
 src/include/access/tableam_common.h      |  2 ++
 src/include/access/tableamapi.h          | 13 +++++++++++++
 8 files changed, 86 insertions(+), 28 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ba49551a07..53123927da 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -22,6 +22,7 @@
 
 #include "access/heapam.h"
 #include "access/relscan.h"
+#include "access/rewriteheap.h"
 #include "access/tableamapi.h"
 #include "pgstat.h"
 #include "storage/lmgr.h"
@@ -388,5 +389,10 @@ heap_tableam_handler(PG_FUNCTION_ARGS)
 	amroutine->freebulkinsertstate = FreeBulkInsertState;
 	amroutine->releasebulkinsertstate = ReleaseBulkInsertStatePin;
 
+	amroutine->begin_heap_rewrite = begin_heap_rewrite;
+	amroutine->end_heap_rewrite = end_heap_rewrite;
+	amroutine->rewrite_heap_tuple = rewrite_heap_tuple;
+	amroutine->rewrite_heap_dead_tuple = rewrite_heap_dead_tuple;
+
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index 626d7ab237..08b4ddd9d0 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -402,3 +402,36 @@ table_releasebulkinsertstate(Relation rel, BulkInsertState bistate)
 {
 	rel->rd_tableamroutine->releasebulkinsertstate(bistate);
 }
+
+/*
+ * -------------------
+ * storage tuple rewrite functions
+ * -------------------
+ */
+RewriteState
+table_begin_rewrite(Relation OldHeap, Relation NewHeap,
+				   TransactionId OldestXmin, TransactionId FreezeXid,
+				   MultiXactId MultiXactCutoff, bool use_wal)
+{
+	return NewHeap->rd_tableamroutine->begin_heap_rewrite(OldHeap, NewHeap,
+			OldestXmin, FreezeXid, MultiXactCutoff, use_wal);
+}
+
+void
+table_end_rewrite(Relation rel, RewriteState state)
+{
+	rel->rd_tableamroutine->end_heap_rewrite(state);
+}
+
+void
+table_rewrite_tuple(Relation rel, RewriteState state, HeapTuple oldTuple,
+				   HeapTuple newTuple)
+{
+	rel->rd_tableamroutine->rewrite_heap_tuple(state, oldTuple, newTuple);
+}
+
+bool
+table_rewrite_dead_tuple(Relation rel, RewriteState state, HeapTuple oldTuple)
+{
+	return rel->rd_tableamroutine->rewrite_heap_dead_tuple(state, oldTuple);
+}
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 00eea8fc37..3133a2ad06 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -74,9 +74,8 @@ static void copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 			   TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
 static void reform_and_rewrite_tuple(HeapTuple tuple,
-						 TupleDesc oldTupDesc, TupleDesc newTupDesc,
-						 Datum *values, bool *isnull,
-						 bool newRelHasOids, RewriteState rwstate);
+						 Relation OldHeap, Relation NewHeap,
+						 Datum *values, bool *isnull, RewriteState rwstate);
 
 
 /*---------------------------------------------------------------------------
@@ -878,7 +877,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	is_system_catalog = IsSystemRelation(OldHeap);
 
 	/* Initialize the rewrite operation */
-	rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin, FreezeXid,
+	rwstate = table_begin_rewrite(OldHeap, NewHeap, OldestXmin, FreezeXid,
 								 MultiXactCutoff, use_wal);
 
 	/*
@@ -1027,7 +1026,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 		{
 			tups_vacuumed += 1;
 			/* heap rewrite module still needs to see it... */
-			if (rewrite_heap_dead_tuple(rwstate, tuple))
+			if (table_rewrite_dead_tuple(NewHeap, rwstate, tuple))
 			{
 				/* A previous recently-dead tuple is now known dead */
 				tups_vacuumed += 1;
@@ -1041,9 +1040,8 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 			tuplesort_putheaptuple(tuplesort, tuple);
 		else
 			reform_and_rewrite_tuple(tuple,
-									 oldTupDesc, newTupDesc,
-									 values, isnull,
-									 NewHeap->rd_rel->relhasoids, rwstate);
+									 OldHeap, NewHeap,
+									 values, isnull, rwstate);
 	}
 
 	if (indexScan != NULL)
@@ -1070,16 +1068,15 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 				break;
 
 			reform_and_rewrite_tuple(tuple,
-									 oldTupDesc, newTupDesc,
-									 values, isnull,
-									 NewHeap->rd_rel->relhasoids, rwstate);
+									 OldHeap, NewHeap,
+									 values, isnull, rwstate);
 		}
 
 		tuplesort_end(tuplesort);
 	}
 
 	/* Write out any remaining tuples, and fsync if needed */
-	end_heap_rewrite(rwstate);
+	table_end_rewrite(NewHeap, rwstate);
 
 	/* Reset rd_toastoid just to be tidy --- it shouldn't be looked at again */
 	NewHeap->rd_toastoid = InvalidOid;
@@ -1733,10 +1730,11 @@ get_tables_to_cluster(MemoryContext cluster_context)
  */
 static void
 reform_and_rewrite_tuple(HeapTuple tuple,
-						 TupleDesc oldTupDesc, TupleDesc newTupDesc,
-						 Datum *values, bool *isnull,
-						 bool newRelHasOids, RewriteState rwstate)
+						 Relation OldHeap, Relation NewHeap,
+						 Datum *values, bool *isnull, RewriteState rwstate)
 {
+	TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
+	TupleDesc newTupDesc = RelationGetDescr(NewHeap);
 	HeapTuple	copiedTuple;
 	int			i;
 
@@ -1752,11 +1750,11 @@ reform_and_rewrite_tuple(HeapTuple tuple,
 	copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
 
 	/* Preserve OID, if any */
-	if (newRelHasOids)
+	if (NewHeap->rd_rel->relhasoids)
 		HeapTupleSetOid(copiedTuple, HeapTupleGetOid(tuple));
 
 	/* The heap rewrite module does the rest */
-	rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+	table_rewrite_tuple(NewHeap, rwstate, tuple, copiedTuple);
 
 	heap_freetuple(copiedTuple);
 }
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index f9a0602b86..354ac62fb1 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -212,4 +212,13 @@ extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
 extern bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
 extern bool HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin);
 
+/* in heap/rewriteheap.c */
+extern RewriteState begin_heap_rewrite(Relation OldHeap, Relation NewHeap,
+				   TransactionId OldestXmin, TransactionId FreezeXid,
+				   MultiXactId MultiXactCutoff, bool use_wal);
+extern void end_heap_rewrite(RewriteState state);
+extern void rewrite_heap_tuple(RewriteState state, HeapTuple oldTuple,
+				   HeapTuple newTuple);
+extern bool rewrite_heap_dead_tuple(RewriteState state, HeapTuple oldTuple);
+
 #endif							/* HEAPAM_H */
diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h
index 6d7f669cbc..c610914133 100644
--- a/src/include/access/rewriteheap.h
+++ b/src/include/access/rewriteheap.h
@@ -18,17 +18,6 @@
 #include "storage/relfilenode.h"
 #include "utils/relcache.h"
 
-/* struct definition is private to rewriteheap.c */
-typedef struct RewriteStateData *RewriteState;
-
-extern RewriteState begin_heap_rewrite(Relation OldHeap, Relation NewHeap,
-				   TransactionId OldestXmin, TransactionId FreezeXid,
-				   MultiXactId MultiXactCutoff, bool use_wal);
-extern void end_heap_rewrite(RewriteState state);
-extern void rewrite_heap_tuple(RewriteState state, HeapTuple oldTuple,
-				   HeapTuple newTuple);
-extern bool rewrite_heap_dead_tuple(RewriteState state, HeapTuple oldTuple);
-
 /*
  * On-Disk data format for an individual logical rewrite mapping.
  */
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 1027bcfba8..54f7c41108 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -122,4 +122,12 @@ extern BulkInsertState table_getbulkinsertstate(Relation rel);
 extern void table_freebulkinsertstate(Relation rel, BulkInsertState bistate);
 extern void table_releasebulkinsertstate(Relation rel, BulkInsertState bistate);
 
+extern RewriteState table_begin_rewrite(Relation OldHeap, Relation NewHeap,
+				   TransactionId OldestXmin, TransactionId FreezeXid,
+				   MultiXactId MultiXactCutoff, bool use_wal);
+extern void table_end_rewrite(Relation rel, RewriteState state);
+extern void table_rewrite_tuple(Relation rel, RewriteState state, HeapTuple oldTuple,
+				   HeapTuple newTuple);
+extern bool table_rewrite_dead_tuple(Relation rel, RewriteState state, HeapTuple oldTuple);
+
 #endif		/* TABLEAM_H */
diff --git a/src/include/access/tableam_common.h b/src/include/access/tableam_common.h
index 74c8ac58bb..c147cb7c24 100644
--- a/src/include/access/tableam_common.h
+++ b/src/include/access/tableam_common.h
@@ -41,6 +41,8 @@ typedef enum
 
 typedef struct BulkInsertStateData *BulkInsertState;
 
+/* struct definition is private to rewriteheap.c */
+typedef struct RewriteStateData *RewriteState;
 
 /*
  * slot table AM routine functions
diff --git a/src/include/access/tableamapi.h b/src/include/access/tableamapi.h
index adc3057eca..9e43db0259 100644
--- a/src/include/access/tableamapi.h
+++ b/src/include/access/tableamapi.h
@@ -85,6 +85,14 @@ typedef BulkInsertState (*GetBulkInsertState_function) (void);
 typedef void (*FreeBulkInsertState_function) (BulkInsertState bistate);
 typedef void (*ReleaseBulkInsertState_function) (BulkInsertState bistate);
 
+typedef RewriteState (*BeginHeapRewrite_function) (Relation OldHeap, Relation NewHeap,
+				   TransactionId OldestXmin, TransactionId FreezeXid,
+				   MultiXactId MultiXactCutoff, bool use_wal);
+typedef void (*EndHeapRewrite_function) (RewriteState state);
+typedef void (*RewriteHeapTuple_function) (RewriteState state, HeapTuple oldTuple,
+				   HeapTuple newTuple);
+typedef bool (*RewriteHeapDeadTuple_function) (RewriteState state, HeapTuple oldTuple);
+
 typedef TableScanDesc (*ScanBegin_function) (Relation relation,
 											Snapshot snapshot,
 											int nkeys, ScanKey key,
@@ -161,6 +169,11 @@ typedef struct TableAmRoutine
 	FreeBulkInsertState_function freebulkinsertstate;
 	ReleaseBulkInsertState_function releasebulkinsertstate;
 
+	BeginHeapRewrite_function begin_heap_rewrite;
+	EndHeapRewrite_function end_heap_rewrite;
+	RewriteHeapTuple_function rewrite_heap_tuple;
+	RewriteHeapDeadTuple_function rewrite_heap_dead_tuple;
+
 	/* Operations on relation scans */
 	ScanBegin_function scan_begin;
 	ScanGetParallelheapscandesc_function scan_get_parallelheapscandesc;
-- 
2.15.0.windows.1



  [application/octet-stream] 0011-Improve-tuple-locking-interface.patch (46.6K, ../../CAJrrPGdY_hgvu06R_vCibUktKRLp=b8nBfeZkh=KRc95tq4euA@mail.gmail.com/4-0011-Improve-tuple-locking-interface.patch)
  download | inline diff:
From 2e460d5bab7a712b3b55c88ba0a6f2023dc474b0 Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Tue, 9 Jan 2018 22:14:20 +1100
Subject: [PATCH 11/12] 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    | 173 ++++++++++++++++-
 src/backend/access/heap/heapam_visibility.c |  20 +-
 src/backend/access/table/tableam.c          |  11 +-
 src/backend/commands/trigger.c              |  66 +++----
 src/backend/executor/execMain.c             | 278 +---------------------------
 src/backend/executor/execReplication.c      |  36 ++--
 src/backend/executor/nodeLockRows.c         |  67 +++----
 src/backend/executor/nodeModifyTable.c      | 152 ++++++++++-----
 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 +
 15 files changed, 414 insertions(+), 441 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 9c477f7013..53cefb9a59 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3191,6 +3191,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;
@@ -3204,6 +3205,8 @@ l1:
 			UnlockTupleTuplock(relation, &(tp.t_self), LockTupleExclusive);
 		if (vmbuffer != InvalidBuffer)
 			ReleaseBuffer(vmbuffer);
+		if (result == HeapTupleUpdated && ItemPointerEquals(tid, &hufd->ctid))
+			result = HeapTupleDeleted;
 		return result;
 	}
 
@@ -3413,6 +3416,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;
@@ -3832,6 +3839,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;
@@ -3850,6 +3858,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;
 	}
 
@@ -4468,6 +4478,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;
@@ -4520,6 +4534,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
@@ -4588,7 +4603,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;
@@ -4868,7 +4883,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;
@@ -5028,6 +5043,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;
 	}
@@ -5036,7 +5053,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);
@@ -5915,6 +5932,10 @@ next:
 	result = HeapTupleMayBeUpdated;
 
 out_locked:
+
+	if (result == HeapTupleUpdated && ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid))
+		result = HeapTupleDeleted;
+
 	UnlockReleaseBuffer(buf);
 
 	if (vmbuffer != InvalidBuffer)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 53123927da..49a295951e 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,185 @@ 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 (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 08b4ddd9d0..a25812bd1c 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 4cb6930fcb..837508e83f 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -3019,8 +3019,6 @@ GetTupleForTrigger(EState *estate,
 	Relation	relation = relinfo->ri_RelationDesc;
 	TableTuple tuple;
 	HeapTuple	result;
-	Buffer		buffer;
-	tuple_data	t_data;
 
 	if (newSlot != NULL)
 	{
@@ -3035,11 +3033,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)
 		{
@@ -3060,63 +3058,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")));
-				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;
@@ -3146,9 +3136,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 cec8ab9980..f7e4553b61 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -2477,9 +2477,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)
@@ -2492,32 +2490,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.
 	 */
@@ -2527,7 +2505,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
@@ -2559,256 +2537,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 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.
-		 */
-		t_data = table_tuple_get_data(relation, tuple, CTID);
-		ctid = t_data.tid;
-		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 6561f52792..3ab6db5902 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();
@@ -196,6 +194,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:
@@ -274,21 +278,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);
 
@@ -304,6 +306,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 d827fccc54..271d1a7b8a 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,38 +231,19 @@ lnext:
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("could not serialize access 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;
+				/* skip lock */
+				goto lnext;
 
-				/* Continue loop until we have all target tuples */
-				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; 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 ba27bf002a..e3aaad24ac 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -202,7 +202,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);
@@ -218,7 +219,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);
 }
 
 /*
@@ -678,6 +680,7 @@ ExecDelete(ModifyTableState *mtstate,
 	HTSU_Result result;
 	HeapUpdateFailureData hufd;
 	TupleTableSlot *slot = NULL;
+	TableTuple	tuple;
 
 	/*
 	 * get information on the (current) result relation
@@ -760,6 +763,35 @@ ldelete:;
 								true /* wait for commit */ ,
 								NULL,
 								&hufd);
+
+		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);
+
+			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:
@@ -805,23 +837,16 @@ ldelete:;
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("could not serialize access 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;
-					}
-				}
+				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("could not serialize access due to concurrent delete")));
 				/* tuple already deleted; nothing to do */
 				return NULL;
 
@@ -1060,6 +1085,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);
+
+			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:
@@ -1104,25 +1160,16 @@ lreplace:;
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("could not serialize access 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;
-					}
-				}
+				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("could not serialize access due to concurrent delete")));
 				/* tuple already deleted; nothing to do */
 				return NULL;
 
@@ -1191,8 +1238,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);
@@ -1203,8 +1250,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:
@@ -1261,8 +1312,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;
 
@@ -1291,10 +1349,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
@@ -1309,8 +1367,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 */
@@ -1356,8 +1412,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 354ac62fb1..2bfc50ec7d 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 54f7c41108..cec2a49002 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 9e43db0259..f9d6190f9d 100644
--- a/src/include/access/tableamapi.h
+++ b/src/include/access/tableamapi.h
@@ -60,12 +60,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 00cf705c05..00b86589d9 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -199,11 +199,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;
-- 
2.15.0.windows.1



  [application/octet-stream] 0012-Table-AM-shared-memory-API.patch (6.5K, ../../CAJrrPGdY_hgvu06R_vCibUktKRLp=b8nBfeZkh=KRc95tq4euA@mail.gmail.com/5-0012-Table-AM-shared-memory-API.patch)
  download | inline diff:
From 8051be7f253b920b79e0279688245e0ac437a0a3 Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Tue, 9 Jan 2018 22:33:01 +1100
Subject: [PATCH 12/12] Table AM shared memory API

Added API to provide needed shared memory for
table AM. As of now only the syncscan infrastructure
uses the shared memory, it can enhanced for other
storages.

And also all the sync scan exposed API usage is
removed outside heap.
---
 src/backend/access/heap/heapam_handler.c | 12 ++++++++++++
 src/backend/access/table/tableam.c       |  7 +++++++
 src/backend/executor/nodeSamplescan.c    |  2 +-
 src/backend/storage/ipc/ipci.c           |  4 ++--
 src/include/access/heapam.h              |  4 ++++
 src/include/access/tableam.h             |  1 +
 src/include/access/tableamapi.h          |  4 ++--
 7 files changed, 29 insertions(+), 5 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 49a295951e..7edbb55678 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -503,6 +503,17 @@ heapam_fetch_tuple_from_offset(TableScanDesc sscan, BlockNumber blkno, OffsetNum
 	return &(scan->rs_ctup);
 }
 
+Size
+heapam_storage_shmem_size()
+{
+	return SyncScanShmemSize();
+}
+
+void
+heapam_storage_shmem_init()
+{
+	return SyncScanShmemInit();
+}
 
 Datum
 heap_tableam_handler(PG_FUNCTION_ARGS)
@@ -537,6 +548,7 @@ heap_tableam_handler(PG_FUNCTION_ARGS)
 	 * BitmapHeap and Sample Scans
 	 */
 	amroutine->scan_get_heappagescandesc = heapam_get_heappagescandesc;
+	amroutine->sync_scan_report_location = ss_report_location;
 
 	amroutine->tuple_fetch = heapam_fetch;
 	amroutine->tuple_insert = heapam_heap_insert;
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index a25812bd1c..46badb5ab4 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -20,6 +20,7 @@
 #include "utils/rel.h"
 #include "utils/tqual.h"
 
+
 /*
  *	table_fetch		- retrieve tuple with given tid
  */
@@ -89,6 +90,12 @@ tableam_get_heappagescandesc(TableScanDesc sscan)
 	return sscan->rs_rd->rd_tableamroutine->scan_get_heappagescandesc(sscan);
 }
 
+void
+table_syncscan_report_location(Relation rel, BlockNumber location)
+{
+	return rel->rd_tableamroutine->sync_scan_report_location(rel, location);
+}
+
 /*
  * heap_setscanlimits - restrict range of a heapscan
  *
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index e171b5b13d..e684c7d77c 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -512,7 +512,7 @@ tablesample_getnext(SampleScanState *scanstate)
 			 * We don't guarantee any specific ordering in general, though.
 			 */
 			if (pagescan->rs_syncscan)
-				ss_report_location(scan->rs_rd, blockno);
+				table_syncscan_report_location(scan->rs_rd, blockno);
 
 			finished = (blockno == pagescan->rs_startblock);
 		}
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 0c86a581c0..9f9c6618c9 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -147,7 +147,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port)
 		size = add_size(size, ApplyLauncherShmemSize());
 		size = add_size(size, SnapMgrShmemSize());
 		size = add_size(size, BTreeShmemSize());
-		size = add_size(size, SyncScanShmemSize());
+		size = add_size(size, heapam_storage_shmem_size());
 		size = add_size(size, AsyncShmemSize());
 		size = add_size(size, BackendRandomShmemSize());
 #ifdef EXEC_BACKEND
@@ -267,7 +267,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port)
 	 */
 	SnapMgrInit();
 	BTreeShmemInit();
-	SyncScanShmemInit();
+	heapam_storage_shmem_init();
 	AsyncShmemInit();
 	BackendRandomShmemInit();
 
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 2bfc50ec7d..0c21ef1f54 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -222,4 +222,8 @@ extern void rewrite_heap_tuple(RewriteState state, HeapTuple oldTuple,
 				   HeapTuple newTuple);
 extern bool rewrite_heap_dead_tuple(RewriteState state, HeapTuple oldTuple);
 
+/* in heap/heapam_storage.c */
+extern Size heapam_storage_shmem_size(void);
+extern void heapam_storage_shmem_init(void);
+
 #endif							/* HEAPAM_H */
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index cec2a49002..a9f5465c6e 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -45,6 +45,7 @@ typedef void (*DeleteIndexTuples) (Relation rel, ItemPointer tid, TransactionId
 extern TableScanDesc table_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan);
 extern ParallelHeapScanDesc tableam_get_parallelheapscandesc(TableScanDesc sscan);
 extern HeapPageScanDesc tableam_get_heappagescandesc(TableScanDesc sscan);
+extern void table_syncscan_report_location(Relation rel, BlockNumber location);
 extern void table_setscanlimits(TableScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
 extern TableScanDesc table_beginscan(Relation relation, Snapshot snapshot,
 				  int nkeys, ScanKey key);
diff --git a/src/include/access/tableamapi.h b/src/include/access/tableamapi.h
index f9d6190f9d..19bfcb930e 100644
--- a/src/include/access/tableamapi.h
+++ b/src/include/access/tableamapi.h
@@ -106,7 +106,7 @@ typedef TableScanDesc (*ScanBegin_function) (Relation relation,
 
 typedef ParallelHeapScanDesc (*ScanGetParallelheapscandesc_function) (TableScanDesc scan);
 typedef HeapPageScanDesc(*ScanGetHeappagescandesc_function) (TableScanDesc scan);
-
+typedef void (*SyncScanReportLocation_function) (Relation rel, BlockNumber location);
 typedef void (*ScanSetlimits_function) (TableScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
 
 /* must return a TupleTableSlot? */
@@ -131,7 +131,6 @@ typedef bool (*HotSearchBuffer_function) (ItemPointer tid, Relation relation,
 										  Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
 										  bool *all_dead, bool first_call);
 
-
 /*
  * API struct for a table AM.  Note this must be stored in a single palloc'd
  * chunk of memory.
@@ -178,6 +177,7 @@ typedef struct TableAmRoutine
 	ScanBegin_function scan_begin;
 	ScanGetParallelheapscandesc_function scan_get_parallelheapscandesc;
 	ScanGetHeappagescandesc_function scan_get_heappagescandesc;
+	SyncScanReportLocation_function sync_scan_report_location;
 	ScanSetlimits_function scansetlimits;
 	ScanGetnext_function scan_getnext;
 	ScanGetnextSlot_function scan_getnextslot;
-- 
2.15.0.windows.1



  [application/octet-stream] 0001-Change-Create-Access-method-to-include-table-handler.patch (9.3K, ../../CAJrrPGdY_hgvu06R_vCibUktKRLp=b8nBfeZkh=KRc95tq4euA@mail.gmail.com/6-0001-Change-Create-Access-method-to-include-table-handler.patch)
  download | inline diff:
From ab02ba63031a6233860737c29bb66eeb0066f9eb Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Tue, 29 Aug 2017 19:45:30 +1000
Subject: [PATCH 01/12] Change Create Access method to include table handler

Add the support of table access method.
---
 src/backend/commands/amcmds.c            | 17 ++++++++++++++---
 src/backend/parser/gram.y                | 11 +++++++++--
 src/backend/utils/adt/pseudotypes.c      |  1 +
 src/include/catalog/pg_am.h              |  1 +
 src/include/catalog/pg_proc.h            |  4 ++++
 src/include/catalog/pg_type.h            |  2 ++
 src/test/regress/expected/opr_sanity.out | 19 ++++++++++++++++---
 src/test/regress/sql/opr_sanity.sql      | 16 +++++++++++++---
 8 files changed, 60 insertions(+), 11 deletions(-)

diff --git a/src/backend/commands/amcmds.c b/src/backend/commands/amcmds.c
index f2173450ad..00563b9b73 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -29,7 +29,7 @@
 #include "utils/syscache.h"
 
 
-static Oid	lookup_index_am_handler_func(List *handler_name, char amtype);
+static Oid	lookup_am_handler_func(List *handler_name, char amtype);
 static const char *get_am_type_string(char amtype);
 
 
@@ -72,7 +72,7 @@ CreateAccessMethod(CreateAmStmt *stmt)
 	/*
 	 * Get the handler function oid, verifying the AM type while at it.
 	 */
-	amhandler = lookup_index_am_handler_func(stmt->handler_name, stmt->amtype);
+	amhandler = lookup_am_handler_func(stmt->handler_name, stmt->amtype);
 
 	/*
 	 * Insert tuple into pg_am.
@@ -225,6 +225,8 @@ get_am_type_string(char amtype)
 	{
 		case AMTYPE_INDEX:
 			return "INDEX";
+		case AMTYPE_TABLE:
+			return "TABLE";
 		default:
 			/* shouldn't happen */
 			elog(ERROR, "invalid access method type '%c'", amtype);
@@ -239,7 +241,7 @@ get_am_type_string(char amtype)
  * This function either return valid function Oid or throw an error.
  */
 static Oid
-lookup_index_am_handler_func(List *handler_name, char amtype)
+lookup_am_handler_func(List *handler_name, char amtype)
 {
 	Oid			handlerOid;
 	static const Oid funcargtypes[1] = {INTERNALOID};
@@ -263,6 +265,15 @@ lookup_index_am_handler_func(List *handler_name, char amtype)
 								NameListToString(handler_name),
 								"index_am_handler")));
 			break;
+			/* XXX refactor duplicate error */
+		case AMTYPE_TABLE:
+			if (get_func_rettype(handlerOid) != TABLE_AM_HANDLEROID)
+				ereport(ERROR,
+						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+						 errmsg("function %s must return type %s",
+								NameListToString(handler_name),
+								"storage_am_handler")));
+			break;
 		default:
 			elog(ERROR, "unrecognized access method type \"%c\"", amtype);
 	}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 16923e853a..13c3d8c8df 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -321,6 +321,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		OptSchemaName
 %type <list>	OptSchemaEltList
 
+%type <chr>		am_type
+
 %type <boolean> TriggerForSpec TriggerForType
 %type <ival>	TriggerActionTime
 %type <list>	TriggerEvents TriggerOneEvent
@@ -5279,16 +5281,21 @@ row_security_cmd:
  *
  *****************************************************************************/
 
-CreateAmStmt: CREATE ACCESS METHOD name TYPE_P INDEX HANDLER handler_name
+CreateAmStmt: CREATE ACCESS METHOD name TYPE_P am_type HANDLER handler_name
 				{
 					CreateAmStmt *n = makeNode(CreateAmStmt);
 					n->amname = $4;
 					n->handler_name = $8;
-					n->amtype = AMTYPE_INDEX;
+					n->amtype = $6;
 					$$ = (Node *) n;
 				}
 		;
 
+am_type:
+			INDEX			{ $$ = AMTYPE_INDEX; }
+		|	TABLE			{ $$ = AMTYPE_TABLE; }
+		;
+
 /*****************************************************************************
  *
  *		QUERIES :
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index dbe67cdb4c..89aac13c80 100644
--- a/src/backend/utils/adt/pseudotypes.c
+++ b/src/backend/utils/adt/pseudotypes.c
@@ -418,3 +418,4 @@ PSEUDOTYPE_DUMMY_IO_FUNCS(internal);
 PSEUDOTYPE_DUMMY_IO_FUNCS(opaque);
 PSEUDOTYPE_DUMMY_IO_FUNCS(anyelement);
 PSEUDOTYPE_DUMMY_IO_FUNCS(anynonarray);
+PSEUDOTYPE_DUMMY_IO_FUNCS(table_am_handler);
diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h
index 2e785c4cec..38a08ba5b6 100644
--- a/src/include/catalog/pg_am.h
+++ b/src/include/catalog/pg_am.h
@@ -59,6 +59,7 @@ typedef FormData_pg_am *Form_pg_am;
  * ----------------
  */
 #define AMTYPE_INDEX					'i' /* index access method */
+#define AMTYPE_TABLE					't' /* table access method */
 
 /* ----------------
  *		initial contents of pg_am
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 298e0ae2f0..e07e768524 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3877,6 +3877,10 @@ DATA(insert OID = 326  (  index_am_handler_in	PGNSP PGUID 12 1 0 0 0 f f f f f f
 DESCR("I/O");
 DATA(insert OID = 327  (  index_am_handler_out	PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2275 "325" _null_ _null_ _null_ _null_ _null_ index_am_handler_out _null_ _null_ _null_ ));
 DESCR("I/O");
+DATA(insert OID = 3425  (  table_am_handler_in	PGNSP PGUID 12 1 0 0 0 f f f f f f i s 1 0 3998 "2275" _null_ _null_ _null_ _null_ _null_ table_am_handler_in _null_ _null_ _null_ ));
+DESCR("I/O");
+DATA(insert OID = 3426  (  table_am_handler_out	PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2275 "3998" _null_ _null_ _null_ _null_ _null_ table_am_handler_out _null_ _null_ _null_ ));
+DESCR("I/O");
 DATA(insert OID = 3311 (  tsm_handler_in	PGNSP PGUID 12 1 0 0 0 f f f f f f i s 1 0 3310 "2275" _null_ _null_ _null_ _null_ _null_ tsm_handler_in _null_ _null_ _null_ ));
 DESCR("I/O");
 DATA(insert OID = 3312 (  tsm_handler_out	PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2275 "3310" _null_ _null_ _null_ _null_ _null_ tsm_handler_out _null_ _null_ _null_ ));
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 5b5b1218de..7e8f56ef41 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -708,6 +708,8 @@ DATA(insert OID = 3115 ( fdw_handler	PGNSP PGUID  4 t p P f t \054 0 0 0 fdw_han
 #define FDW_HANDLEROID	3115
 DATA(insert OID = 325 ( index_am_handler	PGNSP PGUID  4 t p P f t \054 0 0 0 index_am_handler_in index_am_handler_out - - - - - i p f 0 -1 0 0 _null_ _null_ _null_ ));
 #define INDEX_AM_HANDLEROID 325
+DATA(insert OID = 3998 ( table_am_handler	PGNSP PGUID 4 t p P f t \054 0 0 0 table_am_handler_in table_am_handler_out - - - - - i p f 0 -1 0 0 _null_ _null_ _null_ ));
+#define TABLE_AM_HANDLEROID	3998
 DATA(insert OID = 3310 ( tsm_handler	PGNSP PGUID  4 t p P f t \054 0 0 0 tsm_handler_in tsm_handler_out - - - - - i p f 0 -1 0 0 _null_ _null_ _null_ ));
 #define TSM_HANDLEROID	3310
 DATA(insert OID = 3831 ( anyrange		PGNSP PGUID  -1 f p P f t \054 0 0 0 anyrange_in anyrange_out - - - - - d x f 0 -1 0 0 _null_ _null_ _null_ ));
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 684f7f20a8..06a7fdf42e 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1713,11 +1713,24 @@ WHERE p1.amhandler = 0;
 -----+--------
 (0 rows)
 
--- Check for amhandler functions with the wrong signature
+-- Check for index amhandler functions with the wrong signature
 SELECT p1.oid, p1.amname, p2.oid, p2.proname
 FROM pg_am AS p1, pg_proc AS p2
-WHERE p2.oid = p1.amhandler AND
-    (p2.prorettype != 'index_am_handler'::regtype OR p2.proretset
+WHERE p2.oid = p1.amhandler AND p1.amtype = 'i' AND
+    (p2.prorettype != 'index_am_handler'::regtype
+     OR p2.proretset
+     OR p2.pronargs != 1
+     OR p2.proargtypes[0] != 'internal'::regtype);
+ oid | amname | oid | proname 
+-----+--------+-----+---------
+(0 rows)
+
+-- Check for table amhandler functions with the wrong signature
+SELECT p1.oid, p1.amname, p2.oid, p2.proname
+FROM pg_am AS p1, pg_proc AS p2
+WHERE p2.oid = p1.amhandler AND p1.amtype = 's' AND
+    (p2.prorettype != 'table_am_handler'::regtype
+     OR p2.proretset
      OR p2.pronargs != 1
      OR p2.proargtypes[0] != 'internal'::regtype);
  oid | amname | oid | proname 
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index e8fdf8454d..830963cfb1 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -1155,15 +1155,25 @@ SELECT p1.oid, p1.amname
 FROM pg_am AS p1
 WHERE p1.amhandler = 0;
 
--- Check for amhandler functions with the wrong signature
+-- Check for index amhandler functions with the wrong signature
 
 SELECT p1.oid, p1.amname, p2.oid, p2.proname
 FROM pg_am AS p1, pg_proc AS p2
-WHERE p2.oid = p1.amhandler AND
-    (p2.prorettype != 'index_am_handler'::regtype OR p2.proretset
+WHERE p2.oid = p1.amhandler AND p1.amtype = 'i' AND
+    (p2.prorettype != 'index_am_handler'::regtype
+     OR p2.proretset
      OR p2.pronargs != 1
      OR p2.proargtypes[0] != 'internal'::regtype);
 
+-- Check for table amhandler functions with the wrong signature
+
+SELECT p1.oid, p1.amname, p2.oid, p2.proname
+FROM pg_am AS p1, pg_proc AS p2
+WHERE p2.oid = p1.amhandler AND p1.amtype = 's' AND
+    (p2.prorettype != 'table_am_handler'::regtype
+     OR p2.proretset
+     OR p2.pronargs != 1
+     OR p2.proargtypes[0] != 'internal'::regtype);
 
 -- **************** pg_amop ****************
 
-- 
2.15.0.windows.1



  [application/octet-stream] 0002-Table-AM-API-init-functions.patch (10.0K, ../../CAJrrPGdY_hgvu06R_vCibUktKRLp=b8nBfeZkh=KRc95tq4euA@mail.gmail.com/7-0002-Table-AM-API-init-functions.patch)
  download | inline diff:
From 55d055bcd61d0ecfc5fc8aee39ab2380ed934f51 Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Wed, 30 Aug 2017 12:41:15 +1000
Subject: [PATCH 02/12] Table AM API init functions

---
 src/backend/access/Makefile              |   2 +-
 src/backend/access/heap/Makefile         |   3 +-
 src/backend/access/heap/heapam_handler.c |  33 ++++++++++
 src/backend/access/table/Makefile        |  17 +++++
 src/backend/access/table/tableamapi.c    | 103 +++++++++++++++++++++++++++++++
 src/include/access/tableamapi.h          |  39 ++++++++++++
 src/include/catalog/pg_am.h              |   3 +
 src/include/catalog/pg_proc.h            |   5 ++
 src/include/nodes/nodes.h                |   1 +
 9 files changed, 204 insertions(+), 2 deletions(-)
 create mode 100644 src/backend/access/heap/heapam_handler.c
 create mode 100644 src/backend/access/table/Makefile
 create mode 100644 src/backend/access/table/tableamapi.c
 create mode 100644 src/include/access/tableamapi.h

diff --git a/src/backend/access/Makefile b/src/backend/access/Makefile
index bd93a6a8d1..0880e0a8bb 100644
--- a/src/backend/access/Makefile
+++ b/src/backend/access/Makefile
@@ -9,6 +9,6 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 SUBDIRS	    = brin common gin gist hash heap index nbtree rmgrdesc spgist \
-			  tablesample transam
+			  table tablesample transam
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/heap/Makefile b/src/backend/access/heap/Makefile
index b83d496bcd..87bea410f9 100644
--- a/src/backend/access/heap/Makefile
+++ b/src/backend/access/heap/Makefile
@@ -12,6 +12,7 @@ subdir = src/backend/access/heap
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = heapam.o hio.o pruneheap.o rewriteheap.o syncscan.o tuptoaster.o visibilitymap.o
+OBJS = heapam.o heapam_handler.o hio.o pruneheap.o rewriteheap.o \
+	syncscan.o tuptoaster.o visibilitymap.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
new file mode 100644
index 0000000000..6d4323152e
--- /dev/null
+++ b/src/backend/access/heap/heapam_handler.c
@@ -0,0 +1,33 @@
+/*-------------------------------------------------------------------------
+ *
+ * heapam_handler.c
+ *	  heap table access method code
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/heap/heapam_handler.c
+ *
+ *
+ * NOTES
+ *	  This file contains the heap_ routines which implement
+ *	  the POSTGRES heap table access method used for all POSTGRES
+ *	  relations.
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/tableamapi.h"
+#include "utils/builtins.h"
+
+
+Datum
+heap_tableam_handler(PG_FUNCTION_ARGS)
+{
+	TableAmRoutine *amroutine = makeNode(TableAmRoutine);
+
+	PG_RETURN_POINTER(amroutine);
+}
diff --git a/src/backend/access/table/Makefile b/src/backend/access/table/Makefile
new file mode 100644
index 0000000000..496b7387c6
--- /dev/null
+++ b/src/backend/access/table/Makefile
@@ -0,0 +1,17 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+#    Makefile for access/table
+#
+# IDENTIFICATION
+#    src/backend/access/table/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/access/table
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = tableamapi.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/table/tableamapi.c b/src/backend/access/table/tableamapi.c
new file mode 100644
index 0000000000..f94660e306
--- /dev/null
+++ b/src/backend/access/table/tableamapi.c
@@ -0,0 +1,103 @@
+/*----------------------------------------------------------------------
+ *
+ * tableamapi.c
+ *		Support routines for API for Postgres table access methods
+ *
+ * FIXME: looks like this should be in amapi.c.
+ *
+ * Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * src/backend/access/table/tableamapi.c
+ *----------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/tableamapi.h"
+#include "catalog/pg_am.h"
+#include "catalog/pg_proc.h"
+#include "utils/syscache.h"
+#include "utils/memutils.h"
+
+
+/*
+ * GetTableAmRoutine
+ *		Call the specified access method handler routine to get its
+ *		TableAmRoutine struct, which will be palloc'd in the caller's
+ *		memory context.
+ */
+TableAmRoutine *
+GetTableAmRoutine(Oid amhandler)
+{
+	Datum		datum;
+	TableAmRoutine *routine;
+
+	datum = OidFunctionCall0(amhandler);
+	routine = (TableAmRoutine *) DatumGetPointer(datum);
+
+	if (routine == NULL || !IsA(routine, TableAmRoutine))
+		elog(ERROR, "Table access method handler %u did not return a TableAmRoutine struct",
+			 amhandler);
+
+	return routine;
+}
+
+/* A crock */
+TableAmRoutine *
+GetHeapamTableAmRoutine(void)
+{
+	Datum		datum;
+	static TableAmRoutine * HeapTableAmRoutine = NULL;
+
+	if (HeapTableAmRoutine == NULL)
+	{
+		MemoryContext oldcxt;
+
+		oldcxt = MemoryContextSwitchTo(TopMemoryContext);
+		datum = OidFunctionCall0(HEAP_TABLE_AM_HANDLER_OID);
+		HeapTableAmRoutine = (TableAmRoutine *) DatumGetPointer(datum);
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	return HeapTableAmRoutine;
+}
+
+/*
+ * GetTableAmRoutineByAmId - look up the handler of the table access
+ * method with the given OID, and get its TableAmRoutine struct.
+ */
+TableAmRoutine *
+GetTableAmRoutineByAmId(Oid amoid)
+{
+	regproc		amhandler;
+	HeapTuple	tuple;
+	Form_pg_am	amform;
+
+	/* Get handler function OID for the access method */
+	tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(amoid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for access method %u",
+			 amoid);
+	amform = (Form_pg_am) GETSTRUCT(tuple);
+
+	/* Check that it is a table access method */
+	if (amform->amtype != AMTYPE_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("access method \"%s\" is not of type %s",
+						NameStr(amform->amname), "TABLE")));
+
+	amhandler = amform->amhandler;
+
+	/* Complain if handler OID is invalid */
+	if (!RegProcedureIsValid(amhandler))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("table access method \"%s\" does not have a handler",
+						NameStr(amform->amname))));
+
+	ReleaseSysCache(tuple);
+
+	/* And finally, call the handler function to get the API struct. */
+	return GetTableAmRoutine(amhandler);
+}
diff --git a/src/include/access/tableamapi.h b/src/include/access/tableamapi.h
new file mode 100644
index 0000000000..55ddad68fb
--- /dev/null
+++ b/src/include/access/tableamapi.h
@@ -0,0 +1,39 @@
+/*---------------------------------------------------------------------
+ *
+ * tableamapi.h
+ *		API for Postgres table access methods
+ *
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ *
+ * src/include/access/tableamapi.h
+ *---------------------------------------------------------------------
+ */
+#ifndef TABLEEAMAPI_H
+#define TABLEEAMAPI_H
+
+#include "nodes/nodes.h"
+#include "fmgr.h"
+
+/* A physical tuple coming from a table AM scan */
+typedef void *TableTuple;
+
+/*
+ * API struct for a table AM.  Note this must be stored in a single palloc'd
+ * chunk of memory.
+ *
+ * XXX currently all functions are together in a single struct.  Would it be
+ * worthwhile to split the slot-accessor functions to a different struct?
+ * That way, MinimalTuple could be handled without a complete TableAmRoutine
+ * for them -- it'd only have a few functions in TupleTableSlotAmRoutine or so.
+ */
+typedef struct TableAmRoutine
+{
+	NodeTag		type;
+
+}			TableAmRoutine;
+
+extern TableAmRoutine * GetTableAmRoutine(Oid amhandler);
+extern TableAmRoutine * GetTableAmRoutineByAmId(Oid amoid);
+extern TableAmRoutine * GetHeapamTableAmRoutine(void);
+
+#endif							/* TABLEEAMAPI_H */
diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h
index 38a08ba5b6..127973dc84 100644
--- a/src/include/catalog/pg_am.h
+++ b/src/include/catalog/pg_am.h
@@ -84,5 +84,8 @@ DESCR("SP-GiST index access method");
 DATA(insert OID = 3580 (  brin		brinhandler i ));
 DESCR("block range index (BRIN) access method");
 #define BRIN_AM_OID 3580
+DATA(insert OID = 4001 (  heap_tableam         heap_tableam_handler s ));
+DESCR("heap table access method");
+#define HEAP_TABLE_AM_OID 4001
 
 #endif							/* PG_AM_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index e07e768524..a275b7bc8d 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -558,6 +558,11 @@ DESCR("convert int4 to float4");
 DATA(insert OID = 319 (  int4			   PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1  0 23 "700" _null_ _null_ _null_ _null_ _null_	ftoi4 _null_ _null_ _null_ ));
 DESCR("convert float4 to int4");
 
+/* heap table access method handler */
+DATA(insert OID = 4002 (  heap_tableam_handler		PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 3998 "2281" _null_ _null_ _null_ _null_ _null_	heap_tableam_handler _null_ _null_ _null_ ));
+DESCR("row-oriented heap table access method handler");
+#define HEAP_TABLE_AM_HANDLER_OID	4002
+
 /* Index access method handlers */
 DATA(insert OID = 330 (  bthandler		PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 325 "2281" _null_ _null_ _null_ _null_ _null_	bthandler _null_ _null_ _null_ ));
 DESCR("btree index access method handler");
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 2eb3d6d371..7849ee64da 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -499,6 +499,7 @@ typedef enum NodeTag
 	T_InlineCodeBlock,			/* in nodes/parsenodes.h */
 	T_FdwRoutine,				/* in foreign/fdwapi.h */
 	T_IndexAmRoutine,			/* in access/amapi.h */
+	T_TableAmRoutine,			/* in access/tableamapi.h */
 	T_TsmRoutine,				/* in access/tsmapi.h */
 	T_ForeignKeyCacheInfo		/* in utils/rel.h */
 } NodeTag;
-- 
2.15.0.windows.1



  [application/octet-stream] 0003-Adding-tableam-hanlder-to-relation-structure.patch (7.0K, ../../CAJrrPGdY_hgvu06R_vCibUktKRLp=b8nBfeZkh=KRc95tq4euA@mail.gmail.com/8-0003-Adding-tableam-hanlder-to-relation-structure.patch)
  download | inline diff:
From 956b15b97fc113ce902a4fe1bd0bbacca11e762f Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Wed, 30 Aug 2017 12:49:46 +1000
Subject: [PATCH 03/12] Adding tableam hanlder to relation structure

And also the necessary functions to initialize
the tableam handler
---
 src/backend/utils/cache/relcache.c | 119 ++++++++++++++++++++++++++++++++++++-
 src/include/utils/rel.h            |  12 ++++
 src/include/utils/relcache.h       |   2 +
 3 files changed, 130 insertions(+), 3 deletions(-)

diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 28a4483434..8578ea9c36 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -36,6 +36,7 @@
 #include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/sysattr.h"
+#include "access/tableamapi.h"
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/catalog.h"
@@ -1325,10 +1326,27 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	}
 
 	/*
-	 * if it's an index, initialize index-related information
+	 * initialize access method information
 	 */
-	if (OidIsValid(relation->rd_rel->relam))
-		RelationInitIndexAccessInfo(relation);
+	switch (relation->rd_rel->relkind)
+	{
+		case RELKIND_INDEX:
+			Assert(relation->rd_rel->relkind != InvalidOid);
+			RelationInitIndexAccessInfo(relation);
+			break;
+		case RELKIND_RELATION:
+		case RELKIND_SEQUENCE:
+		case RELKIND_TOASTVALUE:
+		case RELKIND_VIEW:		/* Not exactly the storage, but underlying
+								 * tuple access, it is required */
+		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
+			RelationInitTableAccessMethod(relation);
+			break;
+		default:
+			/* nothing to do in other cases */
+			break;
+	}
 
 	/* extract reloptions if any */
 	RelationParseRelOptions(relation, pg_class_tuple);
@@ -1826,6 +1844,71 @@ LookupOpclassInfo(Oid operatorClassOid,
 	return opcentry;
 }
 
+/*
+ * Fill in the TableAmRoutine for a relation
+ *
+ * relation's rd_tableamhandler must be valid already.
+ */
+static void
+InitTableAmRoutine(Relation relation)
+{
+	TableAmRoutine *cached;
+	TableAmRoutine *tmp;
+
+	/*
+	 * Call the tableamhandler in current, short-lived memory context, just in case
+	 * it leaks anything (it probably won't, but let's be paranoid).
+	 */
+	tmp = GetTableAmRoutine(relation->rd_tableamhandler);
+
+	/* XXX do we need a separate memory context for this? */
+	/* OK, now transfer the data into cache context */
+	cached = (TableAmRoutine *) MemoryContextAlloc(CacheMemoryContext,
+													 sizeof(TableAmRoutine));
+	memcpy(cached, tmp, sizeof(TableAmRoutine));
+	relation->rd_tableamroutine = cached;
+
+	pfree(tmp);
+}
+
+/*
+ * Initialize table-access-method support data for a heap relation
+ */
+void
+RelationInitTableAccessMethod(Relation relation)
+{
+	HeapTuple	tuple;
+	Form_pg_am	aform;
+
+	/*
+	 * Relations that don't have a catalogued table access method use the
+	 * standard heap tableam module; otherwise a catalog lookup is in order.
+	 */
+	if (!OidIsValid(relation->rd_rel->relam))
+	{
+		relation->rd_tableamhandler = HEAP_TABLE_AM_HANDLER_OID;
+	}
+	else
+	{
+		/*
+		 * Look up the table access method, save the OID of its handler
+		 * function.
+		 */
+		tuple = SearchSysCache1(AMOID,
+								ObjectIdGetDatum(relation->rd_rel->relam));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for access method %u",
+				 relation->rd_rel->relam);
+		aform = (Form_pg_am) GETSTRUCT(tuple);
+		relation->rd_tableamhandler = aform->amhandler;
+		ReleaseSysCache(tuple);
+	}
+
+	/*
+	 * Now we can fetch the table AM's API struct
+	 */
+	InitTableAmRoutine(relation);
+}
 
 /*
  *		formrdesc
@@ -1984,6 +2067,11 @@ formrdesc(const char *relationName, Oid relationReltype,
 	 */
 	RelationInitPhysicalAddr(relation);
 
+	/*
+	 * initialize the table am handler
+	 */
+	relation->rd_tableamroutine = GetHeapamTableAmRoutine();
+
 	/*
 	 * initialize the rel-has-index flag, using hardwired knowledge
 	 */
@@ -2312,6 +2400,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		pfree(relation->rd_pubactions);
 	if (relation->rd_options)
 		pfree(relation->rd_options);
+	if (relation->rd_tableamroutine)
+		pfree(relation->rd_tableamroutine);
 	if (relation->rd_indextuple)
 		pfree(relation->rd_indextuple);
 	if (relation->rd_indexcxt)
@@ -3326,6 +3416,14 @@ RelationBuildLocalRelation(const char *relname,
 
 	RelationInitPhysicalAddr(rel);
 
+	if (relkind == RELKIND_RELATION ||
+		relkind == RELKIND_MATVIEW ||
+		relkind == RELKIND_VIEW ||	/* Not exactly the storage, but underlying
+									 * tuple access, it is required */
+		relkind == RELKIND_PARTITIONED_TABLE ||
+		relkind == RELKIND_TOASTVALUE)
+		RelationInitTableAccessMethod(rel);
+
 	/*
 	 * Okay to insert into the relcache hash table.
 	 *
@@ -3847,6 +3945,18 @@ RelationCacheInitializePhase3(void)
 			restart = true;
 		}
 
+		if (relation->rd_tableamroutine == NULL &&
+			(relation->rd_rel->relkind == RELKIND_RELATION ||
+			 relation->rd_rel->relkind == RELKIND_MATVIEW ||
+			 relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+			 relation->rd_rel->relkind == RELKIND_TOASTVALUE))
+		{
+			RelationInitTableAccessMethod(relation);
+			Assert(relation->rd_tableamroutine != NULL);
+
+			restart = true;
+		}
+
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -5567,6 +5677,9 @@ load_relcache_init_file(bool shared)
 			if (rel->rd_isnailed)
 				nailed_rels++;
 
+			/* Load table AM stuff */
+			RelationInitTableAccessMethod(rel);
+
 			Assert(rel->rd_index == NULL);
 			Assert(rel->rd_indextuple == NULL);
 			Assert(rel->rd_indexcxt == NULL);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index aa8add544a..64a3b97889 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -160,6 +160,12 @@ typedef struct RelationData
 	/* use "struct" here to avoid needing to include htup.h: */
 	struct HeapTupleData *rd_indextuple;	/* all of pg_index tuple */
 
+	/*
+	 * Underlying table access method support
+	 */
+	Oid			rd_tableamhandler;	/* OID of table AM handler function */
+	struct TableAmRoutine *rd_tableamroutine;	/* table AM's API struct */
+
 	/*
 	 * index access support info (used only for an index relation)
 	 *
@@ -436,6 +442,12 @@ typedef struct ViewOptions
  */
 #define RelationGetDescr(relation) ((relation)->rd_att)
 
+/*
+ * RelationGetTableamRoutine
+ *		Returns the table AM routine for a relation.
+ */
+#define RelationGettableamRoutine(relation) ((relation)->rd_tableamroutine)
+
 /*
  * RelationGetRelationName
  *		Returns the rel's name.
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 8a546aba28..0c500729c0 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -76,6 +76,8 @@ extern void RelationInitIndexAccessInfo(Relation relation);
 struct PublicationActions;
 extern struct PublicationActions *GetRelationPublicationActions(Relation relation);
 
+extern void RelationInitTableAccessMethod(Relation relation);
+
 /*
  * Routines to support ereport() reports of relation-related errors
  */
-- 
2.15.0.windows.1



  [application/octet-stream] 0004-Adding-tuple-visibility-functions-to-table-AM.patch (52.4K, ../../CAJrrPGdY_hgvu06R_vCibUktKRLp=b8nBfeZkh=KRc95tq4euA@mail.gmail.com/9-0004-Adding-tuple-visibility-functions-to-table-AM.patch)
  download | inline diff:
From 94a99d600c2e72b61baaf660d756517c719df94a Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Tue, 9 Jan 2018 16:01:32 +1100
Subject: [PATCH 04/12] Adding tuple visibility functions to table AM

Tuple visibility functions are now part of the
heap table AM routine. The visibilty execution
procedure is changed accoridngly.

The snapshot satifies function is changed to an
enum to represent what type of snapshot is it
and this enum value is used to call the corresponding
visibilty function from the storage AM when the
visibilty of the tuple is required.

The common code is that is part of both server
and pluggable table access methods is now moved
into tableam_common.h file.
---
 contrib/pg_visibility/pg_visibility.c              |  11 +-
 contrib/pgrowlocks/pgrowlocks.c                    |   7 +-
 contrib/pgstattuple/pgstatapprox.c                 |   7 +-
 contrib/pgstattuple/pgstattuple.c                  |   3 +-
 src/backend/access/heap/Makefile                   |   4 +-
 src/backend/access/heap/heapam.c                   |  61 ++++--
 src/backend/access/heap/heapam_handler.c           |   6 +
 .../tqual.c => access/heap/heapam_visibility.c}    | 244 ++++++++++++---------
 src/backend/access/heap/pruneheap.c                |   4 +-
 src/backend/access/index/genam.c                   |   4 +-
 src/backend/catalog/index.c                        |   6 +-
 src/backend/commands/analyze.c                     |   6 +-
 src/backend/commands/cluster.c                     |   3 +-
 src/backend/commands/vacuumlazy.c                  |   4 +-
 src/backend/executor/nodeBitmapHeapscan.c          |   2 +-
 src/backend/executor/nodeModifyTable.c             |   7 +-
 src/backend/executor/nodeSamplescan.c              |   3 +-
 src/backend/replication/logical/snapbuild.c        |   6 +-
 src/backend/storage/lmgr/predicate.c               |   2 +-
 src/backend/utils/adt/ri_triggers.c                |   2 +-
 src/backend/utils/time/Makefile                    |   2 +-
 src/backend/utils/time/snapmgr.c                   |  10 +-
 src/include/access/heapam.h                        |  13 ++
 src/include/access/tableam_common.h                |  41 ++++
 src/include/access/tableamapi.h                    |  15 +-
 src/include/storage/bufmgr.h                       |   5 +-
 src/include/utils/snapshot.h                       |  14 +-
 src/include/utils/tqual.h                          |  54 +----
 28 files changed, 327 insertions(+), 219 deletions(-)
 rename src/backend/{utils/time/tqual.c => access/heap/heapam_visibility.c} (95%)
 create mode 100644 src/include/access/tableam_common.h

diff --git a/contrib/pg_visibility/pg_visibility.c b/contrib/pg_visibility/pg_visibility.c
index 944dea66fc..0102f3d1d7 100644
--- a/contrib/pg_visibility/pg_visibility.c
+++ b/contrib/pg_visibility/pg_visibility.c
@@ -11,6 +11,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/tableamapi.h"
 #include "access/visibilitymap.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage_xlog.h"
@@ -51,7 +52,7 @@ static vbits *collect_visibility_data(Oid relid, bool include_pd);
 static corrupt_items *collect_corrupt_items(Oid relid, bool all_visible,
 					  bool all_frozen);
 static void record_corrupt_item(corrupt_items *items, ItemPointer tid);
-static bool tuple_all_visible(HeapTuple tup, TransactionId OldestXmin,
+static bool tuple_all_visible(Relation rel, HeapTuple tup, TransactionId OldestXmin,
 				  Buffer buffer);
 static void check_relation_relkind(Relation rel);
 
@@ -656,7 +657,7 @@ collect_corrupt_items(Oid relid, bool all_visible, bool all_frozen)
 			 * the tuple to be all-visible.
 			 */
 			if (check_visible &&
-				!tuple_all_visible(&tuple, OldestXmin, buffer))
+				!tuple_all_visible(rel, &tuple, OldestXmin, buffer))
 			{
 				TransactionId RecomputedOldestXmin;
 
@@ -681,7 +682,7 @@ collect_corrupt_items(Oid relid, bool all_visible, bool all_frozen)
 				else
 				{
 					OldestXmin = RecomputedOldestXmin;
-					if (!tuple_all_visible(&tuple, OldestXmin, buffer))
+					if (!tuple_all_visible(rel, &tuple, OldestXmin, buffer))
 						record_corrupt_item(items, &tuple.t_self);
 				}
 			}
@@ -739,12 +740,12 @@ record_corrupt_item(corrupt_items *items, ItemPointer tid)
  * The buffer should contain the tuple and should be locked and pinned.
  */
 static bool
-tuple_all_visible(HeapTuple tup, TransactionId OldestXmin, Buffer buffer)
+tuple_all_visible(Relation rel, HeapTuple tup, TransactionId OldestXmin, Buffer buffer)
 {
 	HTSV_Result state;
 	TransactionId xmin;
 
-	state = HeapTupleSatisfiesVacuum(tup, OldestXmin, buffer);
+	state = rel->rd_tableamroutine->snapshot_satisfiesVacuum(tup, OldestXmin, buffer);
 	if (state != HEAPTUPLE_LIVE)
 		return false;			/* all-visible implies live */
 
diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index eabca65bd2..03710c68c3 100644
--- a/contrib/pgrowlocks/pgrowlocks.c
+++ b/contrib/pgrowlocks/pgrowlocks.c
@@ -26,6 +26,7 @@
 
 #include "access/multixact.h"
 #include "access/relscan.h"
+#include "access/tableamapi.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
@@ -149,9 +150,9 @@ pgrowlocks(PG_FUNCTION_ARGS)
 		/* must hold a buffer lock to call HeapTupleSatisfiesUpdate */
 		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 
-		htsu = HeapTupleSatisfiesUpdate(tuple,
-										GetCurrentCommandId(false),
-										scan->rs_cbuf);
+		htsu = rel->rd_tableamroutine->snapshot_satisfiesUpdate(tuple,
+															 GetCurrentCommandId(false),
+															 scan->rs_cbuf);
 		xmax = HeapTupleHeaderGetRawXmax(tuple->t_data);
 		infomask = tuple->t_data->t_infomask;
 
diff --git a/contrib/pgstattuple/pgstatapprox.c b/contrib/pgstattuple/pgstatapprox.c
index 3cfbc08649..c32097d53c 100644
--- a/contrib/pgstattuple/pgstatapprox.c
+++ b/contrib/pgstattuple/pgstatapprox.c
@@ -12,12 +12,13 @@
  */
 #include "postgres.h"
 
-#include "access/visibilitymap.h"
 #include "access/transam.h"
+#include "access/visibilitymap.h"
 #include "access/xact.h"
 #include "access/multixact.h"
 #include "access/htup_details.h"
 #include "catalog/namespace.h"
+#include "commands/vacuum.h"
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
@@ -26,7 +27,7 @@
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
 #include "utils/tqual.h"
-#include "commands/vacuum.h"
+
 
 PG_FUNCTION_INFO_V1(pgstattuple_approx);
 PG_FUNCTION_INFO_V1(pgstattuple_approx_v1_5);
@@ -156,7 +157,7 @@ statapprox_heap(Relation rel, output_type *stat)
 			 * We count live and dead tuples, but we also need to add up
 			 * others in order to feed vac_estimate_reltuples.
 			 */
-			switch (HeapTupleSatisfiesVacuum(&tuple, OldestXmin, buf))
+			switch (rel->rd_tableamroutine->snapshot_satisfiesVacuum(&tuple, OldestXmin, buf))
 			{
 				case HEAPTUPLE_RECENTLY_DEAD:
 					misc_count++;
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index 7ca1bb24d2..88583b1e57 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -322,6 +322,7 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 	Buffer		buffer;
 	pgstattuple_type stat = {0};
 	SnapshotData SnapshotDirty;
+	TableAmRoutine *method = rel->rd_tableamroutine;
 
 	/* Disable syncscan because we assume we scan from block zero upwards */
 	scan = heap_beginscan_strat(rel, SnapshotAny, 0, NULL, true, false);
@@ -337,7 +338,7 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 		/* must hold a buffer lock to call HeapTupleSatisfiesVisibility */
 		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 
-		if (HeapTupleSatisfiesVisibility(tuple, &SnapshotDirty, scan->rs_cbuf))
+		if (HeapTupleSatisfiesVisibility(method, tuple, &SnapshotDirty, scan->rs_cbuf))
 		{
 			stat.tuple_len += tuple->t_len;
 			stat.tuple_count++;
diff --git a/src/backend/access/heap/Makefile b/src/backend/access/heap/Makefile
index 87bea410f9..297ad9ddc1 100644
--- a/src/backend/access/heap/Makefile
+++ b/src/backend/access/heap/Makefile
@@ -12,7 +12,7 @@ subdir = src/backend/access/heap
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = heapam.o heapam_handler.o hio.o pruneheap.o rewriteheap.o \
-	syncscan.o tuptoaster.o visibilitymap.o
+OBJS = heapam.o heapam_handler.o heapam_visibility.o hio.o pruneheap.o \
+	rewriteheap.o syncscan.o tuptoaster.o visibilitymap.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index dbc8f2d6c7..9fb2ecad25 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -45,6 +45,7 @@
 #include "access/multixact.h"
 #include "access/parallel.h"
 #include "access/relscan.h"
+#include "access/tableamapi.h"
 #include "access/sysattr.h"
 #include "access/transam.h"
 #include "access/tuptoaster.h"
@@ -438,7 +439,7 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 			if (all_visible)
 				valid = true;
 			else
-				valid = HeapTupleSatisfiesVisibility(&loctup, snapshot, buffer);
+				valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_tableamroutine, &loctup, snapshot, buffer);
 
 			CheckForSerializableConflictOut(valid, scan->rs_rd, &loctup,
 											buffer, snapshot);
@@ -653,7 +654,8 @@ heapgettup(HeapScanDesc scan,
 				/*
 				 * if current tuple qualifies, return it.
 				 */
-				valid = HeapTupleSatisfiesVisibility(tuple,
+				valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_tableamroutine,
+													 tuple,
 													 snapshot,
 													 scan->rs_cbuf);
 
@@ -841,6 +843,7 @@ heapgettup_pagemode(HeapScanDesc scan,
 			lineindex = scan->rs_cindex + 1;
 		}
 
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 		dp = BufferGetPage(scan->rs_cbuf);
 		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
 		lines = scan->rs_ntuples;
@@ -885,6 +888,7 @@ heapgettup_pagemode(HeapScanDesc scan,
 			page = scan->rs_cblock; /* current page */
 		}
 
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 		dp = BufferGetPage(scan->rs_cbuf);
 		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
 		lines = scan->rs_ntuples;
@@ -954,23 +958,31 @@ heapgettup_pagemode(HeapScanDesc scan,
 			/*
 			 * if current tuple qualifies, return it.
 			 */
-			if (key != NULL)
+			if (HeapTupleSatisfiesVisibility(scan->rs_rd->rd_tableamroutine, tuple, scan->rs_snapshot, scan->rs_cbuf))
 			{
-				bool		valid;
+				/*
+				 * if current tuple qualifies, return it.
+				 */
+				if (key != NULL)
+				{
+					bool		valid;
 
-				HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
-							nkeys, key, valid);
-				if (valid)
+					HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
+								nkeys, key, valid);
+					if (valid)
+					{
+						scan->rs_cindex = lineindex;
+						LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+						return;
+					}
+				}
+				else
 				{
 					scan->rs_cindex = lineindex;
+					LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
 					return;
 				}
 			}
-			else
-			{
-				scan->rs_cindex = lineindex;
-				return;
-			}
 
 			/*
 			 * otherwise move to the next item on the page
@@ -982,6 +994,12 @@ heapgettup_pagemode(HeapScanDesc scan,
 				++lineindex;
 		}
 
+		/*
+		 * if we get here, it means we've exhausted the items on this page and
+		 * it's time to move to the next.
+		 */
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+
 		/*
 		 * if we get here, it means we've exhausted the items on this page and
 		 * it's time to move to the next.
@@ -1039,6 +1057,7 @@ heapgettup_pagemode(HeapScanDesc scan,
 
 		heapgetpage(scan, page);
 
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 		dp = BufferGetPage(scan->rs_cbuf);
 		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
 		lines = scan->rs_ntuples;
@@ -1831,7 +1850,7 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 
 	pgstat_count_heap_getnext(scan->rs_rd);
 
-	return &(scan->rs_ctup);
+	return heap_copytuple(&(scan->rs_ctup));
 }
 
 /*
@@ -1950,7 +1969,7 @@ heap_fetch(Relation relation,
 	/*
 	 * check time qualification of tuple, then release lock
 	 */
-	valid = HeapTupleSatisfiesVisibility(tuple, snapshot, buffer);
+	valid = HeapTupleSatisfiesVisibility(relation->rd_tableamroutine, tuple, snapshot, buffer);
 
 	if (valid)
 		PredicateLockTuple(relation, tuple, snapshot);
@@ -2097,7 +2116,7 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
 			ItemPointerSet(&(heapTuple->t_self), BufferGetBlockNumber(buffer), offnum);
 
 			/* If it's visible per the snapshot, we must return it */
-			valid = HeapTupleSatisfiesVisibility(heapTuple, snapshot, buffer);
+			valid = HeapTupleSatisfiesVisibility(relation->rd_tableamroutine, heapTuple, snapshot, buffer);
 			CheckForSerializableConflictOut(valid, relation, heapTuple,
 											buffer, snapshot);
 			/* reset to original, non-redirected, tid */
@@ -2271,7 +2290,7 @@ heap_get_latest_tid(Relation relation,
 		 * Check time qualification of tuple; if visible, set it as the new
 		 * result candidate.
 		 */
-		valid = HeapTupleSatisfiesVisibility(&tp, snapshot, buffer);
+		valid = HeapTupleSatisfiesVisibility(relation->rd_tableamroutine, &tp, snapshot, buffer);
 		CheckForSerializableConflictOut(valid, relation, &tp, buffer, snapshot);
 		if (valid)
 			*tid = ctid;
@@ -3097,7 +3116,7 @@ heap_delete(Relation relation, ItemPointer tid,
 	tp.t_self = *tid;
 
 l1:
-	result = HeapTupleSatisfiesUpdate(&tp, cid, buffer);
+	result = relation->rd_tableamroutine->snapshot_satisfiesUpdate(&tp, cid, buffer);
 
 	if (result == HeapTupleInvisible)
 	{
@@ -3208,7 +3227,7 @@ l1:
 	if (crosscheck != InvalidSnapshot && result == HeapTupleMayBeUpdated)
 	{
 		/* Perform additional check for transaction-snapshot mode RI updates */
-		if (!HeapTupleSatisfiesVisibility(&tp, crosscheck, buffer))
+		if (!HeapTupleSatisfiesVisibility(relation->rd_tableamroutine, &tp, crosscheck, buffer))
 			result = HeapTupleUpdated;
 	}
 
@@ -3668,7 +3687,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 l2:
 	checked_lockers = false;
 	locker_remains = false;
-	result = HeapTupleSatisfiesUpdate(&oldtup, cid, buffer);
+	result = relation->rd_tableamroutine->snapshot_satisfiesUpdate(&oldtup, cid, buffer);
 
 	/* see below about the "no wait" case */
 	Assert(result != HeapTupleBeingUpdated || wait);
@@ -3849,7 +3868,7 @@ l2:
 	if (crosscheck != InvalidSnapshot && result == HeapTupleMayBeUpdated)
 	{
 		/* Perform additional check for transaction-snapshot mode RI updates */
-		if (!HeapTupleSatisfiesVisibility(&oldtup, crosscheck, buffer))
+		if (!HeapTupleSatisfiesVisibility(relation->rd_tableamroutine, &oldtup, crosscheck, buffer))
 			result = HeapTupleUpdated;
 	}
 
@@ -4600,7 +4619,7 @@ heap_lock_tuple(Relation relation, HeapTuple tuple,
 	tuple->t_tableOid = RelationGetRelid(relation);
 
 l3:
-	result = HeapTupleSatisfiesUpdate(tuple, cid, *buffer);
+	result = relation->rd_tableamroutine->snapshot_satisfiesUpdate(tuple, cid, *buffer);
 
 	if (result == HeapTupleInvisible)
 	{
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 6d4323152e..61086fe64c 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -20,6 +20,7 @@
  */
 #include "postgres.h"
 
+#include "access/heapam.h"
 #include "access/tableamapi.h"
 #include "utils/builtins.h"
 
@@ -29,5 +30,10 @@ heap_tableam_handler(PG_FUNCTION_ARGS)
 {
 	TableAmRoutine *amroutine = makeNode(TableAmRoutine);
 
+	amroutine->snapshot_satisfies = HeapTupleSatisfies;
+
+	amroutine->snapshot_satisfiesUpdate = HeapTupleSatisfiesUpdate;
+	amroutine->snapshot_satisfiesVacuum = HeapTupleSatisfiesVacuum;
+
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/utils/time/tqual.c b/src/backend/access/heap/heapam_visibility.c
similarity index 95%
rename from src/backend/utils/time/tqual.c
rename to src/backend/access/heap/heapam_visibility.c
index f7c4c9188c..c45575f049 100644
--- a/src/backend/utils/time/tqual.c
+++ b/src/backend/access/heap/heapam_visibility.c
@@ -1,7 +1,6 @@
 /*-------------------------------------------------------------------------
  *
- * tqual.c
- *	  POSTGRES "time qualification" code, ie, tuple visibility rules.
+ * POSTGRES "time qualification" code, ie, tuple visibility rules.
  *
  * NOTE: all the HeapTupleSatisfies routines will update the tuple's
  * "hint" status bits if we see that the inserting or deleting transaction
@@ -56,13 +55,14 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  src/backend/utils/time/tqual.c
+ *	  src/backend/access/heap/heapam_visibilty.c
  *
  *-------------------------------------------------------------------------
  */
 
 #include "postgres.h"
 
+#include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/multixact.h"
 #include "access/subtrans.h"
@@ -76,11 +76,9 @@
 #include "utils/snapmgr.h"
 #include "utils/tqual.h"
 
-
 /* Static variables representing various special snapshot semantics */
-SnapshotData SnapshotSelfData = {HeapTupleSatisfiesSelf};
-SnapshotData SnapshotAnyData = {HeapTupleSatisfiesAny};
-
+SnapshotData SnapshotSelfData = {SELF_VISIBILITY};
+SnapshotData SnapshotAnyData = {ANY_VISIBILITY};
 
 /*
  * SetHintBits()
@@ -172,9 +170,10 @@ HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
  *			(Xmax != my-transaction &&			the row was deleted by another transaction
  *			 Xmax is not committed)))			that has not been committed
  */
-bool
-HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
+static bool
+HeapTupleSatisfiesSelf(TableTuple stup, Snapshot snapshot, Buffer buffer)
 {
+	HeapTuple	htup = (HeapTuple) stup;
 	HeapTupleHeader tuple = htup->t_data;
 
 	Assert(ItemPointerIsValid(&htup->t_self));
@@ -342,8 +341,8 @@ HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
  * HeapTupleSatisfiesAny
  *		Dummy "satisfies" routine: any tuple satisfies SnapshotAny.
  */
-bool
-HeapTupleSatisfiesAny(HeapTuple htup, Snapshot snapshot, Buffer buffer)
+static bool
+HeapTupleSatisfiesAny(TableTuple stup, Snapshot snapshot, Buffer buffer)
 {
 	return true;
 }
@@ -362,10 +361,11 @@ HeapTupleSatisfiesAny(HeapTuple htup, Snapshot snapshot, Buffer buffer)
  * Among other things, this means you can't do UPDATEs of rows in a TOAST
  * table.
  */
-bool
-HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot,
+static bool
+HeapTupleSatisfiesToast(TableTuple stup, Snapshot snapshot,
 						Buffer buffer)
 {
+	HeapTuple	htup = (HeapTuple) stup;
 	HeapTupleHeader tuple = htup->t_data;
 
 	Assert(ItemPointerIsValid(&htup->t_self));
@@ -457,9 +457,10 @@ HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot,
  *	distinguish that case must test for it themselves.)
  */
 HTSU_Result
-HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
+HeapTupleSatisfiesUpdate(TableTuple stup, CommandId curcid,
 						 Buffer buffer)
 {
+	HeapTuple	htup = (HeapTuple) stup;
 	HeapTupleHeader tuple = htup->t_data;
 
 	Assert(ItemPointerIsValid(&htup->t_self));
@@ -735,10 +736,11 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
  * on the insertion without aborting the whole transaction, the associated
  * token is also returned in snapshot->speculativeToken.
  */
-bool
-HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
+static bool
+HeapTupleSatisfiesDirty(TableTuple stup, Snapshot snapshot,
 						Buffer buffer)
 {
+	HeapTuple	htup = (HeapTuple) stup;
 	HeapTupleHeader tuple = htup->t_data;
 
 	Assert(ItemPointerIsValid(&htup->t_self));
@@ -959,10 +961,11 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
  * inserting/deleting transaction was still running --- which was more cycles
  * and more contention on the PGXACT array.
  */
-bool
-HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
+static bool
+HeapTupleSatisfiesMVCC(TableTuple stup, Snapshot snapshot,
 					   Buffer buffer)
 {
+	HeapTuple	htup = (HeapTuple) stup;
 	HeapTupleHeader tuple = htup->t_data;
 
 	Assert(ItemPointerIsValid(&htup->t_self));
@@ -1161,9 +1164,10 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
  * even if we see that the deleting transaction has committed.
  */
 HTSV_Result
-HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
+HeapTupleSatisfiesVacuum(TableTuple stup, TransactionId OldestXmin,
 						 Buffer buffer)
 {
+	HeapTuple	htup = (HeapTuple) stup;
 	HeapTupleHeader tuple = htup->t_data;
 
 	Assert(ItemPointerIsValid(&htup->t_self));
@@ -1383,84 +1387,77 @@ HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
 	return HEAPTUPLE_DEAD;
 }
 
-
 /*
  * HeapTupleSatisfiesNonVacuumable
  *
- *	True if tuple might be visible to some transaction; false if it's
- *	surely dead to everyone, ie, vacuumable.
+ *     True if tuple might be visible to some transaction; false if it's
+ *     surely dead to everyone, ie, vacuumable.
  *
- *	This is an interface to HeapTupleSatisfiesVacuum that meets the
- *	SnapshotSatisfiesFunc API, so it can be used through a Snapshot.
- *	snapshot->xmin must have been set up with the xmin horizon to use.
+ *     This is an interface to HeapTupleSatisfiesVacuum that meets the
+ *     SnapshotSatisfiesFunc API, so it can be used through a Snapshot.
+ *     snapshot->xmin must have been set up with the xmin horizon to use.
  */
-bool
-HeapTupleSatisfiesNonVacuumable(HeapTuple htup, Snapshot snapshot,
+static bool
+HeapTupleSatisfiesNonVacuumable(TableTuple htup, Snapshot snapshot,
 								Buffer buffer)
 {
 	return HeapTupleSatisfiesVacuum(htup, snapshot->xmin, buffer)
 		!= HEAPTUPLE_DEAD;
 }
 
-
 /*
- * HeapTupleIsSurelyDead
+ * Is the tuple really only locked?  That is, is it not updated?
  *
- *	Cheaply determine whether a tuple is surely dead to all onlookers.
- *	We sometimes use this in lieu of HeapTupleSatisfiesVacuum when the
- *	tuple has just been tested by another visibility routine (usually
- *	HeapTupleSatisfiesMVCC) and, therefore, any hint bits that can be set
- *	should already be set.  We assume that if no hint bits are set, the xmin
- *	or xmax transaction is still running.  This is therefore faster than
- *	HeapTupleSatisfiesVacuum, because we don't consult PGXACT nor CLOG.
- *	It's okay to return false when in doubt, but we must return true only
- *	if the tuple is removable.
+ * It's easy to check just infomask bits if the locker is not a multi; but
+ * otherwise we need to verify that the updating transaction has not aborted.
+ *
+ * This function is here because it follows the same time qualification rules
+ * laid out at the top of this file.
  */
 bool
-HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin)
+HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple)
 {
-	HeapTupleHeader tuple = htup->t_data;
+	TransactionId xmax;
 
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
+	/* if there's no valid Xmax, then there's obviously no update either */
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)
+		return true;
 
-	/*
-	 * If the inserting transaction is marked invalid, then it aborted, and
-	 * the tuple is definitely dead.  If it's marked neither committed nor
-	 * invalid, then we assume it's still alive (since the presumption is that
-	 * all relevant hint bits were just set moments ago).
-	 */
-	if (!HeapTupleHeaderXminCommitted(tuple))
-		return HeapTupleHeaderXminInvalid(tuple) ? true : false;
+	if (tuple->t_infomask & HEAP_XMAX_LOCK_ONLY)
+		return true;
 
-	/*
-	 * If the inserting transaction committed, but any deleting transaction
-	 * aborted, the tuple is still alive.
-	 */
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)
-		return false;
+	/* invalid xmax means no update */
+	if (!TransactionIdIsValid(HeapTupleHeaderGetRawXmax(tuple)))
+		return true;
 
 	/*
-	 * If the XMAX is just a lock, the tuple is still alive.
+	 * if HEAP_XMAX_LOCK_ONLY is not set and not a multi, then this must
+	 * necessarily have been updated
 	 */
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+	if (!(tuple->t_infomask & HEAP_XMAX_IS_MULTI))
 		return false;
 
-	/*
-	 * If the Xmax is a MultiXact, it might be dead or alive, but we cannot
-	 * know without checking pg_multixact.
-	 */
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-		return false;
+	/* ... but if it's a multi, then perhaps the updating Xid aborted. */
+	xmax = HeapTupleGetUpdateXid(tuple);
 
-	/* If deleter isn't known to have committed, assume it's still running. */
-	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
+	/* not LOCKED_ONLY, so it has to have an xmax */
+	Assert(TransactionIdIsValid(xmax));
+
+	if (TransactionIdIsCurrentTransactionId(xmax))
+		return false;
+	if (TransactionIdIsInProgress(xmax))
+		return false;
+	if (TransactionIdDidCommit(xmax))
 		return false;
 
-	/* Deleter committed, so tuple is dead if the XID is old enough. */
-	return TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin);
+	/*
+	 * not current, not in progress, not committed -- must have aborted or
+	 * crashed
+	 */
+	return true;
 }
 
+
 /*
  * XidInMVCCSnapshot
  *		Is the given XID still-in-progress according to the snapshot?
@@ -1584,55 +1581,61 @@ XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot)
 }
 
 /*
- * Is the tuple really only locked?  That is, is it not updated?
- *
- * It's easy to check just infomask bits if the locker is not a multi; but
- * otherwise we need to verify that the updating transaction has not aborted.
+ * HeapTupleIsSurelyDead
  *
- * This function is here because it follows the same time qualification rules
- * laid out at the top of this file.
+ *	Cheaply determine whether a tuple is surely dead to all onlookers.
+ *	We sometimes use this in lieu of HeapTupleSatisfiesVacuum when the
+ *	tuple has just been tested by another visibility routine (usually
+ *	HeapTupleSatisfiesMVCC) and, therefore, any hint bits that can be set
+ *	should already be set.  We assume that if no hint bits are set, the xmin
+ *	or xmax transaction is still running.  This is therefore faster than
+ *	HeapTupleSatisfiesVacuum, because we don't consult PGXACT nor CLOG.
+ *	It's okay to return false when in doubt, but we must return TRUE only
+ *	if the tuple is removable.
  */
 bool
-HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple)
+HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin)
 {
-	TransactionId xmax;
-
-	/* if there's no valid Xmax, then there's obviously no update either */
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)
-		return true;
+	HeapTupleHeader tuple = htup->t_data;
 
-	if (tuple->t_infomask & HEAP_XMAX_LOCK_ONLY)
-		return true;
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
 
-	/* invalid xmax means no update */
-	if (!TransactionIdIsValid(HeapTupleHeaderGetRawXmax(tuple)))
-		return true;
+	/*
+	 * If the inserting transaction is marked invalid, then it aborted, and
+	 * the tuple is definitely dead.  If it's marked neither committed nor
+	 * invalid, then we assume it's still alive (since the presumption is that
+	 * all relevant hint bits were just set moments ago).
+	 */
+	if (!HeapTupleHeaderXminCommitted(tuple))
+		return HeapTupleHeaderXminInvalid(tuple) ? true : false;
 
 	/*
-	 * if HEAP_XMAX_LOCK_ONLY is not set and not a multi, then this must
-	 * necessarily have been updated
+	 * If the inserting transaction committed, but any deleting transaction
+	 * aborted, the tuple is still alive.
 	 */
-	if (!(tuple->t_infomask & HEAP_XMAX_IS_MULTI))
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)
 		return false;
 
-	/* ... but if it's a multi, then perhaps the updating Xid aborted. */
-	xmax = HeapTupleGetUpdateXid(tuple);
-
-	/* not LOCKED_ONLY, so it has to have an xmax */
-	Assert(TransactionIdIsValid(xmax));
-
-	if (TransactionIdIsCurrentTransactionId(xmax))
-		return false;
-	if (TransactionIdIsInProgress(xmax))
-		return false;
-	if (TransactionIdDidCommit(xmax))
+	/*
+	 * If the XMAX is just a lock, the tuple is still alive.
+	 */
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
 		return false;
 
 	/*
-	 * not current, not in progress, not committed -- must have aborted or
-	 * crashed
+	 * If the Xmax is a MultiXact, it might be dead or alive, but we cannot
+	 * know without checking pg_multixact.
 	 */
-	return true;
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+		return false;
+
+	/* If deleter isn't known to have committed, assume it's still running. */
+	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
+		return false;
+
+	/* Deleter committed, so tuple is dead if the XID is old enough. */
+	return TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin);
 }
 
 /*
@@ -1659,10 +1662,11 @@ TransactionIdInArray(TransactionId xid, TransactionId *xip, Size num)
  * dangerous to do so as the semantics of doing so during timetravel are more
  * complicated than when dealing "only" with the present.
  */
-bool
-HeapTupleSatisfiesHistoricMVCC(HeapTuple htup, Snapshot snapshot,
+static bool
+HeapTupleSatisfiesHistoricMVCC(TableTuple stup, Snapshot snapshot,
 							   Buffer buffer)
 {
+	HeapTuple	htup = (HeapTuple) stup;
 	HeapTupleHeader tuple = htup->t_data;
 	TransactionId xmin = HeapTupleHeaderGetXmin(tuple);
 	TransactionId xmax = HeapTupleHeaderGetRawXmax(tuple);
@@ -1796,3 +1800,35 @@ HeapTupleSatisfiesHistoricMVCC(HeapTuple htup, Snapshot snapshot,
 	else
 		return true;
 }
+
+bool
+HeapTupleSatisfies(TableTuple stup, Snapshot snapshot, Buffer buffer)
+{
+	switch (snapshot->visibility_type)
+	{
+		case MVCC_VISIBILITY:
+			return HeapTupleSatisfiesMVCC(stup, snapshot, buffer);
+			break;
+		case SELF_VISIBILITY:
+			return HeapTupleSatisfiesSelf(stup, snapshot, buffer);
+			break;
+		case ANY_VISIBILITY:
+			return HeapTupleSatisfiesAny(stup, snapshot, buffer);
+			break;
+		case TOAST_VISIBILITY:
+			return HeapTupleSatisfiesToast(stup, snapshot, buffer);
+			break;
+		case DIRTY_VISIBILITY:
+			return HeapTupleSatisfiesDirty(stup, snapshot, buffer);
+			break;
+		case HISTORIC_MVCC_VISIBILITY:
+			return HeapTupleSatisfiesHistoricMVCC(stup, snapshot, buffer);
+			break;
+		case NON_VACUUMABLE_VISIBILTY:
+			return HeapTupleSatisfiesNonVacuumable(stup, snapshot, buffer);
+			break;
+		default:
+			Assert(0);
+			break;
+	}
+}
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index f67d7d15df..51ec8fb708 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -402,7 +402,7 @@ heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum,
 			 * either here or while following a chain below.  Whichever path
 			 * gets there first will mark the tuple unused.
 			 */
-			if (HeapTupleSatisfiesVacuum(&tup, OldestXmin, buffer)
+			if (relation->rd_tableamroutine->snapshot_satisfiesVacuum(&tup, OldestXmin, buffer)
 				== HEAPTUPLE_DEAD && !HeapTupleHeaderIsHotUpdated(htup))
 			{
 				heap_prune_record_unused(prstate, rootoffnum);
@@ -486,7 +486,7 @@ heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum,
 		 */
 		tupdead = recent_dead = false;
 
-		switch (HeapTupleSatisfiesVacuum(&tup, OldestXmin, buffer))
+		switch (relation->rd_tableamroutine->snapshot_satisfiesVacuum(&tup, OldestXmin, buffer))
 		{
 			case HEAPTUPLE_DEAD:
 				tupdead = true;
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 214825114e..8760905e72 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -472,7 +472,7 @@ systable_recheck_tuple(SysScanDesc sysscan, HeapTuple tup)
 		Assert(BufferIsValid(scan->xs_cbuf));
 		/* must hold a buffer lock to call HeapTupleSatisfiesVisibility */
 		LockBuffer(scan->xs_cbuf, BUFFER_LOCK_SHARE);
-		result = HeapTupleSatisfiesVisibility(tup, freshsnap, scan->xs_cbuf);
+		result = HeapTupleSatisfiesVisibility(sysscan->heap_rel->rd_tableamroutine, tup, freshsnap, scan->xs_cbuf);
 		LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK);
 	}
 	else
@@ -484,7 +484,7 @@ systable_recheck_tuple(SysScanDesc sysscan, HeapTuple tup)
 		Assert(BufferIsValid(scan->rs_cbuf));
 		/* must hold a buffer lock to call HeapTupleSatisfiesVisibility */
 		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-		result = HeapTupleSatisfiesVisibility(tup, freshsnap, scan->rs_cbuf);
+		result = HeapTupleSatisfiesVisibility(sysscan->heap_rel->rd_tableamroutine, tup, freshsnap, scan->rs_cbuf);
 		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
 	}
 	return result;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 330488b96f..10c7207314 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2225,6 +2225,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	TransactionId OldestXmin;
 	BlockNumber root_blkno = InvalidBlockNumber;
 	OffsetNumber root_offsets[MaxHeapTuplesPerPage];
+	TableAmRoutine *method;
 
 	/*
 	 * sanity checks
@@ -2280,6 +2281,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 		OldestXmin = GetOldestXmin(heapRelation, PROCARRAY_FLAGS_VACUUM);
 	}
 
+	method = heapRelation->rd_tableamroutine;
 	scan = heap_beginscan_strat(heapRelation,	/* relation */
 								snapshot,	/* snapshot */
 								0,	/* number of keys */
@@ -2360,8 +2362,8 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 			 */
 			LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 
-			switch (HeapTupleSatisfiesVacuum(heapTuple, OldestXmin,
-											 scan->rs_cbuf))
+			switch (method->snapshot_satisfiesVacuum(heapTuple, OldestXmin,
+													 scan->rs_cbuf))
 			{
 				case HEAPTUPLE_DEAD:
 					/* Definitely dead, we can ignore it */
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 5f21fcb5f4..80700ef8c6 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -1119,9 +1119,9 @@ acquire_sample_rows(Relation onerel, int elevel,
 			targtuple.t_data = (HeapTupleHeader) PageGetItem(targpage, itemid);
 			targtuple.t_len = ItemIdGetLength(itemid);
 
-			switch (HeapTupleSatisfiesVacuum(&targtuple,
-											 OldestXmin,
-											 targbuffer))
+			switch (onerel->rd_tableamroutine->snapshot_satisfiesVacuum(&targtuple,
+																	 OldestXmin,
+																	 targbuffer))
 			{
 				case HEAPTUPLE_LIVE:
 					sample_it = true;
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index eb73299199..45608e7dfc 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -21,6 +21,7 @@
 #include "access/multixact.h"
 #include "access/relscan.h"
 #include "access/rewriteheap.h"
+#include "access/tableamapi.h"
 #include "access/transam.h"
 #include "access/tuptoaster.h"
 #include "access/xact.h"
@@ -971,7 +972,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 
 		LockBuffer(buf, BUFFER_LOCK_SHARE);
 
-		switch (HeapTupleSatisfiesVacuum(tuple, OldestXmin, buf))
+		switch (OldHeap->rd_tableamroutine->snapshot_satisfiesVacuum(tuple, OldestXmin, buf))
 		{
 			case HEAPTUPLE_DEAD:
 				/* Definitely dead */
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index cf7f5e1162..73053950c1 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -988,7 +988,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
 
 			tupgone = false;
 
-			switch (HeapTupleSatisfiesVacuum(&tuple, OldestXmin, buf))
+			switch (onerel->rd_tableamroutine->snapshot_satisfiesVacuum(&tuple, OldestXmin, buf))
 			{
 				case HEAPTUPLE_DEAD:
 
@@ -2162,7 +2162,7 @@ heap_page_is_all_visible(Relation rel, Buffer buf,
 		tuple.t_len = ItemIdGetLength(itemid);
 		tuple.t_tableOid = RelationGetRelid(rel);
 
-		switch (HeapTupleSatisfiesVacuum(&tuple, OldestXmin, buf))
+		switch (rel->rd_tableamroutine->snapshot_satisfiesVacuum(&tuple, OldestXmin, buf))
 		{
 			case HEAPTUPLE_LIVE:
 				{
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index 7ba1db7d7e..1220ae4769 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -461,7 +461,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 			loctup.t_len = ItemIdGetLength(lp);
 			loctup.t_tableOid = scan->rs_rd->rd_id;
 			ItemPointerSet(&loctup.t_self, page, offnum);
-			valid = HeapTupleSatisfiesVisibility(&loctup, snapshot, buffer);
+			valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_tableamroutine, &loctup, snapshot, buffer);
 			if (valid)
 			{
 				scan->rs_vistuples[ntup++] = offnum;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 55dff5b21a..f816eda9be 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -191,6 +191,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
  */
 static void
 ExecCheckHeapTupleVisible(EState *estate,
+						  Relation rel,
 						  HeapTuple tuple,
 						  Buffer buffer)
 {
@@ -202,7 +203,7 @@ ExecCheckHeapTupleVisible(EState *estate,
 	 * Caller should be holding pin, but not lock.
 	 */
 	LockBuffer(buffer, BUFFER_LOCK_SHARE);
-	if (!HeapTupleSatisfiesVisibility(tuple, estate->es_snapshot, buffer))
+	if (!HeapTupleSatisfiesVisibility(rel->rd_tableamroutine, tuple, estate->es_snapshot, buffer))
 	{
 		/*
 		 * We should not raise a serialization failure if the conflict is
@@ -237,7 +238,7 @@ ExecCheckTIDVisible(EState *estate,
 	tuple.t_self = *tid;
 	if (!heap_fetch(rel, SnapshotAny, &tuple, &buffer, false, NULL))
 		elog(ERROR, "failed to fetch conflicting tuple for ON CONFLICT");
-	ExecCheckHeapTupleVisible(estate, &tuple, buffer);
+	ExecCheckHeapTupleVisible(estate, rel, &tuple, buffer);
 	ReleaseBuffer(buffer);
 }
 
@@ -1314,7 +1315,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	 * snapshot.  This is in line with the way UPDATE deals with newer tuple
 	 * versions.
 	 */
-	ExecCheckHeapTupleVisible(estate, &tuple, buffer);
+	ExecCheckHeapTupleVisible(estate, relation, &tuple, buffer);
 
 	/* Store target's existing tuple in the state's dedicated slot */
 	ExecStoreTuple(&tuple, mtstate->mt_existing, buffer, false);
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index e88cd18737..9750509eae 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -588,7 +588,8 @@ SampleTupleVisible(HeapTuple tuple, OffsetNumber tupoffset, HeapScanDesc scan)
 	else
 	{
 		/* Otherwise, we have to check the tuple individually. */
-		return HeapTupleSatisfiesVisibility(tuple,
+		return HeapTupleSatisfiesVisibility(scan->rs_rd->rd_tableamroutine,
+											tuple,
 											scan->rs_snapshot,
 											scan->rs_cbuf);
 	}
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index 5b35f22a32..9c5329812d 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -376,7 +376,7 @@ static void
 SnapBuildFreeSnapshot(Snapshot snap)
 {
 	/* make sure we don't get passed an external snapshot */
-	Assert(snap->satisfies == HeapTupleSatisfiesHistoricMVCC);
+	Assert(snap->visibility_type == HISTORIC_MVCC_VISIBILITY);
 
 	/* make sure nobody modified our snapshot */
 	Assert(snap->curcid == FirstCommandId);
@@ -434,7 +434,7 @@ void
 SnapBuildSnapDecRefcount(Snapshot snap)
 {
 	/* make sure we don't get passed an external snapshot */
-	Assert(snap->satisfies == HeapTupleSatisfiesHistoricMVCC);
+	Assert(snap->visibility_type == HISTORIC_MVCC_VISIBILITY);
 
 	/* make sure nobody modified our snapshot */
 	Assert(snap->curcid == FirstCommandId);
@@ -476,7 +476,7 @@ SnapBuildBuildSnapshot(SnapBuild *builder)
 
 	snapshot = MemoryContextAllocZero(builder->context, ssize);
 
-	snapshot->satisfies = HeapTupleSatisfiesHistoricMVCC;
+	snapshot->visibility_type = HISTORIC_MVCC_VISIBILITY;
 
 	/*
 	 * We misuse the original meaning of SnapshotData's xip and subxip fields
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index d1ff2b1edc..efdfe73b97 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -3972,7 +3972,7 @@ CheckForSerializableConflictOut(bool visible, Relation relation,
 	 * tuple is visible to us, while HeapTupleSatisfiesVacuum checks what else
 	 * is going on with it.
 	 */
-	htsvResult = HeapTupleSatisfiesVacuum(tuple, TransactionXmin, buffer);
+	htsvResult = relation->rd_tableamroutine->snapshot_satisfiesVacuum(tuple, TransactionXmin, buffer);
 	switch (htsvResult)
 	{
 		case HEAPTUPLE_LIVE:
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 8faae1d069..6f5e06356f 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -287,7 +287,7 @@ RI_FKey_check(TriggerData *trigdata)
 	 * should be holding pin, but not lock.
 	 */
 	LockBuffer(new_row_buf, BUFFER_LOCK_SHARE);
-	if (!HeapTupleSatisfiesVisibility(new_row, SnapshotSelf, new_row_buf))
+	if (!HeapTupleSatisfiesVisibility(trigdata->tg_relation->rd_tableamroutine, new_row, SnapshotSelf, new_row_buf))
 	{
 		LockBuffer(new_row_buf, BUFFER_LOCK_UNLOCK);
 		return PointerGetDatum(NULL);
diff --git a/src/backend/utils/time/Makefile b/src/backend/utils/time/Makefile
index 5a6e6fa4c8..f17b1c5324 100644
--- a/src/backend/utils/time/Makefile
+++ b/src/backend/utils/time/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/utils/time
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = combocid.o tqual.o snapmgr.o
+OBJS = combocid.o snapmgr.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c
index e58c69dbd7..dd486fc6a3 100644
--- a/src/backend/utils/time/snapmgr.c
+++ b/src/backend/utils/time/snapmgr.c
@@ -141,9 +141,9 @@ static volatile OldSnapshotControlData *oldSnapshotControl;
  * These SnapshotData structs are static to simplify memory allocation
  * (see the hack in GetSnapshotData to avoid repeated malloc/free).
  */
-static SnapshotData CurrentSnapshotData = {HeapTupleSatisfiesMVCC};
-static SnapshotData SecondarySnapshotData = {HeapTupleSatisfiesMVCC};
-SnapshotData CatalogSnapshotData = {HeapTupleSatisfiesMVCC};
+static SnapshotData CurrentSnapshotData = {MVCC_VISIBILITY};
+static SnapshotData SecondarySnapshotData = {MVCC_VISIBILITY};
+SnapshotData CatalogSnapshotData = {MVCC_VISIBILITY};
 
 /* Pointers to valid snapshots */
 static Snapshot CurrentSnapshot = NULL;
@@ -2046,7 +2046,7 @@ EstimateSnapshotSpace(Snapshot snap)
 	Size		size;
 
 	Assert(snap != InvalidSnapshot);
-	Assert(snap->satisfies == HeapTupleSatisfiesMVCC);
+	Assert(snap->visibility_type == MVCC_VISIBILITY);
 
 	/* We allocate any XID arrays needed in the same palloc block. */
 	size = add_size(sizeof(SerializedSnapshotData),
@@ -2143,7 +2143,7 @@ RestoreSnapshot(char *start_address)
 
 	/* Copy all required fields */
 	snapshot = (Snapshot) MemoryContextAlloc(TopTransactionContext, size);
-	snapshot->satisfies = HeapTupleSatisfiesMVCC;
+	snapshot->visibility_type = MVCC_VISIBILITY;
 	snapshot->xmin = serialized_snapshot.xmin;
 	snapshot->xmax = serialized_snapshot.xmax;
 	snapshot->xip = NULL;
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 4c0256b18a..9118e5c991 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -16,6 +16,7 @@
 
 #include "access/sdir.h"
 #include "access/skey.h"
+#include "access/tableam_common.h"
 #include "nodes/lockoptions.h"
 #include "nodes/primnodes.h"
 #include "storage/bufpage.h"
@@ -200,4 +201,16 @@ extern BlockNumber ss_get_location(Relation rel, BlockNumber relnblocks);
 extern void SyncScanShmemInit(void);
 extern Size SyncScanShmemSize(void);
 
+/* in heap/heapam_visibility.c */
+extern bool HeapTupleSatisfies(TableTuple stup, Snapshot snapshot, Buffer buffer);
+extern HTSU_Result HeapTupleSatisfiesUpdate(TableTuple stup, CommandId curcid,
+						 Buffer buffer);
+extern HTSV_Result HeapTupleSatisfiesVacuum(TableTuple stup, TransactionId OldestXmin,
+						 Buffer buffer);
+extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
+					 uint16 infomask, TransactionId xid);
+extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
+extern bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
+extern bool HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin);
+
 #endif							/* HEAPAM_H */
diff --git a/src/include/access/tableam_common.h b/src/include/access/tableam_common.h
new file mode 100644
index 0000000000..78b24d76c7
--- /dev/null
+++ b/src/include/access/tableam_common.h
@@ -0,0 +1,41 @@
+/*-------------------------------------------------------------------------
+ *
+ * tableam_common.h
+ *	  POSTGRES table access method definitions shared across
+ *	  all pluggable table access methods and server.
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/tableam_common.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef TABLEAM_COMMON_H
+#define TABLEAM_COMMON_H
+
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/transam.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "storage/bufpage.h"
+#include "storage/bufmgr.h"
+
+
+/* A physical tuple coming from a table AM scan */
+typedef void *TableTuple;
+
+/* Result codes for HeapTupleSatisfiesVacuum */
+typedef enum
+{
+	HEAPTUPLE_DEAD,				/* tuple is dead and deletable */
+	HEAPTUPLE_LIVE,				/* tuple is live (committed, no deleter) */
+	HEAPTUPLE_RECENTLY_DEAD,	/* tuple is dead, but not deletable yet */
+	HEAPTUPLE_INSERT_IN_PROGRESS,	/* inserting xact is still in progress */
+	HEAPTUPLE_DELETE_IN_PROGRESS	/* deleting xact is still in progress */
+} HTSV_Result;
+
+#endif							/* TABLEAM_COMMON_H */
diff --git a/src/include/access/tableamapi.h b/src/include/access/tableamapi.h
index 55ddad68fb..4bd50b48f1 100644
--- a/src/include/access/tableamapi.h
+++ b/src/include/access/tableamapi.h
@@ -11,11 +11,18 @@
 #ifndef TABLEEAMAPI_H
 #define TABLEEAMAPI_H
 
+#include "access/tableam_common.h"
 #include "nodes/nodes.h"
 #include "fmgr.h"
+#include "utils/snapshot.h"
 
-/* A physical tuple coming from a table AM scan */
-typedef void *TableTuple;
+
+/*
+ * Storage routine function hooks
+ */
+typedef bool (*SnapshotSatisfies_function) (TableTuple htup, Snapshot snapshot, Buffer buffer);
+typedef HTSU_Result (*SnapshotSatisfiesUpdate_function) (TableTuple htup, CommandId curcid, Buffer buffer);
+typedef HTSV_Result (*SnapshotSatisfiesVacuum_function) (TableTuple htup, TransactionId OldestXmin, Buffer buffer);
 
 /*
  * API struct for a table AM.  Note this must be stored in a single palloc'd
@@ -30,6 +37,10 @@ typedef struct TableAmRoutine
 {
 	NodeTag		type;
 
+	SnapshotSatisfies_function snapshot_satisfies;
+	SnapshotSatisfiesUpdate_function snapshot_satisfiesUpdate;	/* HeapTupleSatisfiesUpdate */
+	SnapshotSatisfiesVacuum_function snapshot_satisfiesVacuum;	/* HeapTupleSatisfiesVacuum */
+
 }			TableAmRoutine;
 
 extern TableAmRoutine * GetTableAmRoutine(Oid amhandler);
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 3cce3906a0..95915bdc92 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -20,7 +20,6 @@
 #include "storage/relfilenode.h"
 #include "utils/relcache.h"
 #include "utils/snapmgr.h"
-#include "utils/tqual.h"
 
 typedef void *Block;
 
@@ -268,8 +267,8 @@ TestForOldSnapshot(Snapshot snapshot, Relation relation, Page page)
 
 	if (old_snapshot_threshold >= 0
 		&& (snapshot) != NULL
-		&& ((snapshot)->satisfies == HeapTupleSatisfiesMVCC
-			|| (snapshot)->satisfies == HeapTupleSatisfiesToast)
+		&& ((snapshot)->visibility_type == MVCC_VISIBILITY
+			|| (snapshot)->visibility_type == TOAST_VISIBILITY)
 		&& !XLogRecPtrIsInvalid((snapshot)->lsn)
 		&& PageGetLSN(page) > (snapshot)->lsn)
 		TestForOldSnapshot_impl(snapshot, relation);
diff --git a/src/include/utils/snapshot.h b/src/include/utils/snapshot.h
index a8a5a8f4c0..ca96fd00fa 100644
--- a/src/include/utils/snapshot.h
+++ b/src/include/utils/snapshot.h
@@ -19,6 +19,18 @@
 #include "lib/pairingheap.h"
 #include "storage/buf.h"
 
+typedef enum tuple_visibility_type
+{
+	MVCC_VISIBILITY = 0,		/* HeapTupleSatisfiesMVCC */
+	SELF_VISIBILITY,			/* HeapTupleSatisfiesSelf */
+	ANY_VISIBILITY,				/* HeapTupleSatisfiesAny */
+	TOAST_VISIBILITY,			/* HeapTupleSatisfiesToast */
+	DIRTY_VISIBILITY,			/* HeapTupleSatisfiesDirty */
+	HISTORIC_MVCC_VISIBILITY,	/* HeapTupleSatisfiesHistoricMVCC */
+	NON_VACUUMABLE_VISIBILTY,	/* HeapTupleSatisfiesNonVacuumable */
+
+	END_OF_VISIBILITY
+}			tuple_visibility_type;
 
 typedef struct SnapshotData *Snapshot;
 
@@ -52,7 +64,7 @@ typedef bool (*SnapshotSatisfiesFunc) (HeapTuple htup,
  */
 typedef struct SnapshotData
 {
-	SnapshotSatisfiesFunc satisfies;	/* tuple test function */
+	tuple_visibility_type visibility_type;	/* tuple visibility test type */
 
 	/*
 	 * The remaining fields are used only for MVCC snapshots, and are normally
diff --git a/src/include/utils/tqual.h b/src/include/utils/tqual.h
index d3b6e99bb4..075303b410 100644
--- a/src/include/utils/tqual.h
+++ b/src/include/utils/tqual.h
@@ -16,6 +16,7 @@
 #define TQUAL_H
 
 #include "utils/snapshot.h"
+#include "access/tableamapi.h"
 #include "access/xlogdefs.h"
 
 
@@ -29,8 +30,8 @@ extern PGDLLIMPORT SnapshotData CatalogSnapshotData;
 
 /* This macro encodes the knowledge of which snapshots are MVCC-safe */
 #define IsMVCCSnapshot(snapshot)  \
-	((snapshot)->satisfies == HeapTupleSatisfiesMVCC || \
-	 (snapshot)->satisfies == HeapTupleSatisfiesHistoricMVCC)
+	((snapshot)->visibility_type == MVCC_VISIBILITY || \
+	 (snapshot)->visibility_type == HISTORIC_MVCC_VISIBILITY)
 
 /*
  * HeapTupleSatisfiesVisibility
@@ -42,47 +43,8 @@ extern PGDLLIMPORT SnapshotData CatalogSnapshotData;
  *	Hint bits in the HeapTuple's t_infomask may be updated as a side effect;
  *	if so, the indicated buffer is marked dirty.
  */
-#define HeapTupleSatisfiesVisibility(tuple, snapshot, buffer) \
-	((*(snapshot)->satisfies) (tuple, snapshot, buffer))
-
-/* Result codes for HeapTupleSatisfiesVacuum */
-typedef enum
-{
-	HEAPTUPLE_DEAD,				/* tuple is dead and deletable */
-	HEAPTUPLE_LIVE,				/* tuple is live (committed, no deleter) */
-	HEAPTUPLE_RECENTLY_DEAD,	/* tuple is dead, but not deletable yet */
-	HEAPTUPLE_INSERT_IN_PROGRESS,	/* inserting xact is still in progress */
-	HEAPTUPLE_DELETE_IN_PROGRESS	/* deleting xact is still in progress */
-} HTSV_Result;
-
-/* These are the "satisfies" test routines for the various snapshot types */
-extern bool HeapTupleSatisfiesMVCC(HeapTuple htup,
-					   Snapshot snapshot, Buffer buffer);
-extern bool HeapTupleSatisfiesSelf(HeapTuple htup,
-					   Snapshot snapshot, Buffer buffer);
-extern bool HeapTupleSatisfiesAny(HeapTuple htup,
-					  Snapshot snapshot, Buffer buffer);
-extern bool HeapTupleSatisfiesToast(HeapTuple htup,
-						Snapshot snapshot, Buffer buffer);
-extern bool HeapTupleSatisfiesDirty(HeapTuple htup,
-						Snapshot snapshot, Buffer buffer);
-extern bool HeapTupleSatisfiesNonVacuumable(HeapTuple htup,
-								Snapshot snapshot, Buffer buffer);
-extern bool HeapTupleSatisfiesHistoricMVCC(HeapTuple htup,
-							   Snapshot snapshot, Buffer buffer);
-
-/* Special "satisfies" routines with different APIs */
-extern HTSU_Result HeapTupleSatisfiesUpdate(HeapTuple htup,
-						 CommandId curcid, Buffer buffer);
-extern HTSV_Result HeapTupleSatisfiesVacuum(HeapTuple htup,
-						 TransactionId OldestXmin, Buffer buffer);
-extern bool HeapTupleIsSurelyDead(HeapTuple htup,
-					  TransactionId OldestXmin);
-extern bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
-
-extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
-					 uint16 infomask, TransactionId xid);
-extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
+#define HeapTupleSatisfiesVisibility(method, tuple, snapshot, buffer) \
+	(((method)->snapshot_satisfies) (tuple, snapshot, buffer))
 
 /*
  * To avoid leaking too much knowledge about reorderbuffer implementation
@@ -101,14 +63,14 @@ extern bool ResolveCminCmaxDuringDecoding(struct HTAB *tuplecid_data,
  * local variable of type SnapshotData, and initialize it with this macro.
  */
 #define InitDirtySnapshot(snapshotdata)  \
-	((snapshotdata).satisfies = HeapTupleSatisfiesDirty)
+	((snapshotdata).visibility_type = DIRTY_VISIBILITY)
 
 /*
  * Similarly, some initialization is required for a NonVacuumable snapshot.
  * The caller must supply the xmin horizon to use (e.g., RecentGlobalXmin).
  */
 #define InitNonVacuumableSnapshot(snapshotdata, xmin_horizon)  \
-	((snapshotdata).satisfies = HeapTupleSatisfiesNonVacuumable, \
+	((snapshotdata).visibility_type = NON_VACUUMABLE_VISIBILTY, \
 	 (snapshotdata).xmin = (xmin_horizon))
 
 /*
@@ -116,7 +78,7 @@ extern bool ResolveCminCmaxDuringDecoding(struct HTAB *tuplecid_data,
  * to set lsn and whenTaken correctly to support snapshot_too_old.
  */
 #define InitToastSnapshot(snapshotdata, l, w)  \
-	((snapshotdata).satisfies = HeapTupleSatisfiesToast, \
+	((snapshotdata).visibility_type = TOAST_VISIBILITY, \
 	 (snapshotdata).lsn = (l),					\
 	 (snapshotdata).whenTaken = (w))
 
-- 
2.15.0.windows.1



  [application/octet-stream] 0005-slot-hooks-are-added-to-table-AM.patch (62.9K, ../../CAJrrPGdY_hgvu06R_vCibUktKRLp=b8nBfeZkh=KRc95tq4euA@mail.gmail.com/10-0005-slot-hooks-are-added-to-table-AM.patch)
  download | inline diff:
From 6dae70eae95f1f2a1acfcaee7d16a6ef4b603703 Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Tue, 9 Jan 2018 16:17:54 +1100
Subject: [PATCH 05/12] slot hooks are added to table AM

The tuple is removed as part of the slot and added
an void pointer to store the tuple data that can
understand only by the table AM routine.

The slot utility functions are reorganized to use
two table AM routines to satify the current
functionality.

Currently the slot supports minimum tuple also.
---
 src/backend/access/common/heaptuple.c     | 302 +--------------------
 src/backend/access/heap/heapam_handler.c  |   2 +
 src/backend/access/table/Makefile         |   2 +-
 src/backend/access/table/tableam_common.c | 433 ++++++++++++++++++++++++++++++
 src/backend/commands/copy.c               |   2 +-
 src/backend/commands/createas.c           |   2 +-
 src/backend/commands/matview.c            |   2 +-
 src/backend/commands/trigger.c            |  15 +-
 src/backend/executor/execExprInterp.c     |  35 +--
 src/backend/executor/execReplication.c    |  90 ++-----
 src/backend/executor/execTuples.c         | 268 +++++++++---------
 src/backend/executor/nodeForeignscan.c    |   2 +-
 src/backend/executor/nodeModifyTable.c    |  24 +-
 src/backend/executor/tqueue.c             |   2 +-
 src/backend/replication/logical/worker.c  |   5 +-
 src/include/access/htup_details.h         |  15 +-
 src/include/access/tableam_common.h       |  37 +++
 src/include/access/tableamapi.h           |   2 +
 src/include/executor/tuptable.h           |  54 ++--
 19 files changed, 718 insertions(+), 576 deletions(-)
 create mode 100644 src/backend/access/table/tableam_common.c

diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 0a13251067..454294fd3e 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -57,6 +57,7 @@
 
 #include "postgres.h"
 
+#include "access/tableamapi.h"
 #include "access/sysattr.h"
 #include "access/tuptoaster.h"
 #include "executor/tuptable.h"
@@ -1021,111 +1022,6 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
 	}
 }
 
-/*
- * slot_deform_tuple
- *		Given a TupleTableSlot, extract data from the slot's physical tuple
- *		into its Datum/isnull arrays.  Data is extracted up through the
- *		natts'th column (caller must ensure this is a legal column number).
- *
- *		This is essentially an incremental version of heap_deform_tuple:
- *		on each call we extract attributes up to the one needed, without
- *		re-computing information about previously extracted attributes.
- *		slot->tts_nvalid is the number of attributes already extracted.
- */
-static void
-slot_deform_tuple(TupleTableSlot *slot, int natts)
-{
-	HeapTuple	tuple = slot->tts_tuple;
-	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
-	Datum	   *values = slot->tts_values;
-	bool	   *isnull = slot->tts_isnull;
-	HeapTupleHeader tup = tuple->t_data;
-	bool		hasnulls = HeapTupleHasNulls(tuple);
-	int			attnum;
-	char	   *tp;				/* ptr to tuple data */
-	long		off;			/* offset in tuple data */
-	bits8	   *bp = tup->t_bits;	/* ptr to null bitmap in tuple */
-	bool		slow;			/* can we use/set attcacheoff? */
-
-	/*
-	 * Check whether the first call for this tuple, and initialize or restore
-	 * loop state.
-	 */
-	attnum = slot->tts_nvalid;
-	if (attnum == 0)
-	{
-		/* Start from the first attribute */
-		off = 0;
-		slow = false;
-	}
-	else
-	{
-		/* Restore state from previous execution */
-		off = slot->tts_off;
-		slow = slot->tts_slow;
-	}
-
-	tp = (char *) tup + tup->t_hoff;
-
-	for (; attnum < natts; attnum++)
-	{
-		Form_pg_attribute thisatt = TupleDescAttr(tupleDesc, attnum);
-
-		if (hasnulls && att_isnull(attnum, bp))
-		{
-			values[attnum] = (Datum) 0;
-			isnull[attnum] = true;
-			slow = true;		/* can't use attcacheoff anymore */
-			continue;
-		}
-
-		isnull[attnum] = false;
-
-		if (!slow && thisatt->attcacheoff >= 0)
-			off = thisatt->attcacheoff;
-		else if (thisatt->attlen == -1)
-		{
-			/*
-			 * We can only cache the offset for a varlena attribute if the
-			 * offset is already suitably aligned, so that there would be no
-			 * pad bytes in any case: then the offset will be valid for either
-			 * an aligned or unaligned value.
-			 */
-			if (!slow &&
-				off == att_align_nominal(off, thisatt->attalign))
-				thisatt->attcacheoff = off;
-			else
-			{
-				off = att_align_pointer(off, thisatt->attalign, -1,
-										tp + off);
-				slow = true;
-			}
-		}
-		else
-		{
-			/* not varlena, so safe to use att_align_nominal */
-			off = att_align_nominal(off, thisatt->attalign);
-
-			if (!slow)
-				thisatt->attcacheoff = off;
-		}
-
-		values[attnum] = fetchatt(thisatt, tp + off);
-
-		off = att_addlength_pointer(off, thisatt->attlen, tp + off);
-
-		if (thisatt->attlen <= 0)
-			slow = true;		/* can't use attcacheoff anymore */
-	}
-
-	/*
-	 * Save state for next execution
-	 */
-	slot->tts_nvalid = attnum;
-	slot->tts_off = off;
-	slot->tts_slow = slow;
-}
-
 /*
  * slot_getattr
  *		This function fetches an attribute of the slot's current tuple.
@@ -1141,91 +1037,7 @@ slot_deform_tuple(TupleTableSlot *slot, int natts)
 Datum
 slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
 {
-	HeapTuple	tuple = slot->tts_tuple;
-	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
-	HeapTupleHeader tup;
-
-	/*
-	 * system attributes are handled by heap_getsysattr
-	 */
-	if (attnum <= 0)
-	{
-		if (tuple == NULL)		/* internal error */
-			elog(ERROR, "cannot extract system attribute from virtual tuple");
-		if (tuple == &(slot->tts_minhdr))	/* internal error */
-			elog(ERROR, "cannot extract system attribute from minimal tuple");
-		return heap_getsysattr(tuple, attnum, tupleDesc, isnull);
-	}
-
-	/*
-	 * fast path if desired attribute already cached
-	 */
-	if (attnum <= slot->tts_nvalid)
-	{
-		*isnull = slot->tts_isnull[attnum - 1];
-		return slot->tts_values[attnum - 1];
-	}
-
-	/*
-	 * return NULL if attnum is out of range according to the tupdesc
-	 */
-	if (attnum > tupleDesc->natts)
-	{
-		*isnull = true;
-		return (Datum) 0;
-	}
-
-	/*
-	 * otherwise we had better have a physical tuple (tts_nvalid should equal
-	 * natts in all virtual-tuple cases)
-	 */
-	if (tuple == NULL)			/* internal error */
-		elog(ERROR, "cannot extract attribute from empty tuple slot");
-
-	/*
-	 * return NULL if attnum is out of range according to the tuple
-	 *
-	 * (We have to check this separately because of various inheritance and
-	 * table-alteration scenarios: the tuple could be either longer or shorter
-	 * than the tupdesc.)
-	 */
-	tup = tuple->t_data;
-	if (attnum > HeapTupleHeaderGetNatts(tup))
-	{
-		*isnull = true;
-		return (Datum) 0;
-	}
-
-	/*
-	 * check if target attribute is null: no point in groveling through tuple
-	 */
-	if (HeapTupleHasNulls(tuple) && att_isnull(attnum - 1, tup->t_bits))
-	{
-		*isnull = true;
-		return (Datum) 0;
-	}
-
-	/*
-	 * If the attribute's column has been dropped, we force a NULL result.
-	 * This case should not happen in normal use, but it could happen if we
-	 * are executing a plan cached before the column was dropped.
-	 */
-	if (TupleDescAttr(tupleDesc, attnum - 1)->attisdropped)
-	{
-		*isnull = true;
-		return (Datum) 0;
-	}
-
-	/*
-	 * Extract the attribute, along with any preceding attributes.
-	 */
-	slot_deform_tuple(slot, attnum);
-
-	/*
-	 * The result is acquired from tts_values array.
-	 */
-	*isnull = slot->tts_isnull[attnum - 1];
-	return slot->tts_values[attnum - 1];
+	return slot->tts_slottableam->slot_getattr(slot, attnum, isnull);
 }
 
 /*
@@ -1237,40 +1049,7 @@ slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
 void
 slot_getallattrs(TupleTableSlot *slot)
 {
-	int			tdesc_natts = slot->tts_tupleDescriptor->natts;
-	int			attnum;
-	HeapTuple	tuple;
-
-	/* Quick out if we have 'em all already */
-	if (slot->tts_nvalid == tdesc_natts)
-		return;
-
-	/*
-	 * otherwise we had better have a physical tuple (tts_nvalid should equal
-	 * natts in all virtual-tuple cases)
-	 */
-	tuple = slot->tts_tuple;
-	if (tuple == NULL)			/* internal error */
-		elog(ERROR, "cannot extract attribute from empty tuple slot");
-
-	/*
-	 * load up any slots available from physical tuple
-	 */
-	attnum = HeapTupleHeaderGetNatts(tuple->t_data);
-	attnum = Min(attnum, tdesc_natts);
-
-	slot_deform_tuple(slot, attnum);
-
-	/*
-	 * If tuple doesn't have all the atts indicated by tupleDesc, read the
-	 * rest as null
-	 */
-	for (; attnum < tdesc_natts; attnum++)
-	{
-		slot->tts_values[attnum] = (Datum) 0;
-		slot->tts_isnull[attnum] = true;
-	}
-	slot->tts_nvalid = tdesc_natts;
+	slot->tts_slottableam->slot_virtualize_tuple(slot, slot->tts_tupleDescriptor->natts);
 }
 
 /*
@@ -1281,43 +1060,7 @@ slot_getallattrs(TupleTableSlot *slot)
 void
 slot_getsomeattrs(TupleTableSlot *slot, int attnum)
 {
-	HeapTuple	tuple;
-	int			attno;
-
-	/* Quick out if we have 'em all already */
-	if (slot->tts_nvalid >= attnum)
-		return;
-
-	/* Check for caller error */
-	if (attnum <= 0 || attnum > slot->tts_tupleDescriptor->natts)
-		elog(ERROR, "invalid attribute number %d", attnum);
-
-	/*
-	 * otherwise we had better have a physical tuple (tts_nvalid should equal
-	 * natts in all virtual-tuple cases)
-	 */
-	tuple = slot->tts_tuple;
-	if (tuple == NULL)			/* internal error */
-		elog(ERROR, "cannot extract attribute from empty tuple slot");
-
-	/*
-	 * load up any slots available from physical tuple
-	 */
-	attno = HeapTupleHeaderGetNatts(tuple->t_data);
-	attno = Min(attno, attnum);
-
-	slot_deform_tuple(slot, attno);
-
-	/*
-	 * If tuple doesn't have all the atts indicated by tupleDesc, read the
-	 * rest as null
-	 */
-	for (; attno < attnum; attno++)
-	{
-		slot->tts_values[attno] = (Datum) 0;
-		slot->tts_isnull[attno] = true;
-	}
-	slot->tts_nvalid = attnum;
+	slot->tts_slottableam->slot_virtualize_tuple(slot, attnum);
 }
 
 /*
@@ -1328,42 +1071,11 @@ slot_getsomeattrs(TupleTableSlot *slot, int attnum)
 bool
 slot_attisnull(TupleTableSlot *slot, int attnum)
 {
-	HeapTuple	tuple = slot->tts_tuple;
-	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
-
-	/*
-	 * system attributes are handled by heap_attisnull
-	 */
-	if (attnum <= 0)
-	{
-		if (tuple == NULL)		/* internal error */
-			elog(ERROR, "cannot extract system attribute from virtual tuple");
-		if (tuple == &(slot->tts_minhdr))	/* internal error */
-			elog(ERROR, "cannot extract system attribute from minimal tuple");
-		return heap_attisnull(tuple, attnum);
-	}
-
-	/*
-	 * fast path if desired attribute already cached
-	 */
-	if (attnum <= slot->tts_nvalid)
-		return slot->tts_isnull[attnum - 1];
-
-	/*
-	 * return NULL if attnum is out of range according to the tupdesc
-	 */
-	if (attnum > tupleDesc->natts)
-		return true;
+	bool		isnull;
 
-	/*
-	 * otherwise we had better have a physical tuple (tts_nvalid should equal
-	 * natts in all virtual-tuple cases)
-	 */
-	if (tuple == NULL)			/* internal error */
-		elog(ERROR, "cannot extract attribute from empty tuple slot");
+	slot->tts_slottableam->slot_getattr(slot, attnum, &isnull);
 
-	/* and let the tuple tell it */
-	return heap_attisnull(tuple, attnum);
+	return isnull;
 }
 
 /*
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 61086fe64c..96daa6a5ef 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -35,5 +35,7 @@ heap_tableam_handler(PG_FUNCTION_ARGS)
 	amroutine->snapshot_satisfiesUpdate = HeapTupleSatisfiesUpdate;
 	amroutine->snapshot_satisfiesVacuum = HeapTupleSatisfiesVacuum;
 
+	amroutine->slot_storageam = slot_tableam_handler;
+
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/table/Makefile b/src/backend/access/table/Makefile
index 496b7387c6..ff0989ed24 100644
--- a/src/backend/access/table/Makefile
+++ b/src/backend/access/table/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/table
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = tableamapi.o
+OBJS = tableamapi.o tableam_common.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/table/tableam_common.c b/src/backend/access/table/tableam_common.c
new file mode 100644
index 0000000000..3a6c02d5bd
--- /dev/null
+++ b/src/backend/access/table/tableam_common.c
@@ -0,0 +1,433 @@
+/*-------------------------------------------------------------------------
+ *
+ * tableam_common.c
+ *	  table access method code that is common across all pluggable
+ *	  table access modules
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/table/tableam_common.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/tableam_common.h"
+#include "access/subtrans.h"
+#include "access/transam.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "storage/bufmgr.h"
+#include "storage/procarray.h"
+
+/*-----------------------
+ *
+ * Slot table AM handler API
+ * ----------------------
+ */
+
+static HeapTuple
+heapam_get_tuple(TupleTableSlot *slot, bool palloc_copy)
+{
+	HeapTuple	tup;
+	HeapamTuple *stuple = (HeapamTuple *) slot->tts_storage;
+
+	if (stuple)
+	{
+		if (stuple->hst_mintuple)
+		{
+			tup = heap_tuple_from_minimal_tuple(stuple->hst_mintuple);
+		}
+		else
+		{
+			if (!palloc_copy)
+				tup = stuple->hst_heaptuple;
+			else
+				tup = heap_copytuple(stuple->hst_heaptuple);
+		}
+	}
+	else
+	{
+		tup = heap_form_tuple(slot->tts_tupleDescriptor,
+							  slot->tts_values,
+							  slot->tts_isnull);
+	}
+
+	return tup;
+}
+
+static MinimalTuple
+heapam_get_min_tuple(TupleTableSlot *slot, bool palloc_copy)
+{
+	MinimalTuple tup;
+	HeapamTuple *stuple = (HeapamTuple *) slot->tts_storage;
+
+	if (stuple)
+	{
+		if (stuple->hst_mintuple)
+		{
+			if (!palloc_copy)
+				tup = stuple->hst_mintuple;
+			else
+				tup = heap_copy_minimal_tuple(stuple->hst_mintuple);
+		}
+		else
+		{
+			tup = minimal_tuple_from_heap_tuple(stuple->hst_heaptuple);
+		}
+	}
+	else
+	{
+		tup = heap_form_minimal_tuple(slot->tts_tupleDescriptor,
+									  slot->tts_values,
+									  slot->tts_isnull);
+	}
+
+	return tup;
+}
+
+
+/*
+ * slot_deform_tuple
+ *		Given a TupleTableSlot, extract data from the slot's physical tuple
+ *		into its Datum/isnull arrays.  Data is extracted up through the
+ *		natts'th column (caller must ensure this is a legal column number).
+ *
+ *		This is essentially an incremental version of heap_deform_tuple:
+ *		on each call we extract attributes up to the one needed, without
+ *		re-computing information about previously extracted attributes.
+ *		slot->tts_nvalid is the number of attributes already extracted.
+ */
+static void
+slot_deform_tuple(TupleTableSlot *slot, int natts)
+{
+	HeapamTuple *stuple = (HeapamTuple *) slot->tts_storage;
+	HeapTuple	tuple = stuple ? stuple->hst_heaptuple : NULL;
+	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
+	Datum	   *values = slot->tts_values;
+	bool	   *isnull = slot->tts_isnull;
+	HeapTupleHeader tup = tuple->t_data;
+	bool		hasnulls = HeapTupleHasNulls(tuple);
+	int			attnum;
+	char	   *tp;				/* ptr to tuple data */
+	long		off;			/* offset in tuple data */
+	bits8	   *bp = tup->t_bits;	/* ptr to null bitmap in tuple */
+	bool		slow;			/* can we use/set attcacheoff? */
+
+	/*
+	 * Check whether the first call for this tuple, and initialize or restore
+	 * loop state.
+	 */
+	attnum = slot->tts_nvalid;
+	if (attnum == 0)
+	{
+		/* Start from the first attribute */
+		off = 0;
+		slow = false;
+	}
+	else
+	{
+		/* Restore state from previous execution */
+		off = stuple->hst_off;
+		slow = stuple->hst_slow;
+	}
+
+	tp = (char *) tup + tup->t_hoff;
+
+	for (; attnum < natts; attnum++)
+	{
+		Form_pg_attribute thisatt = TupleDescAttr(tupleDesc, attnum);
+
+		if (hasnulls && att_isnull(attnum, bp))
+		{
+			values[attnum] = (Datum) 0;
+			isnull[attnum] = true;
+			slow = true;		/* can't use attcacheoff anymore */
+			continue;
+		}
+
+		isnull[attnum] = false;
+
+		if (!slow && thisatt->attcacheoff >= 0)
+			off = thisatt->attcacheoff;
+		else if (thisatt->attlen == -1)
+		{
+			/*
+			 * We can only cache the offset for a varlena attribute if the
+			 * offset is already suitably aligned, so that there would be no
+			 * pad bytes in any case: then the offset will be valid for either
+			 * an aligned or unaligned value.
+			 */
+			if (!slow &&
+				off == att_align_nominal(off, thisatt->attalign))
+				thisatt->attcacheoff = off;
+			else
+			{
+				off = att_align_pointer(off, thisatt->attalign, -1,
+										tp + off);
+				slow = true;
+			}
+		}
+		else
+		{
+			/* not varlena, so safe to use att_align_nominal */
+			off = att_align_nominal(off, thisatt->attalign);
+
+			if (!slow)
+				thisatt->attcacheoff = off;
+		}
+
+		values[attnum] = fetchatt(thisatt, tp + off);
+
+		off = att_addlength_pointer(off, thisatt->attlen, tp + off);
+
+		if (thisatt->attlen <= 0)
+			slow = true;		/* can't use attcacheoff anymore */
+	}
+
+	/*
+	 * Save state for next execution
+	 */
+	slot->tts_nvalid = attnum;
+	stuple->hst_off = off;
+	stuple->hst_slow = slow;
+}
+
+static void
+heapam_slot_virtualize_tuple(TupleTableSlot *slot, int16 upto)
+{
+	HeapamTuple *stuple;
+	HeapTuple	tuple;
+	int			attno;
+
+	/* Quick out if we have 'em all already */
+	if (slot->tts_nvalid >= upto)
+		return;
+
+	/* Check for caller error */
+	if (upto <= 0 || upto > slot->tts_tupleDescriptor->natts)
+		elog(ERROR, "invalid attribute number %d", upto);
+
+	/*
+	 * otherwise we had better have a physical tuple (tts_nvalid should equal
+	 * natts in all virtual-tuple cases)
+	 */
+	stuple = slot->tts_storage; /* XXX SlotGetTupleStorage(slot) ??? */
+	tuple = stuple->hst_heaptuple;
+	if (tuple == NULL)			/* internal error */
+		elog(ERROR, "cannot extract attribute from empty tuple slot");
+
+	/*
+	 * load up any slots available from physical tuple
+	 */
+	attno = HeapTupleHeaderGetNatts(tuple->t_data);
+	attno = Min(attno, upto);
+
+	slot_deform_tuple(slot, attno);
+
+	/*
+	 * If tuple doesn't have all the atts indicated by tupleDesc, read the
+	 * rest as null
+	 */
+	for (; attno < upto; attno++)
+	{
+		slot->tts_values[attno] = (Datum) 0;
+		slot->tts_isnull[attno] = true;
+	}
+	slot->tts_nvalid = upto;
+}
+
+static void
+heapam_slot_update_tuple_tableoid(TupleTableSlot *slot, Oid tableoid)
+{
+	HeapTuple	tuple;
+
+	tuple = heapam_get_tuple(slot, false);
+	tuple->t_tableOid = tableoid;
+}
+
+static void
+heapam_slot_store_tuple(TupleTableSlot *slot, TableTuple tuple, bool shouldFree, bool minimum_tuple)
+{
+	HeapamTuple *stuple;
+	MemoryContext oldcontext;
+
+	oldcontext = MemoryContextSwitchTo(slot->tts_mcxt);
+
+	stuple = (HeapamTuple *) palloc0(sizeof(HeapamTuple));
+
+	if (!minimum_tuple)
+	{
+		stuple->hst_heaptuple = tuple;
+		stuple->hst_slow = false;
+		stuple->hst_off = 0;
+		stuple->hst_mintuple = NULL;
+		slot->tts_shouldFreeMin = false;
+		slot->tts_shouldFree = shouldFree;
+	}
+	else
+	{
+		stuple->hst_mintuple = tuple;
+		stuple->hst_minhdr.t_len = ((MinimalTuple) tuple)->t_len + MINIMAL_TUPLE_OFFSET;
+		stuple->hst_minhdr.t_data = (HeapTupleHeader) ((char *) tuple - MINIMAL_TUPLE_OFFSET);
+		stuple->hst_heaptuple = &stuple->hst_minhdr;
+		slot->tts_shouldFreeMin = shouldFree;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	slot->tts_tid = ((HeapTuple) tuple)->t_self;
+	if (slot->tts_tupleDescriptor->tdhasoid)
+		slot->tts_tupleOid = HeapTupleGetOid((HeapTuple) tuple);
+	slot->tts_storage = stuple;
+}
+
+static void
+heapam_slot_clear_tuple(TupleTableSlot *slot)
+{
+	HeapamTuple *stuple;
+
+	/* XXX should this be an Assert() instead? */
+	if (slot->tts_isempty)
+		return;
+
+	stuple = slot->tts_storage;
+	if (stuple == NULL)
+		return;
+
+	if (slot->tts_shouldFree)
+		heap_freetuple(stuple->hst_heaptuple);
+
+	if (slot->tts_shouldFreeMin)
+		heap_free_minimal_tuple(stuple->hst_mintuple);
+
+	slot->tts_shouldFree = false;
+	slot->tts_shouldFreeMin = false;
+
+	pfree(stuple);
+	slot->tts_storage = NULL;
+}
+
+/*
+ * slot_getattr
+ *		This function fetches an attribute of the slot's current tuple.
+ *		It is functionally equivalent to heap_getattr, but fetches of
+ *		multiple attributes of the same tuple will be optimized better,
+ *		because we avoid O(N^2) behavior from multiple calls of
+ *		nocachegetattr(), even when attcacheoff isn't usable.
+ *
+ *		A difference from raw heap_getattr is that attnums beyond the
+ *		slot's tupdesc's last attribute will be considered NULL even
+ *		when the physical tuple is longer than the tupdesc.
+ */
+static Datum
+heapam_slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
+{
+	HeapamTuple *stuple = slot->tts_storage;
+	HeapTuple	tuple = stuple ? stuple->hst_heaptuple : NULL;
+	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
+	HeapTupleHeader tup;
+
+	/*
+	 * system attributes are handled by heap_getsysattr
+	 */
+	if (attnum <= 0)
+	{
+		if (tuple == NULL)		/* internal error */
+			elog(ERROR, "cannot extract system attribute from virtual tuple");
+		if (tuple == &(stuple->hst_minhdr)) /* internal error */
+			elog(ERROR, "cannot extract system attribute from minimal tuple");
+		return heap_getsysattr(tuple, attnum, tupleDesc, isnull);
+	}
+
+	/*
+	 * fast path if desired attribute already cached
+	 */
+	if (attnum <= slot->tts_nvalid)
+	{
+		*isnull = slot->tts_isnull[attnum - 1];
+		return slot->tts_values[attnum - 1];
+	}
+
+	/*
+	 * return NULL if attnum is out of range according to the tupdesc
+	 */
+	if (attnum > tupleDesc->natts)
+	{
+		*isnull = true;
+		return (Datum) 0;
+	}
+
+	/*
+	 * otherwise we had better have a physical tuple (tts_nvalid should equal
+	 * natts in all virtual-tuple cases)
+	 */
+	if (tuple == NULL)			/* internal error */
+		elog(ERROR, "cannot extract attribute from empty tuple slot");
+
+	/*
+	 * return NULL if attnum is out of range according to the tuple
+	 *
+	 * (We have to check this separately because of various inheritance and
+	 * table-alteration scenarios: the tuple could be either longer or shorter
+	 * than the tupdesc.)
+	 */
+	tup = tuple->t_data;
+	if (attnum > HeapTupleHeaderGetNatts(tup))
+	{
+		*isnull = true;
+		return (Datum) 0;
+	}
+
+	/*
+	 * check if target attribute is null: no point in groveling through tuple
+	 */
+	if (HeapTupleHasNulls(tuple) && att_isnull(attnum - 1, tup->t_bits))
+	{
+		*isnull = true;
+		return (Datum) 0;
+	}
+
+	/*
+	 * If the attribute's column has been dropped, we force a NULL result.
+	 * This case should not happen in normal use, but it could happen if we
+	 * are executing a plan cached before the column was dropped.
+	 */
+	if (TupleDescAttr(tupleDesc, (attnum - 1))->attisdropped)
+	{
+		*isnull = true;
+		return (Datum) 0;
+	}
+
+	/*
+	 * Extract the attribute, along with any preceding attributes.
+	 */
+	slot_deform_tuple(slot, attnum);
+
+	/*
+	 * The result is acquired from tts_values array.
+	 */
+	*isnull = slot->tts_isnull[attnum - 1];
+	return slot->tts_values[attnum - 1];
+}
+
+SlotTableAmRoutine *
+slot_tableam_handler(void)
+{
+	SlotTableAmRoutine *amroutine = palloc(sizeof(SlotTableAmRoutine));
+
+	amroutine->slot_store_tuple = heapam_slot_store_tuple;
+	amroutine->slot_virtualize_tuple = heapam_slot_virtualize_tuple;
+	amroutine->slot_clear_tuple = heapam_slot_clear_tuple;
+	amroutine->slot_tuple = heapam_get_tuple;
+	amroutine->slot_min_tuple = heapam_get_min_tuple;
+	amroutine->slot_getattr = heapam_slot_getattr;
+	amroutine->slot_update_tableoid = heapam_slot_update_tuple_tableoid;
+
+	return amroutine;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 6bfca2a4af..dbf59e9d69 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2700,7 +2700,7 @@ CopyFrom(CopyState cstate)
 			if (slot == NULL)	/* "do nothing" */
 				skip_tuple = true;
 			else				/* trigger might have changed tuple */
-				tuple = ExecMaterializeSlot(slot);
+				tuple = ExecHeapifySlot(slot);
 		}
 
 		if (!skip_tuple)
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 3d82edbf58..ff2b7b75e9 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -588,7 +588,7 @@ intorel_receive(TupleTableSlot *slot, DestReceiver *self)
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
 	 */
-	tuple = ExecMaterializeSlot(slot);
+	tuple = ExecHeapifySlot(slot);
 
 	/*
 	 * force assignment of new OID (see comments in ExecInsert)
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index ab6a889b12..467695160a 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -497,7 +497,7 @@ transientrel_receive(TupleTableSlot *slot, DestReceiver *self)
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
 	 */
-	tuple = ExecMaterializeSlot(slot);
+	tuple = ExecHeapifySlot(slot);
 
 	heap_insert(myState->transientrel,
 				tuple,
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 1c488c338a..502f1dee1f 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2289,7 +2289,7 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 					 TupleTableSlot *slot)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
-	HeapTuple	slottuple = ExecMaterializeSlot(slot);
+	HeapTuple	slottuple = ExecHeapifySlot(slot);
 	HeapTuple	newtuple = slottuple;
 	HeapTuple	oldtuple;
 	TriggerData LocTriggerData;
@@ -2370,7 +2370,7 @@ ExecIRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 					 TupleTableSlot *slot)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
-	HeapTuple	slottuple = ExecMaterializeSlot(slot);
+	HeapTuple	slottuple = ExecHeapifySlot(slot);
 	HeapTuple	newtuple = slottuple;
 	HeapTuple	oldtuple;
 	TriggerData LocTriggerData;
@@ -2728,7 +2728,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 					 TupleTableSlot *slot)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
-	HeapTuple	slottuple = ExecMaterializeSlot(slot);
+	HeapTuple	slottuple = ExecHeapifySlot(slot);
 	HeapTuple	newtuple = slottuple;
 	TriggerData LocTriggerData;
 	HeapTuple	trigtuple;
@@ -2770,7 +2770,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 	if (newSlot != NULL)
 	{
 		slot = ExecFilterJunk(relinfo->ri_junkFilter, newSlot);
-		slottuple = ExecMaterializeSlot(slot);
+		slottuple = ExecHeapifySlot(slot);
 		newtuple = slottuple;
 	}
 
@@ -2879,7 +2879,7 @@ ExecIRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 					 HeapTuple trigtuple, TupleTableSlot *slot)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
-	HeapTuple	slottuple = ExecMaterializeSlot(slot);
+	HeapTuple	slottuple = ExecHeapifySlot(slot);
 	HeapTuple	newtuple = slottuple;
 	TriggerData LocTriggerData;
 	HeapTuple	oldtuple;
@@ -4006,14 +4006,13 @@ AfterTriggerExecute(AfterTriggerEvent event,
 			 * because we start with a minimal tuple that ExecFetchSlotTuple()
 			 * must materialize anyway.
 			 */
-			LocTriggerData.tg_trigtuple =
-				ExecMaterializeSlot(trig_tuple_slot1);
+			LocTriggerData.tg_trigtuple = ExecHeapifySlot(trig_tuple_slot1);
 			LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
 
 			LocTriggerData.tg_newtuple =
 				((evtshared->ats_event & TRIGGER_EVENT_OPMASK) ==
 				 TRIGGER_EVENT_UPDATE) ?
-				ExecMaterializeSlot(trig_tuple_slot2) : NULL;
+				ExecHeapifySlot(trig_tuple_slot2) : NULL;
 			LocTriggerData.tg_newtuplebuf = InvalidBuffer;
 
 			break;
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 2e88417265..f73e5a79fc 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -480,13 +480,15 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			Datum		d;
 
 			/* these asserts must match defenses in slot_getattr */
-			Assert(innerslot->tts_tuple != NULL);
-			Assert(innerslot->tts_tuple != &(innerslot->tts_minhdr));
+			Assert(innerslot->tts_storage != NULL);
+
+			/*
+			 * hari
+			 * Assert(innerslot->tts_storageslotam->slot_is_physical_tuple(innerslot));
+			 */
 
 			/* heap_getsysattr has sufficient defenses against bad attnums */
-			d = heap_getsysattr(innerslot->tts_tuple, attnum,
-								innerslot->tts_tupleDescriptor,
-								op->resnull);
+			d = slot_getattr(innerslot, attnum, op->resnull);
 			*op->resvalue = d;
 
 			EEO_NEXT();
@@ -498,13 +500,14 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			Datum		d;
 
 			/* these asserts must match defenses in slot_getattr */
-			Assert(outerslot->tts_tuple != NULL);
-			Assert(outerslot->tts_tuple != &(outerslot->tts_minhdr));
+			Assert(outerslot->tts_storage != NULL);
 
+			/*
+			 * hari
+			 * Assert(outerslot->tts_storageslotam->slot_is_physical_tuple(outerslot));
+			 */
 			/* heap_getsysattr has sufficient defenses against bad attnums */
-			d = heap_getsysattr(outerslot->tts_tuple, attnum,
-								outerslot->tts_tupleDescriptor,
-								op->resnull);
+			d = slot_getattr(outerslot, attnum, op->resnull);
 			*op->resvalue = d;
 
 			EEO_NEXT();
@@ -516,13 +519,15 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			Datum		d;
 
 			/* these asserts must match defenses in slot_getattr */
-			Assert(scanslot->tts_tuple != NULL);
-			Assert(scanslot->tts_tuple != &(scanslot->tts_minhdr));
+			Assert(scanslot->tts_storage != NULL);
+
+			/*
+			 * hari
+			 * Assert(scanslot->tts_storageslotam->slot_is_physical_tuple(scanslot));
+			 */
 
 			/* heap_getsysattr has sufficient defenses against bad attnums */
-			d = heap_getsysattr(scanslot->tts_tuple, attnum,
-								scanslot->tts_tupleDescriptor,
-								op->resnull);
+			d = slot_getattr(scanslot, attnum, op->resnull);
 			*op->resvalue = d;
 
 			EEO_NEXT();
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 32891abbdf..fba19f4fde 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -171,7 +171,7 @@ retry:
 		HTSU_Result res;
 		HeapTupleData locktup;
 
-		ItemPointerCopy(&outslot->tts_tuple->t_self, &locktup.t_self);
+		ItemPointerCopy(&outslot->tts_tid, &locktup.t_self);
 
 		PushActiveSnapshot(GetLatestSnapshot());
 
@@ -211,59 +211,6 @@ retry:
 	return found;
 }
 
-/*
- * Compare the tuple and slot and check if they have equal values.
- *
- * We use binary datum comparison which might return false negatives but
- * that's the best we can do here as there may be multiple notions of
- * equality for the data types and table columns don't specify which one
- * to use.
- */
-static bool
-tuple_equals_slot(TupleDesc desc, HeapTuple tup, TupleTableSlot *slot)
-{
-	Datum		values[MaxTupleAttributeNumber];
-	bool		isnull[MaxTupleAttributeNumber];
-	int			attrnum;
-
-	heap_deform_tuple(tup, desc, values, isnull);
-
-	/* Check equality of the attributes. */
-	for (attrnum = 0; attrnum < desc->natts; attrnum++)
-	{
-		Form_pg_attribute att;
-		TypeCacheEntry *typentry;
-
-		/*
-		 * If one value is NULL and other is not, then they are certainly not
-		 * equal
-		 */
-		if (isnull[attrnum] != slot->tts_isnull[attrnum])
-			return false;
-
-		/*
-		 * If both are NULL, they can be considered equal.
-		 */
-		if (isnull[attrnum])
-			continue;
-
-		att = TupleDescAttr(desc, attrnum);
-
-		typentry = lookup_type_cache(att->atttypid, TYPECACHE_EQ_OPR_FINFO);
-		if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
-			ereport(ERROR,
-					(errcode(ERRCODE_UNDEFINED_FUNCTION),
-					 errmsg("could not identify an equality operator for type %s",
-							format_type_be(att->atttypid))));
-
-		if (!DatumGetBool(FunctionCall2(&typentry->eq_opr_finfo,
-										values[attrnum],
-										slot->tts_values[attrnum])))
-			return false;
-	}
-
-	return true;
-}
 
 /*
  * Search the relation 'rel' for tuple using the sequential scan.
@@ -279,6 +226,7 @@ bool
 RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 						 TupleTableSlot *searchslot, TupleTableSlot *outslot)
 {
+	TupleTableSlot *scanslot;
 	HeapTuple	scantuple;
 	HeapScanDesc scan;
 	SnapshotData snap;
@@ -292,6 +240,8 @@ RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 	InitDirtySnapshot(snap);
 	scan = heap_beginscan(rel, &snap, 0, NULL);
 
+	scanslot = MakeSingleTupleTableSlot(desc);
+
 retry:
 	found = false;
 
@@ -300,12 +250,12 @@ retry:
 	/* Try to find the tuple */
 	while ((scantuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
 	{
-		if (!tuple_equals_slot(desc, scantuple, searchslot))
+		ExecStoreTuple(scantuple, scanslot, InvalidBuffer, false);
+		if (!ExecSlotCompare(scanslot, searchslot))
 			continue;
 
 		found = true;
-		ExecStoreTuple(scantuple, outslot, InvalidBuffer, false);
-		ExecMaterializeSlot(outslot);
+		ExecCopySlot(outslot, scanslot);
 
 		xwait = TransactionIdIsValid(snap.xmin) ?
 			snap.xmin : snap.xmax;
@@ -329,7 +279,7 @@ retry:
 		HTSU_Result res;
 		HeapTupleData locktup;
 
-		ItemPointerCopy(&outslot->tts_tuple->t_self, &locktup.t_self);
+		ItemPointerCopy(&outslot->tts_tid, &locktup.t_self);
 
 		PushActiveSnapshot(GetLatestSnapshot());
 
@@ -362,6 +312,7 @@ retry:
 	}
 
 	heap_endscan(scan);
+	ExecDropSingleTupleTableSlot(scanslot);
 
 	return found;
 }
@@ -404,7 +355,7 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 			ExecConstraints(resultRelInfo, slot, estate, true);
 
 		/* Store the slot into tuple that we can inspect. */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		/* OK, store the tuple and create index entries for it */
 		simple_heap_insert(rel, tuple);
@@ -442,6 +393,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
+	ItemPointer tid = &(searchslot->tts_tid);
 
 	/* For now we support only tables. */
 	Assert(rel->rd_rel->relkind == RELKIND_RELATION);
@@ -453,7 +405,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 		resultRelInfo->ri_TrigDesc->trig_update_before_row)
 	{
 		slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
-									&searchslot->tts_tuple->t_self,
+									tid,
 									NULL, slot);
 
 		if (slot == NULL)		/* "do nothing" */
@@ -469,21 +421,20 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 			ExecConstraints(resultRelInfo, slot, estate, true);
 
 		/* Store the slot into tuple that we can write. */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		/* OK, update the tuple and index entries for it */
-		simple_heap_update(rel, &searchslot->tts_tuple->t_self,
-						   slot->tts_tuple);
+		simple_heap_update(rel, tid, tuple);
 
 		if (resultRelInfo->ri_NumIndices > 0 &&
-			!HeapTupleIsHeapOnly(slot->tts_tuple))
-			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+			!HeapTupleIsHeapOnly(tuple))
+			recheckIndexes = ExecInsertIndexTuples(slot, tid,
 												   estate, false, NULL,
 												   NIL);
 
 		/* AFTER ROW UPDATE Triggers */
 		ExecARUpdateTriggers(estate, resultRelInfo,
-							 &searchslot->tts_tuple->t_self,
+							 tid,
 							 NULL, tuple, recheckIndexes, NULL);
 
 		list_free(recheckIndexes);
@@ -503,6 +454,7 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate,
 	bool		skip_tuple = false;
 	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
+	ItemPointer tid = &(searchslot->tts_tid);
 
 	/* For now we support only tables. */
 	Assert(rel->rd_rel->relkind == RELKIND_RELATION);
@@ -514,7 +466,7 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate,
 		resultRelInfo->ri_TrigDesc->trig_delete_before_row)
 	{
 		skip_tuple = !ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
-										   &searchslot->tts_tuple->t_self,
+										   tid,
 										   NULL);
 	}
 
@@ -523,11 +475,11 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate,
 		List	   *recheckIndexes = NIL;
 
 		/* OK, delete the tuple */
-		simple_heap_delete(rel, &searchslot->tts_tuple->t_self);
+		simple_heap_delete(rel, tid);
 
 		/* AFTER ROW DELETE Triggers */
 		ExecARDeleteTriggers(estate, resultRelInfo,
-							 &searchslot->tts_tuple->t_self, NULL, NULL);
+							 tid, NULL, NULL);
 
 		list_free(recheckIndexes);
 	}
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 5df89e419c..b0f818f07f 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -82,6 +82,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/tableam_common.h"
 #include "access/tuptoaster.h"
 #include "funcapi.h"
 #include "catalog/pg_type.h"
@@ -113,16 +114,15 @@ MakeTupleTableSlot(void)
 	TupleTableSlot *slot = makeNode(TupleTableSlot);
 
 	slot->tts_isempty = true;
-	slot->tts_shouldFree = false;
-	slot->tts_shouldFreeMin = false;
-	slot->tts_tuple = NULL;
 	slot->tts_tupleDescriptor = NULL;
 	slot->tts_mcxt = CurrentMemoryContext;
-	slot->tts_buffer = InvalidBuffer;
 	slot->tts_nvalid = 0;
 	slot->tts_values = NULL;
 	slot->tts_isnull = NULL;
-	slot->tts_mintuple = NULL;
+	slot->tts_tupleOid = InvalidOid;
+	slot->tts_tableOid = InvalidOid;
+	slot->tts_slottableam = slot_tableam_handler();
+	slot->tts_storage = NULL;
 
 	return slot;
 }
@@ -205,6 +205,54 @@ MakeSingleTupleTableSlot(TupleDesc tupdesc)
 	return slot;
 }
 
+/* --------------------------------
+ *		ExecSlotCompare
+ *
+ *		This is a slot comparision function to find out
+ *		whether both the slots are same or not?
+ * --------------------------------
+ */
+bool
+ExecSlotCompare(TupleTableSlot *slot1, TupleTableSlot *slot2)
+{
+	int			attrnum;
+
+	Assert(slot1->tts_tupleDescriptor->natts == slot2->tts_tupleDescriptor->natts);
+
+	slot_getallattrs(slot1);
+	slot_getallattrs(slot2);
+
+	/* Check equality of the attributes. */
+	for (attrnum = 0; attrnum < slot1->tts_tupleDescriptor->natts; attrnum++)
+	{
+		Form_pg_attribute att;
+		TypeCacheEntry *typentry;
+
+		/*
+		 * If one value is NULL and other is not, then they are certainly not
+		 * equal
+		 */
+		if (slot1->tts_isnull[attrnum] != slot2->tts_isnull[attrnum])
+			return false;
+
+		att = TupleDescAttr(slot1->tts_tupleDescriptor, attrnum);
+
+		typentry = lookup_type_cache(att->atttypid, TYPECACHE_EQ_OPR_FINFO);
+		if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_FUNCTION),
+					 errmsg("could not identify an equality operator for type %s",
+							format_type_be(att->atttypid))));
+
+		if (!DatumGetBool(FunctionCall2(&typentry->eq_opr_finfo,
+										slot1->tts_values[attrnum],
+										slot2->tts_values[attrnum])))
+			return false;
+	}
+
+	return true;
+}
+
 /* --------------------------------
  *		ExecDropSingleTupleTableSlot
  *
@@ -317,7 +365,7 @@ ExecSetSlotDescriptor(TupleTableSlot *slot, /* slot to change */
  * --------------------------------
  */
 TupleTableSlot *
-ExecStoreTuple(HeapTuple tuple,
+ExecStoreTuple(void *tuple,
 			   TupleTableSlot *slot,
 			   Buffer buffer,
 			   bool shouldFree)
@@ -328,47 +376,27 @@ ExecStoreTuple(HeapTuple tuple,
 	Assert(tuple != NULL);
 	Assert(slot != NULL);
 	Assert(slot->tts_tupleDescriptor != NULL);
+	Assert(slot->tts_slottableam != NULL);
 	/* passing shouldFree=true for a tuple on a disk page is not sane */
 	Assert(BufferIsValid(buffer) ? (!shouldFree) : true);
 
 	/*
 	 * Free any old physical tuple belonging to the slot.
 	 */
-	if (slot->tts_shouldFree)
-		heap_freetuple(slot->tts_tuple);
-	if (slot->tts_shouldFreeMin)
-		heap_free_minimal_tuple(slot->tts_mintuple);
+	slot->tts_slottableam->slot_clear_tuple(slot);	/* XXX ?? */
 
 	/*
-	 * Store the new tuple into the specified slot.
+	 * Store the new tuple into the specified slot, and mark the slot as no
+	 * longer empty.  This clears any previously stored physical tuple.
 	 */
+	/* XXX should we pass the buffer down to the storageAM perhaps? */
+	slot->tts_slottableam->slot_store_tuple(slot, tuple, shouldFree, false);
+
 	slot->tts_isempty = false;
-	slot->tts_shouldFree = shouldFree;
-	slot->tts_shouldFreeMin = false;
-	slot->tts_tuple = tuple;
-	slot->tts_mintuple = NULL;
 
 	/* Mark extracted state invalid */
 	slot->tts_nvalid = 0;
 
-	/*
-	 * If tuple is on a disk page, keep the page pinned as long as we hold a
-	 * pointer into it.  We assume the caller already has such a pin.
-	 *
-	 * This is coded to optimize the case where the slot previously held a
-	 * tuple on the same disk page: in that case releasing and re-acquiring
-	 * the pin is a waste of cycles.  This is a common situation during
-	 * seqscans, so it's worth troubling over.
-	 */
-	if (slot->tts_buffer != buffer)
-	{
-		if (BufferIsValid(slot->tts_buffer))
-			ReleaseBuffer(slot->tts_buffer);
-		slot->tts_buffer = buffer;
-		if (BufferIsValid(buffer))
-			IncrBufferRefCount(buffer);
-	}
-
 	return slot;
 }
 
@@ -395,31 +423,19 @@ ExecStoreMinimalTuple(MinimalTuple mtup,
 	/*
 	 * Free any old physical tuple belonging to the slot.
 	 */
-	if (slot->tts_shouldFree)
-		heap_freetuple(slot->tts_tuple);
-	if (slot->tts_shouldFreeMin)
-		heap_free_minimal_tuple(slot->tts_mintuple);
-
-	/*
-	 * Drop the pin on the referenced buffer, if there is one.
-	 */
-	if (BufferIsValid(slot->tts_buffer))
-		ReleaseBuffer(slot->tts_buffer);
-
-	slot->tts_buffer = InvalidBuffer;
+	slot->tts_slottableam->slot_clear_tuple(slot);	/* XXX ?? */
 
 	/*
 	 * Store the new tuple into the specified slot.
 	 */
 	slot->tts_isempty = false;
-	slot->tts_shouldFree = false;
-	slot->tts_shouldFreeMin = shouldFree;
-	slot->tts_tuple = &slot->tts_minhdr;
-	slot->tts_mintuple = mtup;
 
-	slot->tts_minhdr.t_len = mtup->t_len + MINIMAL_TUPLE_OFFSET;
-	slot->tts_minhdr.t_data = (HeapTupleHeader) ((char *) mtup - MINIMAL_TUPLE_OFFSET);
-	/* no need to set t_self or t_tableOid since we won't allow access */
+	/*
+	 * Store the new tuple into the specified slot, and mark the slot as no
+	 * longer empty.  This clears any previously stored physical tuple.
+	 */
+	/* XXX should we pass the buffer down to the storageAM perhaps? */
+	slot->tts_slottableam->slot_store_tuple(slot, mtup, false, true);
 
 	/* Mark extracted state invalid */
 	slot->tts_nvalid = 0;
@@ -444,25 +460,9 @@ ExecClearTuple(TupleTableSlot *slot)	/* slot in which to store tuple */
 	Assert(slot != NULL);
 
 	/*
-	 * Free the old physical tuple if necessary.
+	 * Tell the table AM to release any resource associated with the slot.
 	 */
-	if (slot->tts_shouldFree)
-		heap_freetuple(slot->tts_tuple);
-	if (slot->tts_shouldFreeMin)
-		heap_free_minimal_tuple(slot->tts_mintuple);
-
-	slot->tts_tuple = NULL;
-	slot->tts_mintuple = NULL;
-	slot->tts_shouldFree = false;
-	slot->tts_shouldFreeMin = false;
-
-	/*
-	 * Drop the pin on the referenced buffer, if there is one.
-	 */
-	if (BufferIsValid(slot->tts_buffer))
-		ReleaseBuffer(slot->tts_buffer);
-
-	slot->tts_buffer = InvalidBuffer;
+	slot->tts_slottableam->slot_clear_tuple(slot);
 
 	/*
 	 * Mark it empty.
@@ -541,7 +541,7 @@ ExecStoreAllNullTuple(TupleTableSlot *slot)
  *		however the "system columns" of the result will not be meaningful.
  * --------------------------------
  */
-HeapTuple
+TableTuple
 ExecCopySlotTuple(TupleTableSlot *slot)
 {
 	/*
@@ -550,20 +550,7 @@ ExecCopySlotTuple(TupleTableSlot *slot)
 	Assert(slot != NULL);
 	Assert(!slot->tts_isempty);
 
-	/*
-	 * If we have a physical tuple (either format) then just copy it.
-	 */
-	if (TTS_HAS_PHYSICAL_TUPLE(slot))
-		return heap_copytuple(slot->tts_tuple);
-	if (slot->tts_mintuple)
-		return heap_tuple_from_minimal_tuple(slot->tts_mintuple);
-
-	/*
-	 * Otherwise we need to build a tuple from the Datum array.
-	 */
-	return heap_form_tuple(slot->tts_tupleDescriptor,
-						   slot->tts_values,
-						   slot->tts_isnull);
+	return slot->tts_slottableam->slot_tuple(slot, true);
 }
 
 /* --------------------------------
@@ -582,21 +569,19 @@ ExecCopySlotMinimalTuple(TupleTableSlot *slot)
 	Assert(slot != NULL);
 	Assert(!slot->tts_isempty);
 
-	/*
-	 * If we have a physical tuple then just copy it.  Prefer to copy
-	 * tts_mintuple since that's a tad cheaper.
-	 */
-	if (slot->tts_mintuple)
-		return heap_copy_minimal_tuple(slot->tts_mintuple);
-	if (slot->tts_tuple)
-		return minimal_tuple_from_heap_tuple(slot->tts_tuple);
+	return slot->tts_slottableam->slot_min_tuple(slot, true);
+}
 
+void
+ExecSlotUpdateTupleTableoid(TupleTableSlot *slot, Oid tableoid)
+{
 	/*
-	 * Otherwise we need to build a tuple from the Datum array.
+	 * sanity checks
 	 */
-	return heap_form_minimal_tuple(slot->tts_tupleDescriptor,
-								   slot->tts_values,
-								   slot->tts_isnull);
+	Assert(slot != NULL);
+	Assert(!slot->tts_isempty);
+
+	slot->tts_slottableam->slot_update_tableoid(slot, tableoid);
 }
 
 /* --------------------------------
@@ -614,25 +599,34 @@ ExecCopySlotMinimalTuple(TupleTableSlot *slot)
  * Hence, the result must be treated as read-only.
  * --------------------------------
  */
-HeapTuple
+TableTuple
 ExecFetchSlotTuple(TupleTableSlot *slot)
 {
+	MemoryContext oldContext;
+	TableTuple tup;
+
 	/*
 	 * sanity checks
 	 */
 	Assert(slot != NULL);
 	Assert(!slot->tts_isempty);
 
-	/*
-	 * If we have a regular physical tuple then just return it.
-	 */
-	if (TTS_HAS_PHYSICAL_TUPLE(slot))
-		return slot->tts_tuple;
+	if (slot->tts_shouldFree)
+		return slot->tts_slottableam->slot_tuple(slot, false);
 
 	/*
-	 * Otherwise materialize the slot...
+	 * Otherwise, copy or build a tuple, and store it into the slot.
+	 *
+	 * We may be called in a context that is shorter-lived than the tuple
+	 * slot, but we have to ensure that the materialized tuple will survive
+	 * anyway.
 	 */
-	return ExecMaterializeSlot(slot);
+	oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
+	tup = ExecCopySlotTuple(slot);
+	ExecStoreTuple(tup, slot, InvalidBuffer, true);
+	MemoryContextSwitchTo(oldContext);
+
+	return tup;
 }
 
 /* --------------------------------
@@ -652,6 +646,7 @@ MinimalTuple
 ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 {
 	MemoryContext oldContext;
+	MinimalTuple tup;
 
 	/*
 	 * sanity checks
@@ -659,11 +654,8 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 	Assert(slot != NULL);
 	Assert(!slot->tts_isempty);
 
-	/*
-	 * If we have a minimal physical tuple (local or not) then just return it.
-	 */
-	if (slot->tts_mintuple)
-		return slot->tts_mintuple;
+	if (slot->tts_shouldFreeMin)
+		return slot->tts_slottableam->slot_min_tuple(slot, false);
 
 	/*
 	 * Otherwise, copy or build a minimal tuple, and store it into the slot.
@@ -673,18 +665,11 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 	 * anyway.
 	 */
 	oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
-	slot->tts_mintuple = ExecCopySlotMinimalTuple(slot);
-	slot->tts_shouldFreeMin = true;
+	tup = ExecCopySlotMinimalTuple(slot);
+	ExecStoreMinimalTuple(tup, slot, true);
 	MemoryContextSwitchTo(oldContext);
 
-	/*
-	 * Note: we may now have a situation where we have a local minimal tuple
-	 * attached to a virtual or non-local physical tuple.  There seems no harm
-	 * in that at the moment, but if any materializes, we should change this
-	 * function to force the slot into minimal-tuple-only state.
-	 */
-
-	return slot->tts_mintuple;
+	return tup;
 }
 
 /* --------------------------------
@@ -713,18 +698,19 @@ ExecFetchSlotTupleDatum(TupleTableSlot *slot)
  *			Force a slot into the "materialized" state.
  *
  *		This causes the slot's tuple to be a local copy not dependent on
- *		any external storage.  A pointer to the contained tuple is returned.
+ *		any external storage.
  *
  *		A typical use for this operation is to prepare a computed tuple
  *		for being stored on disk.  The original data may or may not be
  *		virtual, but in any case we need a private copy for heap_insert
- *		to scribble on.
+ *		to scribble on.  XXX is this comment good?
  * --------------------------------
  */
-HeapTuple
+void
 ExecMaterializeSlot(TupleTableSlot *slot)
 {
 	MemoryContext oldContext;
+	HeapTuple	tup;
 
 	/*
 	 * sanity checks
@@ -732,12 +718,8 @@ ExecMaterializeSlot(TupleTableSlot *slot)
 	Assert(slot != NULL);
 	Assert(!slot->tts_isempty);
 
-	/*
-	 * If we have a regular physical tuple, and it's locally palloc'd, we have
-	 * nothing to do.
-	 */
-	if (slot->tts_tuple && slot->tts_shouldFree)
-		return slot->tts_tuple;
+	if (slot->tts_shouldFree)
+		return;
 
 	/*
 	 * Otherwise, copy or build a physical tuple, and store it into the slot.
@@ -747,18 +729,10 @@ ExecMaterializeSlot(TupleTableSlot *slot)
 	 * anyway.
 	 */
 	oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
-	slot->tts_tuple = ExecCopySlotTuple(slot);
-	slot->tts_shouldFree = true;
+	tup = ExecCopySlotTuple(slot);
+	ExecStoreTuple(tup, slot, InvalidBuffer, true);
 	MemoryContextSwitchTo(oldContext);
 
-	/*
-	 * Drop the pin on the referenced buffer, if there is one.
-	 */
-	if (BufferIsValid(slot->tts_buffer))
-		ReleaseBuffer(slot->tts_buffer);
-
-	slot->tts_buffer = InvalidBuffer;
-
 	/*
 	 * Mark extracted state invalid.  This is important because the slot is
 	 * not supposed to depend any more on the previous external data; we
@@ -768,17 +742,15 @@ ExecMaterializeSlot(TupleTableSlot *slot)
 	 * that we have not pfree'd tts_mintuple, if there is one.)
 	 */
 	slot->tts_nvalid = 0;
+}
 
-	/*
-	 * On the same principle of not depending on previous remote storage,
-	 * forget the mintuple if it's not local storage.  (If it is local
-	 * storage, we must not pfree it now, since callers might have already
-	 * fetched datum pointers referencing it.)
-	 */
-	if (!slot->tts_shouldFreeMin)
-		slot->tts_mintuple = NULL;
+TableTuple
+ExecHeapifySlot(TupleTableSlot *slot)
+{
+	ExecMaterializeSlot(slot);
+	Assert(slot->tts_storage != NULL);
 
-	return slot->tts_tuple;
+	return slot->tts_slottableam->slot_tuple(slot, false);
 }
 
 /* --------------------------------
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 59865f5cca..b9cf3037b1 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -62,7 +62,7 @@ ForeignNext(ForeignScanState *node)
 	 */
 	if (plan->fsSystemCol && !TupIsNull(slot))
 	{
-		HeapTuple	tup = ExecMaterializeSlot(slot);
+		HeapTuple	tup = ExecHeapifySlot(slot);
 
 		tup->t_tableOid = RelationGetRelid(node->ss.ss_currentRelation);
 	}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index f816eda9be..068be8b971 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -172,7 +172,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
 		 * initialize t_tableOid before evaluating them.
 		 */
 		Assert(!TupIsNull(econtext->ecxt_scantuple));
-		tuple = ExecMaterializeSlot(econtext->ecxt_scantuple);
+		tuple = ExecHeapifySlot(econtext->ecxt_scantuple);
 		tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
 	}
 	econtext->ecxt_outertuple = planSlot;
@@ -272,7 +272,7 @@ ExecInsert(ModifyTableState *mtstate,
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
 	 */
-	tuple = ExecMaterializeSlot(slot);
+	tuple = ExecHeapifySlot(slot);
 
 	/*
 	 * get information on the (current) result relation
@@ -408,7 +408,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW INSERT Triggers */
@@ -421,7 +421,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		newId = InvalidOid;
 	}
@@ -439,7 +439,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* FDW might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		/*
 		 * AFTER ROW Triggers or RETURNING expressions might reference the
@@ -748,7 +748,7 @@ ExecDelete(ModifyTableState *mtstate,
 		 */
 		if (slot->tts_isempty)
 			ExecStoreAllNullTuple(slot);
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
 	}
 	else
@@ -899,7 +899,7 @@ ldelete:;
 		 * Before releasing the target tuple again, make sure rslot has a
 		 * local copy of any pass-by-reference values.
 		 */
-		ExecMaterializeSlot(rslot);
+		ExecHeapifySlot(rslot);
 
 		ExecClearTuple(slot);
 		if (BufferIsValid(delbuffer))
@@ -960,7 +960,7 @@ ExecUpdate(ModifyTableState *mtstate,
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
 	 */
-	tuple = ExecMaterializeSlot(slot);
+	tuple = ExecHeapifySlot(slot);
 
 	/*
 	 * get information on the (current) result relation
@@ -979,7 +979,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW UPDATE Triggers */
@@ -993,7 +993,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
@@ -1009,7 +1009,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* FDW might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		/*
 		 * AFTER ROW Triggers or RETURNING expressions might reference the
@@ -1125,7 +1125,7 @@ lreplace:;
 					{
 						*tupleid = hufd.ctid;
 						slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
-						tuple = ExecMaterializeSlot(slot);
+						tuple = ExecHeapifySlot(slot);
 						goto lreplace;
 					}
 				}
diff --git a/src/backend/executor/tqueue.c b/src/backend/executor/tqueue.c
index ecdbe7f79f..12b9fef894 100644
--- a/src/backend/executor/tqueue.c
+++ b/src/backend/executor/tqueue.c
@@ -58,7 +58,7 @@ tqueueReceiveSlot(TupleTableSlot *slot, DestReceiver *self)
 	shm_mq_result result;
 
 	/* Send the tuple itself. */
-	tuple = ExecMaterializeSlot(slot);
+	tuple = ExecHeapifySlot(slot);
 	result = shm_mq_send(tqueue->queue, tuple->t_len, tuple->t_data, false);
 
 	/* Check for failure. */
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 83c69092ae..17c711d6bc 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -729,9 +729,12 @@ apply_handle_update(StringInfo s)
 	 */
 	if (found)
 	{
+		HeapTuple	tuple;
+
 		/* Process and store remote tuple in the slot */
 		oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
-		ExecStoreTuple(localslot->tts_tuple, remoteslot, InvalidBuffer, false);
+		tuple = ExecHeapifySlot(localslot);
+		ExecStoreTuple(tuple, remoteslot, InvalidBuffer, false);
 		slot_modify_cstrings(remoteslot, rel, newtup.values, newtup.changed);
 		MemoryContextSwitchTo(oldctx);
 
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index 2ab1815390..b1ceb854cd 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -20,6 +20,19 @@
 #include "access/transam.h"
 #include "storage/bufpage.h"
 
+/*
+ * Opaque tuple representation for executor's TupleTableSlot tts_storage
+ * (XXX This should probably live in a separate header)
+ */
+typedef struct HeapamTuple
+{
+	HeapTuple	hst_heaptuple;
+	bool		hst_slow;
+	long		hst_off;
+	MinimalTuple hst_mintuple;	/* minimal tuple, or NULL if none */
+	HeapTupleData hst_minhdr;	/* workspace for minimal-tuple-only case */
+}			HeapamTuple;
+
 /*
  * MaxTupleAttributeNumber limits the number of (user) columns in a tuple.
  * The key limit on this value is that the size of the fixed overhead for
@@ -658,7 +671,7 @@ struct MinimalTupleData
 /*
  * GETSTRUCT - given a HeapTuple pointer, return address of the user data
  */
-#define GETSTRUCT(TUP) ((char *) ((TUP)->t_data) + (TUP)->t_data->t_hoff)
+#define GETSTRUCT(TUP) ((char *) (((HeapTuple)(TUP))->t_data) + ((HeapTuple)(TUP))->t_data->t_hoff)
 
 /*
  * Accessor macros to be used with HeapTuple pointers.
diff --git a/src/include/access/tableam_common.h b/src/include/access/tableam_common.h
index 78b24d76c7..f4f279a7bc 100644
--- a/src/include/access/tableam_common.h
+++ b/src/include/access/tableam_common.h
@@ -21,6 +21,7 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "access/xlog.h"
+#include "executor/tuptable.h"
 #include "storage/bufpage.h"
 #include "storage/bufmgr.h"
 
@@ -38,4 +39,40 @@ typedef enum
 	HEAPTUPLE_DELETE_IN_PROGRESS	/* deleting xact is still in progress */
 } HTSV_Result;
 
+/*
+ * slot table AM routine functions
+ */
+typedef void (*SlotStoreTuple_function) (TupleTableSlot *slot,
+										 TableTuple tuple,
+										 bool shouldFree,
+										 bool minumumtuple);
+typedef void (*SlotClearTuple_function) (TupleTableSlot *slot);
+typedef Datum (*SlotGetattr_function) (TupleTableSlot *slot,
+									   int attnum, bool *isnull);
+typedef void (*SlotVirtualizeTuple_function) (TupleTableSlot *slot, int16 upto);
+
+typedef HeapTuple (*SlotGetTuple_function) (TupleTableSlot *slot, bool palloc_copy);
+typedef MinimalTuple (*SlotGetMinTuple_function) (TupleTableSlot *slot, bool palloc_copy);
+
+typedef void (*SlotUpdateTableoid_function) (TupleTableSlot *slot, Oid tableoid);
+
+typedef void (*SpeculativeAbort_function) (Relation rel,
+										   TupleTableSlot *slot);
+
+typedef struct SlotTableAmRoutine
+{
+	/* Operations on TupleTableSlot */
+	SlotStoreTuple_function slot_store_tuple;
+	SlotVirtualizeTuple_function slot_virtualize_tuple;
+	SlotClearTuple_function slot_clear_tuple;
+	SlotGetattr_function slot_getattr;
+	SlotGetTuple_function slot_tuple;
+	SlotGetMinTuple_function slot_min_tuple;
+	SlotUpdateTableoid_function slot_update_tableoid;
+}			SlotTableAmRoutine;
+
+typedef SlotTableAmRoutine * (*slot_tableam_hook) (void);
+
+extern SlotTableAmRoutine * slot_tableam_handler(void);
+
 #endif							/* TABLEAM_COMMON_H */
diff --git a/src/include/access/tableamapi.h b/src/include/access/tableamapi.h
index 4bd50b48f1..03d6cd42f3 100644
--- a/src/include/access/tableamapi.h
+++ b/src/include/access/tableamapi.h
@@ -41,6 +41,8 @@ typedef struct TableAmRoutine
 	SnapshotSatisfiesUpdate_function snapshot_satisfiesUpdate;	/* HeapTupleSatisfiesUpdate */
 	SnapshotSatisfiesVacuum_function snapshot_satisfiesVacuum;	/* HeapTupleSatisfiesVacuum */
 
+	slot_tableam_hook slot_storageam;
+
 }			TableAmRoutine;
 
 extern TableAmRoutine * GetTableAmRoutine(Oid amhandler);
diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h
index 5b54834d33..79878c2cb9 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -18,9 +18,25 @@
 #include "access/tupdesc.h"
 #include "storage/buf.h"
 
+/*
+ * Forward declare SlotTableAmRoutine
+ */
+struct SlotTableAmRoutine;
+
+/*
+ * Forward declare TableTuple to avoid including table_common.h here
+ */
+typedef void *TableTuple;
+
 /*----------
  * The executor stores tuples in a "tuple table" which is a List of
- * independent TupleTableSlots.  There are several cases we need to handle:
+ * independent TupleTableSlots.
+ *
+ * XXX The "html-commented out" text below no longer reflects reality, as
+ * physical tuples are now responsibility of table AMs.  But we have kept
+ * "minimal tuples".  Adjust this comment!
+ *
+ * <!-- There are several cases we need to handle:
  *		1. physical tuple in a disk buffer page
  *		2. physical tuple constructed in palloc'ed memory
  *		3. "minimal" physical tuple constructed in palloc'ed memory
@@ -56,6 +72,7 @@
  * had the fatal defect of invalidating any pass-by-reference Datums pointing
  * into the existing slot contents.)  Both copies must contain identical data
  * payloads when this is the case.
+ * -->
  *
  * The Datum/isnull arrays of a TupleTableSlot serve double duty.  When the
  * slot contains a virtual tuple, they are the authoritative data.  When the
@@ -82,11 +99,6 @@
  * When tts_shouldFree is true, the physical tuple is "owned" by the slot
  * and should be freed when the slot's reference to the tuple is dropped.
  *
- * If tts_buffer is not InvalidBuffer, then the slot is holding a pin
- * on the indicated buffer page; drop the pin when we release the
- * slot's reference to that buffer.  (tts_shouldFree should always be
- * false in such a case, since presumably tts_tuple is pointing at the
- * buffer page.)
  *
  * tts_nvalid indicates the number of valid columns in the tts_values/isnull
  * arrays.  When the slot is holding a "virtual" tuple this must be equal
@@ -114,24 +126,21 @@ typedef struct TupleTableSlot
 {
 	NodeTag		type;
 	bool		tts_isempty;	/* true = slot is empty */
-	bool		tts_shouldFree; /* should pfree tts_tuple? */
-	bool		tts_shouldFreeMin;	/* should pfree tts_mintuple? */
-	bool		tts_slow;		/* saved state for slot_deform_tuple */
-	HeapTuple	tts_tuple;		/* physical tuple, or NULL if virtual */
+	ItemPointerData tts_tid;	/* XXX describe */
 	TupleDesc	tts_tupleDescriptor;	/* slot's tuple descriptor */
 	MemoryContext tts_mcxt;		/* slot itself is in this context */
-	Buffer		tts_buffer;		/* tuple's buffer, or InvalidBuffer */
+	Oid			tts_tableOid;	/* XXX describe */
+	Oid			tts_tupleOid;	/* XXX describe */
 	int			tts_nvalid;		/* # of valid values in tts_values */
+	uint32		tts_speculativeToken;	/* XXX describe */
+	bool		tts_shouldFree;
+	bool		tts_shouldFreeMin;
 	Datum	   *tts_values;		/* current per-attribute values */
 	bool	   *tts_isnull;		/* current per-attribute isnull flags */
-	MinimalTuple tts_mintuple;	/* minimal tuple, or NULL if none */
-	HeapTupleData tts_minhdr;	/* workspace for minimal-tuple-only case */
-	long		tts_off;		/* saved state for slot_deform_tuple */
+	struct SlotTableAmRoutine *tts_slottableam; /* table AM */
+	void	   *tts_storage;	/* table AM's opaque space */
 } TupleTableSlot;
 
-#define TTS_HAS_PHYSICAL_TUPLE(slot)  \
-	((slot)->tts_tuple != NULL && (slot)->tts_tuple != &((slot)->tts_minhdr))
-
 /*
  * TupIsNull -- is a TupleTableSlot empty?
  */
@@ -143,9 +152,10 @@ extern TupleTableSlot *MakeTupleTableSlot(void);
 extern TupleTableSlot *ExecAllocTableSlot(List **tupleTable);
 extern void ExecResetTupleTable(List *tupleTable, bool shouldFree);
 extern TupleTableSlot *MakeSingleTupleTableSlot(TupleDesc tupdesc);
+extern bool ExecSlotCompare(TupleTableSlot *slot1, TupleTableSlot *slot2);
 extern void ExecDropSingleTupleTableSlot(TupleTableSlot *slot);
 extern void ExecSetSlotDescriptor(TupleTableSlot *slot, TupleDesc tupdesc);
-extern TupleTableSlot *ExecStoreTuple(HeapTuple tuple,
+extern TupleTableSlot *ExecStoreTuple(void *tuple,
 			   TupleTableSlot *slot,
 			   Buffer buffer,
 			   bool shouldFree);
@@ -155,12 +165,14 @@ extern TupleTableSlot *ExecStoreMinimalTuple(MinimalTuple mtup,
 extern TupleTableSlot *ExecClearTuple(TupleTableSlot *slot);
 extern TupleTableSlot *ExecStoreVirtualTuple(TupleTableSlot *slot);
 extern TupleTableSlot *ExecStoreAllNullTuple(TupleTableSlot *slot);
-extern HeapTuple ExecCopySlotTuple(TupleTableSlot *slot);
+extern TableTuple ExecCopySlotTuple(TupleTableSlot *slot);
 extern MinimalTuple ExecCopySlotMinimalTuple(TupleTableSlot *slot);
-extern HeapTuple ExecFetchSlotTuple(TupleTableSlot *slot);
+extern void ExecSlotUpdateTupleTableoid(TupleTableSlot *slot, Oid tableoid);
+extern TableTuple ExecFetchSlotTuple(TupleTableSlot *slot);
 extern MinimalTuple ExecFetchSlotMinimalTuple(TupleTableSlot *slot);
 extern Datum ExecFetchSlotTupleDatum(TupleTableSlot *slot);
-extern HeapTuple ExecMaterializeSlot(TupleTableSlot *slot);
+extern void ExecMaterializeSlot(TupleTableSlot *slot);
+extern TableTuple ExecHeapifySlot(TupleTableSlot *slot);
 extern TupleTableSlot *ExecCopySlot(TupleTableSlot *dstslot,
 			 TupleTableSlot *srcslot);
 
-- 
2.15.0.windows.1



  [application/octet-stream] 0006-Tuple-Insert-API-is-added-to-table-AM.patch (92.8K, ../../CAJrrPGdY_hgvu06R_vCibUktKRLp=b8nBfeZkh=KRc95tq4euA@mail.gmail.com/11-0006-Tuple-Insert-API-is-added-to-table-AM.patch)
  download | inline diff:
From e7b0bde744f63350675e5801f390c0f5041bcf61 Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Tue, 9 Jan 2018 16:40:34 +1100
Subject: [PATCH 06/12] Tuple Insert API is added to table AM

heap_insert, heap_delete, heap_fetch, heap_update,
heap_get_latest_oid, heap_lock_tuple and heap_multi_insert
functions are added to table AM. Move the index insertion
logic into table AM, The Index insert still outside for
the case of multi_insert (Yet to change). In case of delete
also, the index tuple delete function pointer is avaiable.

Replaced the usage of HeapTuple with storageTuple in
some places, increased the use of slot.
---
 src/backend/access/common/heaptuple.c       |  24 +++
 src/backend/access/heap/heapam.c            |  31 +--
 src/backend/access/heap/heapam_handler.c    | 285 ++++++++++++++++++++++++++++
 src/backend/access/heap/heapam_visibility.c |   3 +
 src/backend/access/heap/rewriteheap.c       |   5 +-
 src/backend/access/heap/tuptoaster.c        |   9 +-
 src/backend/access/table/Makefile           |   2 +-
 src/backend/access/table/tableam.c          | 132 +++++++++++++
 src/backend/commands/copy.c                 |  39 ++--
 src/backend/commands/createas.c             |  24 ++-
 src/backend/commands/matview.c              |  22 ++-
 src/backend/commands/tablecmds.c            |   6 +-
 src/backend/commands/trigger.c              |  50 ++---
 src/backend/executor/execIndexing.c         |   2 +-
 src/backend/executor/execMain.c             | 131 +++++++------
 src/backend/executor/execReplication.c      |  72 +++----
 src/backend/executor/nodeLockRows.c         |  47 +++--
 src/backend/executor/nodeModifyTable.c      | 244 +++++++++++-------------
 src/backend/executor/nodeTidscan.c          |  23 +--
 src/backend/utils/adt/tid.c                 |   5 +-
 src/include/access/heapam.h                 |   8 +-
 src/include/access/htup_details.h           |   1 +
 src/include/access/tableam.h                |  84 ++++++++
 src/include/access/tableam_common.h         |   3 -
 src/include/access/tableamapi.h             |  73 ++++++-
 src/include/commands/trigger.h              |   2 +-
 src/include/executor/executor.h             |  15 +-
 src/include/executor/tuptable.h             |   1 +
 src/include/nodes/execnodes.h               |   8 +-
 29 files changed, 971 insertions(+), 380 deletions(-)
 create mode 100644 src/backend/access/table/tableam.c
 create mode 100644 src/include/access/tableam.h

diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 454294fd3e..ffb829cf76 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -685,6 +685,30 @@ heap_copy_tuple_as_datum(HeapTuple tuple, TupleDesc tupleDesc)
 	return PointerGetDatum(td);
 }
 
+/*
+ * heap_form_tuple_by_datum
+ *		construct a tuple from the given dataum
+ *
+ * The result is allocated in the current memory context.
+ */
+HeapTuple
+heap_form_tuple_by_datum(Datum data, Oid tableoid)
+{
+	HeapTuple	newTuple;
+	HeapTupleHeader td;
+
+	td = DatumGetHeapTupleHeader(data);
+
+	newTuple = (HeapTuple) palloc(HEAPTUPLESIZE + HeapTupleHeaderGetDatumLength(td));
+	newTuple->t_len = HeapTupleHeaderGetDatumLength(td);
+	newTuple->t_self = td->t_ctid;
+	newTuple->t_tableOid = tableoid;
+	newTuple->t_data = (HeapTupleHeader) ((char *) newTuple + HEAPTUPLESIZE);
+	memcpy((char *) newTuple->t_data, (char *) td, newTuple->t_len);
+
+	return newTuple;
+}
+
 /*
  * heap_form_tuple
  *		construct a tuple from the given values[] and isnull[] arrays,
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 9fb2ecad25..a74b96d6cd 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -1893,13 +1893,13 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
  */
 bool
 heap_fetch(Relation relation,
+		   ItemPointer tid,
 		   Snapshot snapshot,
 		   HeapTuple tuple,
 		   Buffer *userbuf,
 		   bool keep_buf,
 		   Relation stats_relation)
 {
-	ItemPointer tid = &(tuple->t_self);
 	ItemId		lp;
 	Buffer		buffer;
 	Page		page;
@@ -1933,7 +1933,6 @@ heap_fetch(Relation relation,
 			ReleaseBuffer(buffer);
 			*userbuf = InvalidBuffer;
 		}
-		tuple->t_data = NULL;
 		return false;
 	}
 
@@ -1955,13 +1954,13 @@ heap_fetch(Relation relation,
 			ReleaseBuffer(buffer);
 			*userbuf = InvalidBuffer;
 		}
-		tuple->t_data = NULL;
 		return false;
 	}
 
 	/*
-	 * fill in *tuple fields
+	 * fill in tuple fields and place it in stuple
 	 */
+	ItemPointerCopy(tid, &(tuple->t_self));
 	tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
 	tuple->t_len = ItemIdGetLength(lp);
 	tuple->t_tableOid = RelationGetRelid(relation);
@@ -2312,7 +2311,6 @@ heap_get_latest_tid(Relation relation,
 	}							/* end of loop */
 }
 
-
 /*
  * UpdateXmaxHintBits - update tuple hint bits after xmax transaction ends
  *
@@ -4619,7 +4617,7 @@ heap_lock_tuple(Relation relation, HeapTuple tuple,
 	tuple->t_tableOid = RelationGetRelid(relation);
 
 l3:
-	result = relation->rd_tableamroutine->snapshot_satisfiesUpdate(tuple, cid, *buffer);
+	result = HeapTupleSatisfiesUpdate(tuple, cid, *buffer);
 
 	if (result == HeapTupleInvisible)
 	{
@@ -4912,7 +4910,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))
 		{
 			LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
 			goto failed;
@@ -5683,9 +5681,8 @@ heap_lock_updated_tuple_rec(Relation rel, ItemPointer tid, TransactionId xid,
 		new_infomask = 0;
 		new_xmax = InvalidTransactionId;
 		block = ItemPointerGetBlockNumber(&tupid);
-		ItemPointerCopy(&tupid, &(mytup.t_self));
 
-		if (!heap_fetch(rel, SnapshotAny, &mytup, &buf, false, NULL))
+		if (!heap_fetch(rel, &tupid, SnapshotAny, &mytup, &buf, false, NULL))
 		{
 			/*
 			 * if we fail to find the updated version of the tuple, it's
@@ -6032,14 +6029,18 @@ heap_lock_updated_tuple(Relation rel, HeapTuple tuple, ItemPointer ctid,
  * An explicit confirmation WAL record also makes logical decoding simpler.
  */
 void
-heap_finish_speculative(Relation relation, HeapTuple tuple)
+heap_finish_speculative(Relation relation, TupleTableSlot *slot)
 {
+	HeapamTuple *stuple = (HeapamTuple *) slot->tts_storage;
+	HeapTuple	tuple = stuple->hst_heaptuple;
 	Buffer		buffer;
 	Page		page;
 	OffsetNumber offnum;
 	ItemId		lp = NULL;
 	HeapTupleHeader htup;
 
+	Assert(slot->tts_speculativeToken != 0);
+
 	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&(tuple->t_self)));
 	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 	page = (Page) BufferGetPage(buffer);
@@ -6094,6 +6095,7 @@ heap_finish_speculative(Relation relation, HeapTuple tuple)
 	END_CRIT_SECTION();
 
 	UnlockReleaseBuffer(buffer);
+	slot->tts_speculativeToken = 0;
 }
 
 /*
@@ -6123,8 +6125,10 @@ heap_finish_speculative(Relation relation, HeapTuple tuple)
  * confirmation records.
  */
 void
-heap_abort_speculative(Relation relation, HeapTuple tuple)
+heap_abort_speculative(Relation relation, TupleTableSlot *slot)
 {
+	HeapamTuple *stuple = (HeapamTuple *) slot->tts_storage;
+	HeapTuple	tuple = stuple->hst_heaptuple;
 	TransactionId xid = GetCurrentTransactionId();
 	ItemPointer tid = &(tuple->t_self);
 	ItemId		lp;
@@ -6133,6 +6137,10 @@ heap_abort_speculative(Relation relation, HeapTuple tuple)
 	BlockNumber block;
 	Buffer		buffer;
 
+	/*
+	 * Assert(slot->tts_speculativeToken != 0); This needs some update in
+	 * toast
+	 */
 	Assert(ItemPointerIsValid(tid));
 
 	block = ItemPointerGetBlockNumber(tid);
@@ -6246,6 +6254,7 @@ heap_abort_speculative(Relation relation, HeapTuple tuple)
 
 	/* count deletion, as we counted the insertion too */
 	pgstat_count_heap_delete(relation);
+	slot->tts_speculativeToken = 0;
 }
 
 /*
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 96daa6a5ef..0470b8639b 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -22,9 +22,282 @@
 
 #include "access/heapam.h"
 #include "access/tableamapi.h"
+#include "storage/lmgr.h"
 #include "utils/builtins.h"
+#include "utils/rel.h"
+#include "utils/tqual.h"
 
 
+/* ----------------------------------------------------------------
+ *				storage AM support routines for heapam
+ * ----------------------------------------------------------------
+ */
+
+static bool
+heapam_fetch(Relation relation,
+			 ItemPointer tid,
+			 Snapshot snapshot,
+			 TableTuple * stuple,
+			 Buffer *userbuf,
+			 bool keep_buf,
+			 Relation stats_relation)
+{
+	HeapTupleData tuple;
+
+	*stuple = NULL;
+	if (heap_fetch(relation, tid, snapshot, &tuple, userbuf, keep_buf, stats_relation))
+	{
+		*stuple = heap_copytuple(&tuple);
+		return true;
+	}
+
+	return false;
+}
+
+/*
+ * Insert a heap tuple from a slot, which may contain an OID and speculative
+ * insertion token.
+ */
+static Oid
+heapam_heap_insert(Relation relation, TupleTableSlot *slot, CommandId cid,
+				   int options, BulkInsertState bistate, InsertIndexTuples IndexFunc,
+				   EState *estate, List *arbiterIndexes, List **recheckIndexes)
+{
+	Oid			oid;
+	HeapTuple	tuple = NULL;
+
+	if (slot->tts_storage)
+	{
+		HeapamTuple *htuple = slot->tts_storage;
+
+		tuple = htuple->hst_heaptuple;
+
+		if (relation->rd_rel->relhasoids)
+			HeapTupleSetOid(tuple, InvalidOid);
+	}
+	else
+	{
+		/*
+		 * Obtain the physical tuple to insert, building from the slot values.
+		 * XXX: maybe the slot already contains a physical tuple in the right
+		 * format?  In fact, if the slot isn't fully deformed, this is
+		 * completely bogus ...
+		 */
+		tuple = heap_form_tuple(slot->tts_tupleDescriptor,
+								slot->tts_values,
+								slot->tts_isnull);
+	}
+
+	/* Set the OID, if the slot has one */
+	if (slot->tts_tupleOid != InvalidOid)
+		HeapTupleHeaderSetOid(tuple->t_data, slot->tts_tupleOid);
+
+	/* Update the tuple with table oid */
+	if (slot->tts_tableOid != InvalidOid)
+		tuple->t_tableOid = slot->tts_tableOid;
+
+	/* Set the speculative insertion token, if the slot has one */
+	if ((options & HEAP_INSERT_SPECULATIVE) && slot->tts_speculativeToken)
+		HeapTupleHeaderSetSpeculativeToken(tuple->t_data, slot->tts_speculativeToken);
+
+	/* Perform the insertion, and copy the resulting ItemPointer */
+	oid = heap_insert(relation, tuple, cid, options, bistate);
+	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
+
+	if (slot->tts_storage == NULL)
+		ExecStoreTuple(tuple, slot, InvalidBuffer, true);
+
+	if ((estate != NULL) && (estate->es_result_relation_info->ri_NumIndices > 0))
+	{
+		Assert(IndexFunc != NULL);
+
+		if (options & HEAP_INSERT_SPECULATIVE)
+		{
+			bool		specConflict = false;
+
+			*recheckIndexes = (IndexFunc) (slot, estate, true,
+										   &specConflict,
+										   arbiterIndexes);
+
+			/* adjust the tuple's state accordingly */
+			if (!specConflict)
+				heap_finish_speculative(relation, slot);
+			else
+			{
+				heap_abort_speculative(relation, slot);
+				slot->tts_specConflict = true;
+			}
+		}
+		else
+		{
+			*recheckIndexes = (IndexFunc) (slot, estate, false,
+										   NULL, arbiterIndexes);
+		}
+	}
+
+	return oid;
+}
+
+static HTSU_Result
+heapam_heap_delete(Relation relation, ItemPointer tid, CommandId cid,
+				   Snapshot crosscheck, bool wait, DeleteIndexTuples IndexFunc,
+				   HeapUpdateFailureData *hufd)
+{
+	/*
+	 * Currently Deleting of index tuples are handled at vacuum, in case
+	 * if the storage itself is cleaning the dead tuples by itself, it is
+	 * the time to call the index tuple deletion also.
+	 */
+	return heap_delete(relation, tid, cid, crosscheck, wait, hufd);
+}
+
+
+/*
+ * Locks tuple and fetches its newest version and TID.
+ *
+ *	relation - table containing tuple
+ *	*tid - TID of tuple to lock (rest of struct need not be valid)
+ *	snapshot - snapshot indentifying required version (used for assert check only)
+ *	*stuple - tuple to be returned
+ *	cid - current command ID (used for visibility test, and stored into
+ *		  tuple's cmax if lock is successful)
+ *	mode - indicates if shared or exclusive tuple lock is desired
+ *	wait_policy - what to do if tuple lock is not available
+ *	flags – indicating how do we handle updated tuples
+ *	*hufd - filled in failure cases
+ *
+ * Function result may be:
+ *	HeapTupleMayBeUpdated: lock was successfully acquired
+ *	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
+ *	HeapTupleWouldBlock: lock couldn't be acquired and wait_policy is skip
+ *
+ * In the failure cases other than HeapTupleInvisible, the routine fills
+ * *hufd with the tuple's t_ctid, t_xmax (resolving a possible MultiXact,
+ * if necessary), and t_cmax (the last only for HeapTupleSelfUpdated,
+ * since we cannot obtain cmax from a combocid generated by another
+ * transaction).
+ * 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,
+				HeapUpdateFailureData *hufd)
+{
+	HTSU_Result		result;
+	HeapTupleData	tuple;
+
+	Assert(stuple != NULL);
+	*stuple = NULL;
+
+	tuple.t_self = *tid;
+	result = heap_lock_tuple(relation, &tuple, cid, mode, wait_policy, follow_updates, buffer, hufd);
+
+	*stuple = heap_copytuple(&tuple);
+
+	return result;
+}
+
+
+static HTSU_Result
+heapam_heap_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
+				   EState *estate, CommandId cid, Snapshot crosscheck,
+				   bool wait, HeapUpdateFailureData *hufd, LockTupleMode *lockmode,
+				   InsertIndexTuples IndexFunc, List **recheckIndexes)
+{
+	HeapTuple	tuple;
+	HTSU_Result result;
+
+	if (slot->tts_storage)
+	{
+		HeapamTuple *htuple = slot->tts_storage;
+
+		tuple = htuple->hst_heaptuple;
+	}
+	else
+	{
+		tuple = heap_form_tuple(slot->tts_tupleDescriptor,
+								slot->tts_values,
+								slot->tts_isnull);
+	}
+
+	/* Set the OID, if the slot has one */
+	if (slot->tts_tupleOid != InvalidOid)
+		HeapTupleHeaderSetOid(tuple->t_data, slot->tts_tupleOid);
+
+	/* Update the tuple with table oid */
+	if (slot->tts_tableOid != InvalidOid)
+		tuple->t_tableOid = slot->tts_tableOid;
+
+	result = heap_update(relation, otid, tuple, cid, crosscheck, wait,
+						 hufd, lockmode);
+	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
+
+	if (slot->tts_storage == NULL)
+		ExecStoreTuple(tuple, slot, InvalidBuffer, true);
+
+	/*
+	 * Note: instead of having to update the old index tuples associated with
+	 * the heap tuple, all we do is form and insert new index tuples. This is
+	 * because UPDATEs are actually DELETEs and INSERTs, and index tuple
+	 * deletion is done later by VACUUM (see notes in ExecDelete). All we do
+	 * here is insert new index tuples.  -cim 9/27/89
+	 */
+
+	/*
+	 * insert index entries for tuple
+	 *
+	 * Note: heap_update returns the tid (location) of the new tuple in the
+	 * t_self field.
+	 *
+	 * If it's a HOT update, we mustn't insert new index entries.
+	 */
+	if ((result == HeapTupleMayBeUpdated) &&
+		((estate != NULL) && (estate->es_result_relation_info->ri_NumIndices > 0)) &&
+		(!HeapTupleIsHeapOnly(tuple)))
+		*recheckIndexes = (IndexFunc) (slot, estate, false, NULL, NIL);
+
+	return result;
+}
+
+static tuple_data
+heapam_get_tuple_data(TableTuple tuple, tuple_data_flags flags)
+{
+	tuple_data	result;
+
+	switch (flags)
+	{
+		case XMIN:
+			result.xid = HeapTupleHeaderGetXmin(((HeapTuple) tuple)->t_data);
+			break;
+		case UPDATED_XID:
+			result.xid = HeapTupleHeaderGetUpdateXid(((HeapTuple) tuple)->t_data);
+			break;
+		case CMIN:
+			result.cid = HeapTupleHeaderGetCmin(((HeapTuple) tuple)->t_data);
+			break;
+		case TID:
+			result.tid = ((HeapTuple) tuple)->t_self;
+			break;
+		case CTID:
+			result.tid = ((HeapTuple) tuple)->t_data->t_ctid;
+			break;
+		default:
+			Assert(0);
+			break;
+	}
+
+	return result;
+}
+
+static TableTuple
+heapam_form_tuple_by_datum(Datum data, Oid tableoid)
+{
+	return heap_form_tuple_by_datum(data, tableoid);
+}
+
 Datum
 heap_tableam_handler(PG_FUNCTION_ARGS)
 {
@@ -37,5 +310,17 @@ heap_tableam_handler(PG_FUNCTION_ARGS)
 
 	amroutine->slot_storageam = slot_tableam_handler;
 
+	amroutine->tuple_fetch = heapam_fetch;
+	amroutine->tuple_insert = heapam_heap_insert;
+	amroutine->tuple_delete = heapam_heap_delete;
+	amroutine->tuple_update = heapam_heap_update;
+	amroutine->tuple_lock = heapam_lock_tuple;
+	amroutine->multi_insert = heap_multi_insert;
+
+	amroutine->get_tuple_data = heapam_get_tuple_data;
+	amroutine->tuple_from_datum = heapam_form_tuple_by_datum;
+	amroutine->tuple_get_latest_tid = heap_get_latest_tid;
+	amroutine->relation_sync = heap_sync;
+
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/heap/heapam_visibility.c b/src/backend/access/heap/heapam_visibility.c
index c45575f049..9051d4be88 100644
--- a/src/backend/access/heap/heapam_visibility.c
+++ b/src/backend/access/heap/heapam_visibility.c
@@ -115,6 +115,9 @@ static inline void
 SetHintBits(HeapTupleHeader tuple, Buffer buffer,
 			uint16 infomask, TransactionId xid)
 {
+	if (!BufferIsValid(buffer))
+		return;
+
 	if (TransactionIdIsValid(xid))
 	{
 		/* NB: xid must be known committed here! */
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 7d466c2588..0de6f88076 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -110,6 +110,7 @@
 #include "access/heapam.h"
 #include "access/heapam_xlog.h"
 #include "access/rewriteheap.h"
+#include "access/tableam.h"
 #include "access/transam.h"
 #include "access/tuptoaster.h"
 #include "access/xact.h"
@@ -126,13 +127,13 @@
 
 #include "storage/bufmgr.h"
 #include "storage/fd.h"
+#include "storage/procarray.h"
 #include "storage/smgr.h"
 
 #include "utils/memutils.h"
 #include "utils/rel.h"
 #include "utils/tqual.h"
 
-#include "storage/procarray.h"
 
 /*
  * State associated with a rewrite operation. This is opaque to the user
@@ -357,7 +358,7 @@ end_heap_rewrite(RewriteState state)
 	 * wrote before the checkpoint.
 	 */
 	if (RelationNeedsWAL(state->rs_new_rel))
-		heap_sync(state->rs_new_rel);
+		table_sync(state->rs_new_rel);
 
 	logical_end_heap_rewrite(state);
 
diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c
index 546f80f05c..cbc9c41ca0 100644
--- a/src/backend/access/heap/tuptoaster.c
+++ b/src/backend/access/heap/tuptoaster.c
@@ -32,6 +32,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/tableam.h"
 #include "access/tuptoaster.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
@@ -1777,7 +1778,13 @@ toast_delete_datum(Relation rel, Datum value, bool is_speculative)
 		 * Have a chunk, delete it
 		 */
 		if (is_speculative)
-			heap_abort_speculative(toastrel, toasttup);
+		{
+			TupleTableSlot *slot = MakeSingleTupleTableSlot(RelationGetDescr(toastrel));
+
+			ExecStoreTuple(toasttup, slot, InvalidBuffer, false);
+			heap_abort_speculative(toastrel, slot);
+			ExecDropSingleTupleTableSlot(slot);
+		}
 		else
 			simple_heap_delete(toastrel, &toasttup->t_self);
 	}
diff --git a/src/backend/access/table/Makefile b/src/backend/access/table/Makefile
index ff0989ed24..fe22bf9208 100644
--- a/src/backend/access/table/Makefile
+++ b/src/backend/access/table/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/table
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = tableamapi.o tableam_common.o
+OBJS = tableam.o tableamapi.o tableam_common.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
new file mode 100644
index 0000000000..eaeb888b55
--- /dev/null
+++ b/src/backend/access/table/tableam.c
@@ -0,0 +1,132 @@
+/*-------------------------------------------------------------------------
+ *
+ * tableam.c
+ *	  table access method code
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/table/tableam.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/tableam.h"
+#include "access/tableamapi.h"
+#include "utils/rel.h"
+
+/*
+ *	table_fetch		- retrieve tuple with given tid
+ */
+bool
+table_fetch(Relation relation,
+			  ItemPointer tid,
+			  Snapshot snapshot,
+			  TableTuple * stuple,
+			  Buffer *userbuf,
+			  bool keep_buf,
+			  Relation stats_relation)
+{
+	return relation->rd_tableamroutine->tuple_fetch(relation, tid, snapshot, stuple,
+												 userbuf, keep_buf, stats_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)
+{
+	return relation->rd_tableamroutine->tuple_lock(relation, tid, stuple,
+												cid, mode, wait_policy,
+												follow_updates, buffer, hufd);
+}
+
+/*
+ * Insert a tuple from a slot into table AM routine
+ */
+Oid
+table_insert(Relation relation, TupleTableSlot *slot, CommandId cid,
+			   int options, BulkInsertState bistate, InsertIndexTuples IndexFunc,
+			   EState *estate, List *arbiterIndexes, List **recheckIndexes)
+{
+	return relation->rd_tableamroutine->tuple_insert(relation, slot, cid, options,
+												  bistate, IndexFunc, estate,
+												  arbiterIndexes, recheckIndexes);
+}
+
+/*
+ * Delete a tuple from tid using table AM routine
+ */
+HTSU_Result
+table_delete(Relation relation, ItemPointer tid, CommandId cid,
+			   Snapshot crosscheck, bool wait, DeleteIndexTuples IndexFunc,
+			   HeapUpdateFailureData *hufd)
+{
+	return relation->rd_tableamroutine->tuple_delete(relation, tid, cid,
+												  crosscheck, wait, IndexFunc, hufd);
+}
+
+/*
+ * update a tuple from tid using table AM routine
+ */
+HTSU_Result
+table_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
+			   EState *estate, CommandId cid, Snapshot crosscheck, bool wait,
+			   HeapUpdateFailureData *hufd, LockTupleMode *lockmode,
+			   InsertIndexTuples IndexFunc, List **recheckIndexes)
+{
+	return relation->rd_tableamroutine->tuple_update(relation, otid, slot, estate,
+												  cid, crosscheck, wait, hufd,
+												  lockmode, IndexFunc, recheckIndexes);
+}
+
+
+/*
+ *	table_multi_insert	- insert multiple tuple into a table
+ */
+void
+table_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
+					 CommandId cid, int options, BulkInsertState bistate)
+{
+	relation->rd_tableamroutine->multi_insert(relation, tuples, ntuples,
+										   cid, options, bistate);
+}
+
+tuple_data
+table_tuple_get_data(Relation relation, TableTuple tuple, tuple_data_flags flags)
+{
+	return relation->rd_tableamroutine->get_tuple_data(tuple, flags);
+}
+
+TableTuple
+table_tuple_by_datum(Relation relation, Datum data, Oid tableoid)
+{
+	if (relation)
+		return relation->rd_tableamroutine->tuple_from_datum(data, tableoid);
+	else
+		return heap_form_tuple_by_datum(data, tableoid);
+}
+
+void
+table_get_latest_tid(Relation relation,
+					   Snapshot snapshot,
+					   ItemPointer tid)
+{
+	relation->rd_tableamroutine->tuple_get_latest_tid(relation, snapshot, tid);
+}
+
+/*
+ *	table_sync		- sync a heap, for use when no WAL has been written
+ */
+void
+table_sync(Relation rel)
+{
+	rel->rd_tableamroutine->relation_sync(rel);
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index dbf59e9d69..245766e7cb 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -20,6 +20,7 @@
 
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/tableam.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "access/xlog.h"
@@ -2699,8 +2700,6 @@ CopyFrom(CopyState cstate)
 
 			if (slot == NULL)	/* "do nothing" */
 				skip_tuple = true;
-			else				/* trigger might have changed tuple */
-				tuple = ExecHeapifySlot(slot);
 		}
 
 		if (!skip_tuple)
@@ -2763,19 +2762,11 @@ CopyFrom(CopyState cstate)
 					List	   *recheckIndexes = NIL;
 
 					/* OK, store the tuple and create index entries for it */
-					heap_insert(resultRelInfo->ri_RelationDesc, tuple, mycid,
-								hi_options, bistate);
-
-					if (resultRelInfo->ri_NumIndices > 0)
-						recheckIndexes = ExecInsertIndexTuples(slot,
-															   &(tuple->t_self),
-															   estate,
-															   false,
-															   NULL,
-															   NIL);
+					table_insert(resultRelInfo->ri_RelationDesc, slot, mycid, hi_options,
+								   bistate, ExecInsertIndexTuples, estate, NIL, &recheckIndexes);
 
 					/* AFTER ROW INSERT Triggers */
-					ExecARInsertTriggers(estate, resultRelInfo, tuple,
+					ExecARInsertTriggers(estate, resultRelInfo, slot,
 										 recheckIndexes, cstate->transition_capture);
 
 					list_free(recheckIndexes);
@@ -2845,7 +2836,7 @@ CopyFrom(CopyState cstate)
 	 * indexes since those use WAL anyway)
 	 */
 	if (hi_options & HEAP_INSERT_SKIP_WAL)
-		heap_sync(cstate->rel);
+		table_sync(cstate->rel);
 
 	return processed;
 }
@@ -2878,12 +2869,12 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
 	 * before calling it.
 	 */
 	oldcontext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
-	heap_multi_insert(cstate->rel,
-					  bufferedTuples,
-					  nBufferedTuples,
-					  mycid,
-					  hi_options,
-					  bistate);
+	table_multi_insert(cstate->rel,
+						 bufferedTuples,
+						 nBufferedTuples,
+						 mycid,
+						 hi_options,
+						 bistate);
 	MemoryContextSwitchTo(oldcontext);
 
 	/*
@@ -2899,10 +2890,9 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
 			cstate->cur_lineno = firstBufferedLineNo + i;
 			ExecStoreTuple(bufferedTuples[i], myslot, InvalidBuffer, false);
 			recheckIndexes =
-				ExecInsertIndexTuples(myslot, &(bufferedTuples[i]->t_self),
-									  estate, false, NULL, NIL);
+				ExecInsertIndexTuples(myslot, estate, false, NULL, NIL);
 			ExecARInsertTriggers(estate, resultRelInfo,
-								 bufferedTuples[i],
+								 myslot,
 								 recheckIndexes, cstate->transition_capture);
 			list_free(recheckIndexes);
 		}
@@ -2919,8 +2909,9 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
 		for (i = 0; i < nBufferedTuples; i++)
 		{
 			cstate->cur_lineno = firstBufferedLineNo + i;
+			ExecStoreTuple(bufferedTuples[i], myslot, InvalidBuffer, false);
 			ExecARInsertTriggers(estate, resultRelInfo,
-								 bufferedTuples[i],
+								 myslot,
 								 NIL, cstate->transition_capture);
 		}
 	}
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index ff2b7b75e9..9c531b7f28 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -26,6 +26,7 @@
 
 #include "access/reloptions.h"
 #include "access/htup_details.h"
+#include "access/tableam.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "access/xlog.h"
@@ -582,25 +583,28 @@ static bool
 intorel_receive(TupleTableSlot *slot, DestReceiver *self)
 {
 	DR_intorel *myState = (DR_intorel *) self;
-	HeapTuple	tuple;
 
 	/*
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
 	 */
-	tuple = ExecHeapifySlot(slot);
+	ExecMaterializeSlot(slot);
 
 	/*
 	 * force assignment of new OID (see comments in ExecInsert)
 	 */
 	if (myState->rel->rd_rel->relhasoids)
-		HeapTupleSetOid(tuple, InvalidOid);
-
-	heap_insert(myState->rel,
-				tuple,
-				myState->output_cid,
-				myState->hi_options,
-				myState->bistate);
+		slot->tts_tupleOid = InvalidOid;
+
+	table_insert(myState->rel,
+				   slot,
+				   myState->output_cid,
+				   myState->hi_options,
+				   myState->bistate,
+				   NULL,
+				   NULL,
+				   NIL,
+				   NULL);
 
 	/* We know this is a newly created relation, so there are no indexes */
 
@@ -619,7 +623,7 @@ intorel_shutdown(DestReceiver *self)
 
 	/* If we skipped using WAL, must heap_sync before commit */
 	if (myState->hi_options & HEAP_INSERT_SKIP_WAL)
-		heap_sync(myState->rel);
+		table_sync(myState->rel);
 
 	/* close rel, but keep lock until commit */
 	heap_close(myState->rel, NoLock);
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 467695160a..d51d3c2c8e 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -16,6 +16,7 @@
 
 #include "access/htup_details.h"
 #include "access/multixact.h"
+#include "access/tableam.h"
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/catalog.h"
@@ -491,19 +492,22 @@ static bool
 transientrel_receive(TupleTableSlot *slot, DestReceiver *self)
 {
 	DR_transientrel *myState = (DR_transientrel *) self;
-	HeapTuple	tuple;
 
 	/*
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
 	 */
-	tuple = ExecHeapifySlot(slot);
-
-	heap_insert(myState->transientrel,
-				tuple,
-				myState->output_cid,
-				myState->hi_options,
-				myState->bistate);
+	ExecMaterializeSlot(slot);
+
+	table_insert(myState->transientrel,
+				   slot,
+				   myState->output_cid,
+				   myState->hi_options,
+				   myState->bistate,
+				   NULL,
+				   NULL,
+				   NIL,
+				   NULL);
 
 	/* We know this is a newly created relation, so there are no indexes */
 
@@ -522,7 +526,7 @@ transientrel_shutdown(DestReceiver *self)
 
 	/* If we skipped using WAL, must heap_sync before commit */
 	if (myState->hi_options & HEAP_INSERT_SKIP_WAL)
-		heap_sync(myState->transientrel);
+		table_sync(myState->transientrel);
 
 	/* close transientrel, but keep lock until commit */
 	heap_close(myState->transientrel, NoLock);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f2a928b823..32eb9d9624 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -19,6 +19,7 @@
 #include "access/multixact.h"
 #include "access/reloptions.h"
 #include "access/relscan.h"
+#include "access/tableam.h"
 #include "access/sysattr.h"
 #include "access/tupconvert.h"
 #include "access/xact.h"
@@ -4662,7 +4663,8 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 
 			/* Write the tuple out to the new relation */
 			if (newrel)
-				heap_insert(newrel, tuple, mycid, hi_options, bistate);
+				table_insert(newrel, newslot, mycid, hi_options, bistate,
+							   NULL, NULL, NIL, NULL);
 
 			ResetExprContext(econtext);
 
@@ -4686,7 +4688,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 
 		/* If we skipped writing WAL, then we need to sync the heap. */
 		if (hi_options & HEAP_INSERT_SKIP_WAL)
-			heap_sync(newrel);
+			table_sync(newrel);
 
 		heap_close(newrel, NoLock);
 	}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 502f1dee1f..4cb6930fcb 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -15,6 +15,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/tableam.h"
 #include "access/sysattr.h"
 #include "access/htup_details.h"
 #include "access/xact.h"
@@ -2352,17 +2353,21 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 
 void
 ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
-					 HeapTuple trigtuple, List *recheckIndexes,
+					 TupleTableSlot *slot, List *recheckIndexes,
 					 TransitionCaptureState *transition_capture)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 
 	if ((trigdesc && trigdesc->trig_insert_after_row) ||
 		(transition_capture && transition_capture->tcs_insert_new_table))
+	{
+		HeapTuple	trigtuple = ExecHeapifySlot(slot);
+
 		AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT,
 							  true, NULL, trigtuple,
 							  recheckIndexes, NULL,
 							  transition_capture);
+	}
 }
 
 TupleTableSlot *
@@ -3012,9 +3017,10 @@ GetTupleForTrigger(EState *estate,
 				   TupleTableSlot **newSlot)
 {
 	Relation	relation = relinfo->ri_RelationDesc;
-	HeapTupleData tuple;
+	TableTuple tuple;
 	HeapTuple	result;
 	Buffer		buffer;
+	tuple_data	t_data;
 
 	if (newSlot != NULL)
 	{
@@ -3030,11 +3036,11 @@ GetTupleForTrigger(EState *estate,
 		 * lock tuple for update
 		 */
 ltrmark:;
-		tuple.t_self = *tid;
-		test = heap_lock_tuple(relation, &tuple,
-							   estate->es_output_cid,
-							   lockmode, LockWaitBlock,
-							   false, &buffer, &hufd);
+		test = table_lock_tuple(relation, tid, &tuple,
+								  estate->es_output_cid,
+								  lockmode, LockWaitBlock,
+								  false, &buffer, &hufd);
+		result = tuple;
 		switch (test)
 		{
 			case HeapTupleSelfUpdated:
@@ -3066,7 +3072,8 @@ ltrmark:;
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("could not serialize access due to concurrent update")));
-				if (!ItemPointerEquals(&hufd.ctid, &tuple.t_self))
+				t_data = relation->rd_tableamroutine->get_tuple_data(tuple, TID);
+				if (!ItemPointerEquals(&hufd.ctid, &(t_data.tid)))
 				{
 					/* it was updated, so look at the updated version */
 					TupleTableSlot *epqslot;
@@ -3112,6 +3119,7 @@ ltrmark:;
 	{
 		Page		page;
 		ItemId		lp;
+		HeapTupleData tupledata;
 
 		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
 
@@ -3130,17 +3138,17 @@ ltrmark:;
 
 		Assert(ItemIdIsNormal(lp));
 
-		tuple.t_data = (HeapTupleHeader) PageGetItem(page, lp);
-		tuple.t_len = ItemIdGetLength(lp);
-		tuple.t_self = *tid;
-		tuple.t_tableOid = RelationGetRelid(relation);
+		tupledata.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+		tupledata.t_len = ItemIdGetLength(lp);
+		tupledata.t_self = *tid;
+		tupledata.t_tableOid = RelationGetRelid(relation);
 
 		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+
+		result = heap_copytuple(&tupledata);
 	}
 
-	result = heap_copytuple(&tuple);
 	ReleaseBuffer(buffer);
-
 	return result;
 }
 
@@ -3946,8 +3954,8 @@ AfterTriggerExecute(AfterTriggerEvent event,
 	AfterTriggerShared evtshared = GetTriggerSharedData(event);
 	Oid			tgoid = evtshared->ats_tgoid;
 	TriggerData LocTriggerData;
-	HeapTupleData tuple1;
-	HeapTupleData tuple2;
+	TableTuple tuple1;
+	TableTuple tuple2;
 	HeapTuple	rettuple;
 	Buffer		buffer1 = InvalidBuffer;
 	Buffer		buffer2 = InvalidBuffer;
@@ -4020,10 +4028,9 @@ AfterTriggerExecute(AfterTriggerEvent event,
 		default:
 			if (ItemPointerIsValid(&(event->ate_ctid1)))
 			{
-				ItemPointerCopy(&(event->ate_ctid1), &(tuple1.t_self));
-				if (!heap_fetch(rel, SnapshotAny, &tuple1, &buffer1, false, NULL))
+				if (!table_fetch(rel, &(event->ate_ctid1), SnapshotAny, &tuple1, &buffer1, false, NULL))
 					elog(ERROR, "failed to fetch tuple1 for AFTER trigger");
-				LocTriggerData.tg_trigtuple = &tuple1;
+				LocTriggerData.tg_trigtuple = tuple1;
 				LocTriggerData.tg_trigtuplebuf = buffer1;
 			}
 			else
@@ -4037,10 +4044,9 @@ AfterTriggerExecute(AfterTriggerEvent event,
 				AFTER_TRIGGER_2CTID &&
 				ItemPointerIsValid(&(event->ate_ctid2)))
 			{
-				ItemPointerCopy(&(event->ate_ctid2), &(tuple2.t_self));
-				if (!heap_fetch(rel, SnapshotAny, &tuple2, &buffer2, false, NULL))
+				if (!table_fetch(rel, &(event->ate_ctid2), SnapshotAny, &tuple2, &buffer2, false, NULL))
 					elog(ERROR, "failed to fetch tuple2 for AFTER trigger");
-				LocTriggerData.tg_newtuple = &tuple2;
+				LocTriggerData.tg_newtuple = tuple2;
 				LocTriggerData.tg_newtuplebuf = buffer2;
 			}
 			else
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 62e51f1ef3..1038957c59 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -269,12 +269,12 @@ ExecCloseIndices(ResultRelInfo *resultRelInfo)
  */
 List *
 ExecInsertIndexTuples(TupleTableSlot *slot,
-					  ItemPointer tupleid,
 					  EState *estate,
 					  bool noDupErr,
 					  bool *specConflict,
 					  List *arbiterIndexes)
 {
+	ItemPointer tupleid = &slot->tts_tid;
 	List	   *result = NIL;
 	ResultRelInfo *resultRelInfo;
 	int			i;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 16822e962a..cec8ab9980 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -38,6 +38,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/tableam.h"
 #include "access/sysattr.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -1908,7 +1909,7 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
 	 */
 	if (resultRelInfo->ri_PartitionRoot)
 	{
-		HeapTuple	tuple = ExecFetchSlotTuple(slot);
+		TableTuple	tuple = ExecFetchSlotTuple(slot);
 		TupleDesc	old_tupdesc = RelationGetDescr(rel);
 		TupleConversionMap *map;
 
@@ -1988,7 +1989,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				 */
 				if (resultRelInfo->ri_PartitionRoot)
 				{
-					HeapTuple	tuple = ExecFetchSlotTuple(slot);
+					TableTuple tuple = ExecFetchSlotTuple(slot);
 					TupleConversionMap *map;
 
 					rel = resultRelInfo->ri_PartitionRoot;
@@ -2035,7 +2036,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 			/* See the comment above. */
 			if (resultRelInfo->ri_PartitionRoot)
 			{
-				HeapTuple	tuple = ExecFetchSlotTuple(slot);
+				TableTuple tuple = ExecFetchSlotTuple(slot);
 				TupleDesc	old_tupdesc = RelationGetDescr(rel);
 				TupleConversionMap *map;
 
@@ -2495,7 +2496,8 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
 			 ItemPointer tid, TransactionId priorXmax)
 {
 	TupleTableSlot *slot;
-	HeapTuple	copyTuple;
+	TableTuple copyTuple;
+	tuple_data	t_data;
 
 	Assert(rti > 0);
 
@@ -2512,7 +2514,9 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
 	 * For UPDATE/DELETE we have to return tid of actual row we're executing
 	 * PQ for.
 	 */
-	*tid = copyTuple->t_self;
+
+	t_data = table_tuple_get_data(relation, copyTuple, TID);
+	*tid = t_data.tid;
 
 	/*
 	 * Need to run a recheck subquery.  Initialize or reinitialize EPQ state.
@@ -2543,7 +2547,7 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
 	 * is to guard against early re-use of the EPQ query.
 	 */
 	if (!TupIsNull(slot))
-		(void) ExecMaterializeSlot(slot);
+		ExecMaterializeSlot(slot);
 
 	/*
 	 * Clear out the test tuple.  This is needed in case the EPQ query is
@@ -2576,14 +2580,14 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
  * Note: properly, lockmode should be declared as enum LockTupleMode,
  * but we use "int" to avoid having to include heapam.h in executor.h.
  */
-HeapTuple
+TableTuple
 EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 				  LockWaitPolicy wait_policy,
 				  ItemPointer tid, TransactionId priorXmax)
 {
-	HeapTuple	copyTuple = NULL;
-	HeapTupleData tuple;
+	TableTuple tuple = NULL;
 	SnapshotData SnapshotDirty;
+	tuple_data	t_data;
 
 	/*
 	 * fetch target tuple
@@ -2591,12 +2595,12 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 	 * Loop here to deal with updated or busy tuples
 	 */
 	InitDirtySnapshot(SnapshotDirty);
-	tuple.t_self = *tid;
 	for (;;)
 	{
 		Buffer		buffer;
+		ItemPointerData ctid;
 
-		if (heap_fetch(relation, &SnapshotDirty, &tuple, &buffer, true, NULL))
+		if (table_fetch(relation, tid, &SnapshotDirty, &tuple, &buffer, true, NULL))
 		{
 			HTSU_Result test;
 			HeapUpdateFailureData hufd;
@@ -2610,7 +2614,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 			 * 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),
+			if (!TransactionIdEquals(HeapTupleHeaderGetXmin(((HeapTuple) tuple)->t_data),
 									 priorXmax))
 			{
 				ReleaseBuffer(buffer);
@@ -2632,7 +2636,8 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 				{
 					case LockWaitBlock:
 						XactLockTableWait(SnapshotDirty.xmax,
-										  relation, &tuple.t_self,
+										  relation,
+										  tid,
 										  XLTW_FetchUpdated);
 						break;
 					case LockWaitSkip:
@@ -2661,20 +2666,23 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 			 * that priorXmax == xmin, so we can test that variable instead of
 			 * doing HeapTupleHeaderGetXmin again.
 			 */
-			if (TransactionIdIsCurrentTransactionId(priorXmax) &&
-				HeapTupleHeaderGetCmin(tuple.t_data) >= estate->es_output_cid)
+			if (TransactionIdIsCurrentTransactionId(priorXmax))
 			{
-				ReleaseBuffer(buffer);
-				return NULL;
+				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 = heap_lock_tuple(relation, &tuple,
-								   estate->es_output_cid,
-								   lockmode, wait_policy,
-								   false, &buffer, &hufd);
+			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);
 
@@ -2710,12 +2718,15 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 								(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 								 errmsg("could not serialize access due to concurrent update")));
 
+#if 0 //hari
 					/* Should not encounter speculative tuple on recheck */
 					Assert(!HeapTupleHeaderIsSpeculative(tuple.t_data));
-					if (!ItemPointerEquals(&hufd.ctid, &tuple.t_self))
+#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 */
-						tuple.t_self = hufd.ctid;
+						*tid = hufd.ctid;
 						/* updated row should have xmin matching this xmax */
 						priorXmax = hufd.xmax;
 						continue;
@@ -2737,10 +2748,6 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 					return NULL;	/* keep compiler quiet */
 			}
 
-			/*
-			 * We got tuple - now copy it for use by recheck query.
-			 */
-			copyTuple = heap_copytuple(&tuple);
 			ReleaseBuffer(buffer);
 			break;
 		}
@@ -2749,7 +2756,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 		 * 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)
+		if (tuple == NULL)
 		{
 			ReleaseBuffer(buffer);
 			return NULL;
@@ -2758,7 +2765,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 		/*
 		 * As above, if xmin isn't what we're expecting, do nothing.
 		 */
-		if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple.t_data),
+		if (!TransactionIdEquals(HeapTupleHeaderGetXmin(((HeapTuple) tuple)->t_data),
 								 priorXmax))
 		{
 			ReleaseBuffer(buffer);
@@ -2777,7 +2784,9 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 		 * 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))
+		t_data = table_tuple_get_data(relation, tuple, CTID);
+		ctid = t_data.tid;
+		if (ItemPointerEquals(tid, &ctid))
 		{
 			/* deleted, so forget about it */
 			ReleaseBuffer(buffer);
@@ -2785,17 +2794,19 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 		}
 
 		/* updated, so look at the updated row */
-		tuple.t_self = tuple.t_data->t_ctid;
+		*tid = ctid;
+
 		/* updated row should have xmin matching this xmax */
-		priorXmax = HeapTupleHeaderGetUpdateXid(tuple.t_data);
+		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 copied tuple
+	 * Return the tuple
 	 */
-	return copyTuple;
+	return tuple;
 }
 
 /*
@@ -2841,7 +2852,7 @@ EvalPlanQualSetPlan(EPQState *epqstate, Plan *subplan, List *auxrowmarks)
  * NB: passed tuple must be palloc'd; it may get freed later
  */
 void
-EvalPlanQualSetTuple(EPQState *epqstate, Index rti, HeapTuple tuple)
+EvalPlanQualSetTuple(EPQState *epqstate, Index rti, TableTuple tuple)
 {
 	EState	   *estate = epqstate->estate;
 
@@ -2860,7 +2871,7 @@ EvalPlanQualSetTuple(EPQState *epqstate, Index rti, HeapTuple tuple)
 /*
  * Fetch back the current test tuple (if any) for the specified RTI
  */
-HeapTuple
+TableTuple
 EvalPlanQualGetTuple(EPQState *epqstate, Index rti)
 {
 	EState	   *estate = epqstate->estate;
@@ -2888,7 +2899,7 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 		ExecRowMark *erm = aerm->rowmark;
 		Datum		datum;
 		bool		isNull;
-		HeapTupleData tuple;
+		TableTuple tuple;
 
 		if (RowMarkRequiresRowShareLock(erm->markType))
 			elog(ERROR, "EvalPlanQual doesn't support locking rowmarks");
@@ -2919,8 +2930,6 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 
 		if (erm->markType == ROW_MARK_REFERENCE)
 		{
-			HeapTuple	copyTuple;
-
 			Assert(erm->relation != NULL);
 
 			/* fetch the tuple's ctid */
@@ -2944,11 +2953,11 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 							 errmsg("cannot lock rows in foreign table \"%s\"",
 									RelationGetRelationName(erm->relation))));
-				copyTuple = fdwroutine->RefetchForeignRow(epqstate->estate,
-														  erm,
-														  datum,
-														  &updated);
-				if (copyTuple == NULL)
+				tuple = fdwroutine->RefetchForeignRow(epqstate->estate,
+													  erm,
+													  datum,
+													  &updated);
+				if (tuple == NULL)
 					elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck");
 
 				/*
@@ -2962,23 +2971,18 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 				/* ordinary table, fetch the tuple */
 				Buffer		buffer;
 
-				tuple.t_self = *((ItemPointer) DatumGetPointer(datum));
-				if (!heap_fetch(erm->relation, SnapshotAny, &tuple, &buffer,
-								false, NULL))
+				if (!table_fetch(erm->relation, (ItemPointer) DatumGetPointer(datum), SnapshotAny, &tuple, &buffer,
+								   false, NULL))
 					elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck");
 
-				/* successful, copy tuple */
-				copyTuple = heap_copytuple(&tuple);
 				ReleaseBuffer(buffer);
 			}
 
 			/* store tuple */
-			EvalPlanQualSetTuple(epqstate, erm->rti, copyTuple);
+			EvalPlanQualSetTuple(epqstate, erm->rti, tuple);
 		}
 		else
 		{
-			HeapTupleHeader td;
-
 			Assert(erm->markType == ROW_MARK_COPY);
 
 			/* fetch the whole-row Var for the relation */
@@ -2988,19 +2992,12 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 			/* non-locked rels could be on the inside of outer joins */
 			if (isNull)
 				continue;
-			td = DatumGetHeapTupleHeader(datum);
-
-			/* build a temporary HeapTuple control structure */
-			tuple.t_len = HeapTupleHeaderGetDatumLength(td);
-			tuple.t_data = td;
-			/* relation might be a foreign table, if so provide tableoid */
-			tuple.t_tableOid = erm->relid;
-			/* also copy t_ctid in case there's valid data there */
-			tuple.t_self = td->t_ctid;
-
-			/* copy and store tuple */
-			EvalPlanQualSetTuple(epqstate, erm->rti,
-								 heap_copytuple(&tuple));
+
+			tuple = table_tuple_by_datum(erm->relation, datum, erm->relid);
+
+			/* store tuple */
+			EvalPlanQualSetTuple(epqstate, erm->rti, tuple);
+
 		}
 	}
 }
@@ -3169,8 +3166,8 @@ EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
 	}
 	else
 	{
-		estate->es_epqTuple = (HeapTuple *)
-			palloc0(rtsize * sizeof(HeapTuple));
+		estate->es_epqTuple = (TableTuple *)
+			palloc0(rtsize * sizeof(TableTuple));
 		estate->es_epqTupleSet = (bool *)
 			palloc0(rtsize * sizeof(bool));
 	}
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index fba19f4fde..cbd1e06a6a 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -15,6 +15,7 @@
 #include "postgres.h"
 
 #include "access/relscan.h"
+#include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
 #include "commands/trigger.h"
@@ -169,19 +170,19 @@ retry:
 		Buffer		buf;
 		HeapUpdateFailureData hufd;
 		HTSU_Result res;
-		HeapTupleData locktup;
-
-		ItemPointerCopy(&outslot->tts_tid, &locktup.t_self);
+		TableTuple locktup;
 
 		PushActiveSnapshot(GetLatestSnapshot());
 
-		res = heap_lock_tuple(rel, &locktup, GetCurrentCommandId(false),
-							  lockmode,
-							  LockWaitBlock,
-							  false /* don't follow updates */ ,
-							  &buf, &hufd);
+		res = table_lock_tuple(rel, &(outslot->tts_tid), &locktup, GetCurrentCommandId(false),
+								 lockmode,
+								 LockWaitBlock,
+								 false /* don't follow updates */ ,
+								 &buf, &hufd);
 		/* the tuple slot already has the buffer pinned */
-		ReleaseBuffer(buf);
+		if (BufferIsValid(buf))
+			ReleaseBuffer(buf);
+		pfree(locktup);
 
 		PopActiveSnapshot();
 
@@ -277,19 +278,20 @@ retry:
 		Buffer		buf;
 		HeapUpdateFailureData hufd;
 		HTSU_Result res;
-		HeapTupleData locktup;
-
-		ItemPointerCopy(&outslot->tts_tid, &locktup.t_self);
+		TableTuple locktup;
 
 		PushActiveSnapshot(GetLatestSnapshot());
 
-		res = heap_lock_tuple(rel, &locktup, GetCurrentCommandId(false),
-							  lockmode,
-							  LockWaitBlock,
-							  false /* don't follow updates */ ,
-							  &buf, &hufd);
+		res = table_lock_tuple(rel, &(outslot->tts_tid), &locktup, GetCurrentCommandId(false),
+								 lockmode,
+								 LockWaitBlock,
+								 false /* don't follow updates */ ,
+								 &buf, &hufd);
 		/* the tuple slot already has the buffer pinned */
-		ReleaseBuffer(buf);
+		if (BufferIsValid(buf))
+			ReleaseBuffer(buf);
+
+		pfree(locktup);
 
 		PopActiveSnapshot();
 
@@ -327,7 +329,6 @@ void
 ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 {
 	bool		skip_tuple = false;
-	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 
@@ -354,19 +355,12 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 		if (rel->rd_att->constr)
 			ExecConstraints(resultRelInfo, slot, estate, true);
 
-		/* Store the slot into tuple that we can inspect. */
-		tuple = ExecHeapifySlot(slot);
-
-		/* OK, store the tuple and create index entries for it */
-		simple_heap_insert(rel, tuple);
-
-		if (resultRelInfo->ri_NumIndices > 0)
-			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
-												   estate, false, NULL,
-												   NIL);
+		table_insert(resultRelInfo->ri_RelationDesc, slot,
+					   GetCurrentCommandId(true), 0, NULL,
+					   ExecInsertIndexTuples, estate, NIL, &recheckIndexes);
 
 		/* AFTER ROW INSERT Triggers */
-		ExecARInsertTriggers(estate, resultRelInfo, tuple,
+		ExecARInsertTriggers(estate, resultRelInfo, slot,
 							 recheckIndexes, NULL);
 
 		/*
@@ -390,7 +384,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 						 TupleTableSlot *searchslot, TupleTableSlot *slot)
 {
 	bool		skip_tuple = false;
-	HeapTuple	tuple;
+	TableTuple tuple;
 	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	ItemPointer tid = &(searchslot->tts_tid);
@@ -415,22 +409,18 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 	if (!skip_tuple)
 	{
 		List	   *recheckIndexes = NIL;
+		HeapUpdateFailureData hufd;
+		LockTupleMode lockmode;
+		InsertIndexTuples IndexFunc = ExecInsertIndexTuples;
 
 		/* Check the constraints of the tuple */
 		if (rel->rd_att->constr)
 			ExecConstraints(resultRelInfo, slot, estate, true);
 
-		/* Store the slot into tuple that we can write. */
-		tuple = ExecHeapifySlot(slot);
+		table_update(rel, tid, slot, estate, GetCurrentCommandId(true), InvalidSnapshot,
+					   true, &hufd, &lockmode, IndexFunc, &recheckIndexes);
 
-		/* OK, update the tuple and index entries for it */
-		simple_heap_update(rel, tid, tuple);
-
-		if (resultRelInfo->ri_NumIndices > 0 &&
-			!HeapTupleIsHeapOnly(tuple))
-			recheckIndexes = ExecInsertIndexTuples(slot, tid,
-												   estate, false, NULL,
-												   NIL);
+		tuple = ExecHeapifySlot(slot);
 
 		/* AFTER ROW UPDATE Triggers */
 		ExecARUpdateTriggers(estate, resultRelInfo,
diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c
index 7961b4be6a..d827fccc54 100644
--- a/src/backend/executor/nodeLockRows.c
+++ b/src/backend/executor/nodeLockRows.c
@@ -22,6 +22,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/tableam.h"
 #include "access/xact.h"
 #include "executor/executor.h"
 #include "executor/nodeLockRows.h"
@@ -74,18 +75,20 @@ lnext:
 	{
 		ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc);
 		ExecRowMark *erm = aerm->rowmark;
-		HeapTuple  *testTuple;
+		TableTuple *testTuple;
 		Datum		datum;
 		bool		isNull;
-		HeapTupleData tuple;
+		TableTuple tuple;
 		Buffer		buffer;
 		HeapUpdateFailureData hufd;
 		LockTupleMode lockmode;
 		HTSU_Result test;
-		HeapTuple	copyTuple;
+		TableTuple copyTuple;
+		ItemPointerData tid;
+		tuple_data	t_data;
 
 		/* clear any leftover test tuple for this rel */
-		testTuple = &(node->lr_curtuples[erm->rti - 1]);
+		testTuple = (TableTuple) (&(node->lr_curtuples[erm->rti - 1]));
 		if (*testTuple != NULL)
 			heap_freetuple(*testTuple);
 		*testTuple = NULL;
@@ -159,7 +162,7 @@ lnext:
 		}
 
 		/* okay, try to lock the tuple */
-		tuple.t_self = *((ItemPointer) DatumGetPointer(datum));
+		tid = *((ItemPointer) DatumGetPointer(datum));
 		switch (erm->markType)
 		{
 			case ROW_MARK_EXCLUSIVE:
@@ -180,11 +183,13 @@ lnext:
 				break;
 		}
 
-		test = heap_lock_tuple(erm->relation, &tuple,
-							   estate->es_output_cid,
-							   lockmode, erm->waitPolicy, true,
-							   &buffer, &hufd);
-		ReleaseBuffer(buffer);
+		test = table_lock_tuple(erm->relation, &tid, &tuple,
+								  estate->es_output_cid,
+								  lockmode, erm->waitPolicy, true,
+								  &buffer, &hufd);
+		if (BufferIsValid(buffer))
+			ReleaseBuffer(buffer);
+
 		switch (test)
 		{
 			case HeapTupleWouldBlock:
@@ -218,7 +223,8 @@ lnext:
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("could not serialize access due to concurrent update")));
-				if (ItemPointerEquals(&hufd.ctid, &tuple.t_self))
+				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;
@@ -238,7 +244,8 @@ lnext:
 					goto lnext;
 				}
 				/* remember the actually locked tuple's TID */
-				tuple.t_self = copyTuple->t_self;
+				t_data = erm->relation->rd_tableamroutine->get_tuple_data(copyTuple, TID);
+				tid = t_data.tid;
 
 				/* Save locked tuple for EvalPlanQual testing below */
 				*testTuple = copyTuple;
@@ -258,7 +265,7 @@ lnext:
 		}
 
 		/* Remember locked tuple's TID for EPQ testing and WHERE CURRENT OF */
-		erm->curCtid = tuple.t_self;
+		erm->curCtid = tid;
 	}
 
 	/*
@@ -280,7 +287,7 @@ lnext:
 		{
 			ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc);
 			ExecRowMark *erm = aerm->rowmark;
-			HeapTupleData tuple;
+			TableTuple tuple;
 			Buffer		buffer;
 
 			/* skip non-active child tables, but clear their test tuples */
@@ -308,14 +315,12 @@ lnext:
 			Assert(ItemPointerIsValid(&(erm->curCtid)));
 
 			/* okay, fetch the tuple */
-			tuple.t_self = erm->curCtid;
-			if (!heap_fetch(erm->relation, SnapshotAny, &tuple, &buffer,
-							false, NULL))
+			if (!table_fetch(erm->relation, &erm->curCtid, SnapshotAny, &tuple, &buffer,
+							   false, NULL))
 				elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck");
 
 			/* successful, copy and store tuple */
-			EvalPlanQualSetTuple(&node->lr_epqstate, erm->rti,
-								 heap_copytuple(&tuple));
+			EvalPlanQualSetTuple(&node->lr_epqstate, erm->rti, tuple);
 			ReleaseBuffer(buffer);
 		}
 
@@ -394,8 +399,8 @@ ExecInitLockRows(LockRows *node, EState *estate, int eflags)
 	 * Create workspace in which we can remember per-RTE locked tuples
 	 */
 	lrstate->lr_ntables = list_length(estate->es_range_table);
-	lrstate->lr_curtuples = (HeapTuple *)
-		palloc0(lrstate->lr_ntables * sizeof(HeapTuple));
+	lrstate->lr_curtuples = (TableTuple *)
+		palloc0(lrstate->lr_ntables * sizeof(TableTuple));
 
 	/*
 	 * Locate the ExecRowMark(s) that this node is responsible for, and
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 068be8b971..ba27bf002a 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -38,7 +38,9 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/tableam.h"
 #include "access/xact.h"
+#include "catalog/pg_am.h"
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
@@ -165,15 +167,13 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
 		econtext->ecxt_scantuple = tupleSlot;
 	else
 	{
-		HeapTuple	tuple;
-
 		/*
 		 * RETURNING expressions might reference the tableoid column, so
 		 * initialize t_tableOid before evaluating them.
 		 */
 		Assert(!TupIsNull(econtext->ecxt_scantuple));
-		tuple = ExecHeapifySlot(econtext->ecxt_scantuple);
-		tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+		ExecSlotUpdateTupleTableoid(econtext->ecxt_scantuple,
+									RelationGetRelid(resultRelInfo->ri_RelationDesc));
 	}
 	econtext->ecxt_outertuple = planSlot;
 
@@ -192,7 +192,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
 static void
 ExecCheckHeapTupleVisible(EState *estate,
 						  Relation rel,
-						  HeapTuple tuple,
+						  TableTuple tuple,
 						  Buffer buffer)
 {
 	if (!IsolationUsesXactSnapshot())
@@ -205,13 +205,15 @@ ExecCheckHeapTupleVisible(EState *estate,
 	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);
+
 		/*
 		 * We should not raise a serialization failure if the conflict is
 		 * against a tuple inserted by our own transaction, even if it's not
 		 * visible to our snapshot.  (This would happen, for example, if
 		 * conflicting keys are proposed for insertion in a single command.)
 		 */
-		if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple->t_data)))
+		if (!TransactionIdIsCurrentTransactionId(t_data.xid))
 			ereport(ERROR,
 					(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 					 errmsg("could not serialize access due to concurrent update")));
@@ -227,19 +229,20 @@ ExecCheckTIDVisible(EState *estate,
 					ResultRelInfo *relinfo,
 					ItemPointer tid)
 {
-	Relation	rel = relinfo->ri_RelationDesc;
 	Buffer		buffer;
-	HeapTupleData tuple;
+	Relation	rel = relinfo->ri_RelationDesc;
+	TableTuple tuple;
 
 	/* Redundantly check isolation level */
 	if (!IsolationUsesXactSnapshot())
 		return;
 
-	tuple.t_self = *tid;
-	if (!heap_fetch(rel, SnapshotAny, &tuple, &buffer, false, NULL))
+	if (!table_fetch(rel, tid, SnapshotAny, &tuple, &buffer, false, NULL))
 		elog(ERROR, "failed to fetch conflicting tuple for ON CONFLICT");
-	ExecCheckHeapTupleVisible(estate, rel, &tuple, buffer);
-	ReleaseBuffer(buffer);
+	ExecCheckHeapTupleVisible(estate, rel, tuple, buffer);
+	if (BufferIsValid(buffer))
+		ReleaseBuffer(buffer);
+	pfree(tuple);
 }
 
 /* ----------------------------------------------------------------
@@ -260,7 +263,7 @@ ExecInsert(ModifyTableState *mtstate,
 		   EState *estate,
 		   bool canSetTag)
 {
-	HeapTuple	tuple;
+	TableTuple tuple;
 	ResultRelInfo *resultRelInfo;
 	ResultRelInfo *saved_resultRelInfo = NULL;
 	Relation	resultRelationDesc;
@@ -268,12 +271,6 @@ ExecInsert(ModifyTableState *mtstate,
 	List	   *recheckIndexes = NIL;
 	TupleTableSlot *result = NULL;
 
-	/*
-	 * get the heap tuple out of the tuple table slot, making sure we have a
-	 * writable copy
-	 */
-	tuple = ExecHeapifySlot(slot);
-
 	/*
 	 * get information on the (current) result relation
 	 */
@@ -286,6 +283,8 @@ ExecInsert(ModifyTableState *mtstate,
 		PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
 		TupleConversionMap *map;
 
+		tuple = ExecHeapifySlot(slot);
+
 		/*
 		 * Away we go ... If we end up not finding a partition after all,
 		 * ExecFindPartition() does not return and errors out instead.
@@ -376,19 +375,31 @@ ExecInsert(ModifyTableState *mtstate,
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/*
-	 * If the result relation has OIDs, force the tuple's OID to zero so that
-	 * heap_insert will assign a fresh OID.  Usually the OID already will be
-	 * zero at this point, but there are corner cases where the plan tree can
-	 * return a tuple extracted literally from some table with the same
-	 * rowtype.
+	 * get the heap tuple out of the tuple table slot, making sure we have a
+	 * writable copy  <-- obsolete comment XXX explain what we really do here
+	 *
+	 * Do we really need to do this here?
+	 */
+	ExecMaterializeSlot(slot);
+
+
+	/*
+	 * If the result relation uses heapam and has OIDs, force the tuple's OID
+	 * to zero so that heap_insert will assign a fresh OID.  Usually the OID
+	 * already will be zero at this point, but there are corner cases where
+	 * the plan tree can return a tuple extracted literally from some table
+	 * with the same rowtype.
 	 *
 	 * XXX if we ever wanted to allow users to assign their own OIDs to new
 	 * rows, this'd be the place to do it.  For the moment, we make a point of
 	 * doing this before calling triggers, so that a user-supplied trigger
 	 * could hack the OID if desired.
 	 */
-	if (resultRelationDesc->rd_rel->relhasoids)
-		HeapTupleSetOid(tuple, InvalidOid);
+	if (resultRelationDesc->rd_rel->relam == HEAP_TABLE_AM_OID &&
+		resultRelationDesc->rd_rel->relhasoids)
+	{
+		slot->tts_tupleOid = InvalidOid;
+	}
 
 	/*
 	 * BEFORE ROW INSERT Triggers.
@@ -406,9 +417,6 @@ ExecInsert(ModifyTableState *mtstate,
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
-
-		/* trigger might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW INSERT Triggers */
@@ -420,9 +428,6 @@ ExecInsert(ModifyTableState *mtstate,
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
 
-		/* trigger might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
-
 		newId = InvalidOid;
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
@@ -438,14 +443,12 @@ ExecInsert(ModifyTableState *mtstate,
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
 
-		/* FDW might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
-
 		/*
 		 * AFTER ROW Triggers or RETURNING expressions might reference the
 		 * tableoid column, so initialize t_tableOid before evaluating them.
 		 */
-		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
+		slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
+		ExecSlotUpdateTupleTableoid(slot, slot->tts_tableOid);
 
 		newId = InvalidOid;
 	}
@@ -465,7 +468,8 @@ ExecInsert(ModifyTableState *mtstate,
 		 * Constraints might reference the tableoid column, so initialize
 		 * t_tableOid before evaluating them.
 		 */
-		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
+		slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
+		ExecSlotUpdateTupleTableoid(slot, slot->tts_tableOid);
 
 		/*
 		 * Check any RLS INSERT WITH CHECK policies
@@ -495,7 +499,6 @@ ExecInsert(ModifyTableState *mtstate,
 			/* Perform a speculative insertion. */
 			uint32		specToken;
 			ItemPointerData conflictTid;
-			bool		specConflict;
 
 			/*
 			 * Do a non-conclusive check for conflicts first.
@@ -510,7 +513,7 @@ ExecInsert(ModifyTableState *mtstate,
 			 * speculatively.
 			 */
 	vlock:
-			specConflict = false;
+			slot->tts_specConflict = false;
 			if (!ExecCheckIndexConstraints(slot, estate, &conflictTid,
 										   arbiterIndexes))
 			{
@@ -556,24 +559,17 @@ ExecInsert(ModifyTableState *mtstate,
 			 * waiting for the whole transaction to complete.
 			 */
 			specToken = SpeculativeInsertionLockAcquire(GetCurrentTransactionId());
-			HeapTupleHeaderSetSpeculativeToken(tuple->t_data, specToken);
+			slot->tts_speculativeToken = specToken;
 
 			/* insert the tuple, with the speculative token */
-			newId = heap_insert(resultRelationDesc, tuple,
-								estate->es_output_cid,
-								HEAP_INSERT_SPECULATIVE,
-								NULL);
-
-			/* insert index entries for tuple */
-			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
-												   estate, true, &specConflict,
-												   arbiterIndexes);
-
-			/* adjust the tuple's state accordingly */
-			if (!specConflict)
-				heap_finish_speculative(resultRelationDesc, tuple);
-			else
-				heap_abort_speculative(resultRelationDesc, tuple);
+			newId = table_insert(resultRelationDesc, slot,
+								   estate->es_output_cid,
+								   HEAP_INSERT_SPECULATIVE,
+								   NULL,
+								   ExecInsertIndexTuples,
+								   estate,
+								   arbiterIndexes,
+								   &recheckIndexes);
 
 			/*
 			 * Wake up anyone waiting for our decision.  They will re-check
@@ -589,7 +585,7 @@ ExecInsert(ModifyTableState *mtstate,
 			 * the pre-check again, which will now find the conflicting tuple
 			 * (unless it aborts before we get there).
 			 */
-			if (specConflict)
+			if (slot->tts_specConflict)
 			{
 				list_free(recheckIndexes);
 				goto vlock;
@@ -601,19 +597,14 @@ ExecInsert(ModifyTableState *mtstate,
 		{
 			/*
 			 * insert the tuple normally.
-			 *
-			 * Note: heap_insert returns the tid (location) of the new tuple
-			 * in the t_self field.
 			 */
-			newId = heap_insert(resultRelationDesc, tuple,
-								estate->es_output_cid,
-								0, NULL);
-
-			/* insert index entries for tuple */
-			if (resultRelInfo->ri_NumIndices > 0)
-				recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
-													   estate, false, NULL,
-													   arbiterIndexes);
+			newId = table_insert(resultRelationDesc, slot,
+								   estate->es_output_cid,
+								   0, NULL,
+								   ExecInsertIndexTuples,
+								   estate,
+								   arbiterIndexes,
+								   &recheckIndexes);
 		}
 	}
 
@@ -621,11 +612,11 @@ ExecInsert(ModifyTableState *mtstate,
 	{
 		(estate->es_processed)++;
 		estate->es_lastoid = newId;
-		setLastTid(&(tuple->t_self));
+		setLastTid(&(slot->tts_tid));
 	}
 
 	/* AFTER ROW INSERT Triggers */
-	ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes,
+	ExecARInsertTriggers(estate, resultRelInfo, slot, recheckIndexes,
 						 mtstate->mt_transition_capture);
 
 	list_free(recheckIndexes);
@@ -676,7 +667,7 @@ ExecInsert(ModifyTableState *mtstate,
 static TupleTableSlot *
 ExecDelete(ModifyTableState *mtstate,
 		   ItemPointer tupleid,
-		   HeapTuple oldtuple,
+		   TableTuple oldtuple,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
 		   EState *estate,
@@ -721,8 +712,6 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
-		HeapTuple	tuple;
-
 		/*
 		 * delete from foreign table: let the FDW do it
 		 *
@@ -748,8 +737,10 @@ ExecDelete(ModifyTableState *mtstate,
 		 */
 		if (slot->tts_isempty)
 			ExecStoreAllNullTuple(slot);
-		tuple = ExecHeapifySlot(slot);
-		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
+
+		ExecMaterializeSlot(slot);
+
+		slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
 	}
 	else
 	{
@@ -763,11 +754,12 @@ ExecDelete(ModifyTableState *mtstate,
 		 * mode transactions.
 		 */
 ldelete:;
-		result = heap_delete(resultRelationDesc, tupleid,
-							 estate->es_output_cid,
-							 estate->es_crosscheck_snapshot,
-							 true /* wait for commit */ ,
-							 &hufd);
+		result = table_delete(resultRelationDesc, tupleid,
+								estate->es_output_cid,
+								estate->es_crosscheck_snapshot,
+								true /* wait for commit */ ,
+								NULL,
+								&hufd);
 		switch (result)
 		{
 			case HeapTupleSelfUpdated:
@@ -863,7 +855,7 @@ ldelete:;
 		 * gotta fetch it.  We can use the trigger tuple slot.
 		 */
 		TupleTableSlot *rslot;
-		HeapTupleData deltuple;
+		TableTuple deltuple = NULL;
 		Buffer		delbuffer;
 
 		if (resultRelInfo->ri_FdwRoutine)
@@ -877,20 +869,19 @@ ldelete:;
 			slot = estate->es_trig_tuple_slot;
 			if (oldtuple != NULL)
 			{
-				deltuple = *oldtuple;
+				deltuple = heap_copytuple(oldtuple);
 				delbuffer = InvalidBuffer;
 			}
 			else
 			{
-				deltuple.t_self = *tupleid;
-				if (!heap_fetch(resultRelationDesc, SnapshotAny,
-								&deltuple, &delbuffer, false, NULL))
+				if (!table_fetch(resultRelationDesc, tupleid, SnapshotAny,
+								   &deltuple, &delbuffer, false, NULL))
 					elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
 			}
 
 			if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
 				ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
-			ExecStoreTuple(&deltuple, slot, InvalidBuffer, false);
+			ExecStoreTuple(deltuple, slot, InvalidBuffer, false);
 		}
 
 		rslot = ExecProcessReturning(resultRelInfo, slot, planSlot);
@@ -899,7 +890,7 @@ ldelete:;
 		 * Before releasing the target tuple again, make sure rslot has a
 		 * local copy of any pass-by-reference values.
 		 */
-		ExecHeapifySlot(rslot);
+		ExecMaterializeSlot(rslot);
 
 		ExecClearTuple(slot);
 		if (BufferIsValid(delbuffer))
@@ -936,14 +927,14 @@ ldelete:;
 static TupleTableSlot *
 ExecUpdate(ModifyTableState *mtstate,
 		   ItemPointer tupleid,
-		   HeapTuple oldtuple,
+		   TableTuple oldtuple,
 		   TupleTableSlot *slot,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
 		   EState *estate,
 		   bool canSetTag)
 {
-	HeapTuple	tuple;
+	TableTuple tuple;
 	ResultRelInfo *resultRelInfo;
 	Relation	resultRelationDesc;
 	HTSU_Result result;
@@ -1008,14 +999,14 @@ ExecUpdate(ModifyTableState *mtstate,
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
 
-		/* FDW might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
-
 		/*
 		 * AFTER ROW Triggers or RETURNING expressions might reference the
 		 * tableoid column, so initialize t_tableOid before evaluating them.
 		 */
-		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
+		ExecSlotUpdateTupleTableoid(slot, RelationGetRelid(resultRelationDesc));
+
+		/* FDW might have changed tuple */
+		tuple = ExecHeapifySlot(slot);
 	}
 	else
 	{
@@ -1025,7 +1016,7 @@ ExecUpdate(ModifyTableState *mtstate,
 		 * Constraints might reference the tableoid column, so initialize
 		 * t_tableOid before evaluating them.
 		 */
-		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
+		slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
 
 		/*
 		 * Check any RLS UPDATE WITH CHECK policies
@@ -1061,11 +1052,14 @@ lreplace:;
 		 * needed for referential integrity updates in transaction-snapshot
 		 * mode transactions.
 		 */
-		result = heap_update(resultRelationDesc, tupleid, tuple,
-							 estate->es_output_cid,
-							 estate->es_crosscheck_snapshot,
-							 true /* wait for commit */ ,
-							 &hufd, &lockmode);
+		result = table_update(resultRelationDesc, tupleid, slot,
+								estate,
+								estate->es_output_cid,
+								estate->es_crosscheck_snapshot,
+								true /* wait for commit */ ,
+								&hufd, &lockmode,
+								ExecInsertIndexTuples,
+								&recheckIndexes);
 		switch (result)
 		{
 			case HeapTupleSelfUpdated:
@@ -1136,26 +1130,6 @@ lreplace:;
 				elog(ERROR, "unrecognized heap_update status: %u", result);
 				return NULL;
 		}
-
-		/*
-		 * Note: instead of having to update the old index tuples associated
-		 * with the heap tuple, all we do is form and insert new index tuples.
-		 * This is because UPDATEs are actually DELETEs and INSERTs, and index
-		 * tuple deletion is done later by VACUUM (see notes in ExecDelete).
-		 * All we do here is insert new index tuples.  -cim 9/27/89
-		 */
-
-		/*
-		 * insert index entries for tuple
-		 *
-		 * Note: heap_update returns the tid (location) of the new tuple in
-		 * the t_self field.
-		 *
-		 * If it's a HOT update, we mustn't insert new index entries.
-		 */
-		if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple))
-			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
-												   estate, false, NULL, NIL);
 	}
 
 	if (canSetTag)
@@ -1213,11 +1187,12 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	ExprContext *econtext = mtstate->ps.ps_ExprContext;
 	Relation	relation = resultRelInfo->ri_RelationDesc;
 	ExprState  *onConflictSetWhere = resultRelInfo->ri_onConflictSetWhere;
-	HeapTupleData tuple;
+	TableTuple tuple = NULL;
 	HeapUpdateFailureData hufd;
 	LockTupleMode lockmode;
 	HTSU_Result test;
 	Buffer		buffer;
+	tuple_data	t_data;
 
 	/* Determine lock mode to use */
 	lockmode = ExecUpdateLockMode(estate, resultRelInfo);
@@ -1228,10 +1203,8 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	 * previous conclusion that the tuple is conclusively committed is not
 	 * true anymore.
 	 */
-	tuple.t_self = *conflictTid;
-	test = heap_lock_tuple(relation, &tuple, estate->es_output_cid,
-						   lockmode, LockWaitBlock, false, &buffer,
-						   &hufd);
+	test = table_lock_tuple(relation, conflictTid, &tuple, estate->es_output_cid,
+							  lockmode, LockWaitBlock, false, &buffer, &hufd);
 	switch (test)
 	{
 		case HeapTupleMayBeUpdated:
@@ -1256,7 +1229,8 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 			 * that for SQL MERGE, an exception must be raised in the event of
 			 * an attempt to update the same row twice.
 			 */
-			if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple.t_data)))
+			t_data = table_tuple_get_data(relation, tuple, XMIN);
+			if (TransactionIdIsCurrentTransactionId(t_data.xid))
 				ereport(ERROR,
 						(errcode(ERRCODE_CARDINALITY_VIOLATION),
 						 errmsg("ON CONFLICT DO UPDATE command cannot affect row a second time"),
@@ -1287,7 +1261,9 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 			 * loop here, as the new version of the row might not conflict
 			 * anymore, or the conflicting tuple has actually been deleted.
 			 */
-			ReleaseBuffer(buffer);
+			if (BufferIsValid(buffer))
+				ReleaseBuffer(buffer);
+			pfree(tuple);
 			return false;
 
 		default:
@@ -1315,10 +1291,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, buffer);
 
 	/* Store target's existing tuple in the state's dedicated slot */
-	ExecStoreTuple(&tuple, mtstate->mt_existing, buffer, false);
+	ExecStoreTuple(tuple, mtstate->mt_existing, buffer, false);
 
 	/*
 	 * Make tuple and any needed join variables available to ExecQual and
@@ -1333,7 +1309,9 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 
 	if (!ExecQual(onConflictSetWhere, econtext))
 	{
-		ReleaseBuffer(buffer);
+		if (BufferIsValid(buffer))
+			ReleaseBuffer(buffer);
+		pfree(tuple);
 		InstrCountFiltered1(&mtstate->ps, 1);
 		return true;			/* done with the tuple */
 	}
@@ -1373,12 +1351,14 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	 */
 
 	/* Execute UPDATE with projection */
-	*returning = ExecUpdate(mtstate, &tuple.t_self, NULL,
+	*returning = ExecUpdate(mtstate, conflictTid, NULL,
 							mtstate->mt_conflproj, planSlot,
 							&mtstate->mt_epqstate, mtstate->ps.state,
 							canSetTag);
 
-	ReleaseBuffer(buffer);
+	if (BufferIsValid(buffer))
+		ReleaseBuffer(buffer);
+	pfree(tuple);
 	return true;
 }
 
@@ -1582,7 +1562,7 @@ ExecModifyTable(PlanState *pstate)
 	ItemPointer tupleid;
 	ItemPointerData tuple_ctid;
 	HeapTupleData oldtupdata;
-	HeapTuple	oldtuple;
+	TableTuple oldtuple;
 
 	CHECK_FOR_INTERRUPTS();
 
diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c
index f2737bb7ef..4174015e83 100644
--- a/src/backend/executor/nodeTidscan.c
+++ b/src/backend/executor/nodeTidscan.c
@@ -22,6 +22,7 @@
  */
 #include "postgres.h"
 
+#include "access/tableam.h"
 #include "access/sysattr.h"
 #include "catalog/pg_type.h"
 #include "executor/execdebug.h"
@@ -306,7 +307,7 @@ TidNext(TidScanState *node)
 	ScanDirection direction;
 	Snapshot	snapshot;
 	Relation	heapRelation;
-	HeapTuple	tuple;
+	TableTuple tuple;
 	TupleTableSlot *slot;
 	Buffer		buffer = InvalidBuffer;
 	ItemPointerData *tidList;
@@ -331,12 +332,6 @@ TidNext(TidScanState *node)
 	tidList = node->tss_TidList;
 	numTids = node->tss_NumTids;
 
-	/*
-	 * We use node->tss_htup as the tuple pointer; note this can't just be a
-	 * local variable here, as the scan tuple slot will keep a pointer to it.
-	 */
-	tuple = &(node->tss_htup);
-
 	/*
 	 * Initialize or advance scan position, depending on direction.
 	 */
@@ -364,7 +359,7 @@ TidNext(TidScanState *node)
 
 	while (node->tss_TidPtr >= 0 && node->tss_TidPtr < numTids)
 	{
-		tuple->t_self = tidList[node->tss_TidPtr];
+		ItemPointerData tid = tidList[node->tss_TidPtr];
 
 		/*
 		 * For WHERE CURRENT OF, the tuple retrieved from the cursor might
@@ -372,9 +367,9 @@ TidNext(TidScanState *node)
 		 * current according to our snapshot.
 		 */
 		if (node->tss_isCurrentOf)
-			heap_get_latest_tid(heapRelation, snapshot, &tuple->t_self);
+			table_get_latest_tid(heapRelation, snapshot, &tid);
 
-		if (heap_fetch(heapRelation, snapshot, tuple, &buffer, false, NULL))
+		if (table_fetch(heapRelation, &tid, snapshot, &tuple, &buffer, false, NULL))
 		{
 			/*
 			 * store the scanned tuple in the scan tuple slot of the scan
@@ -385,14 +380,16 @@ TidNext(TidScanState *node)
 			 */
 			ExecStoreTuple(tuple,	/* tuple to store */
 						   slot,	/* slot to store in */
-						   buffer,	/* buffer associated with tuple  */
-						   false);	/* don't pfree */
+						   InvalidBuffer,	/* buffer associated with tuple  */
+						   true);	/* don't pfree */
 
 			/*
 			 * At this point we have an extra pin on the buffer, because
 			 * ExecStoreTuple incremented the pin count. Drop our local pin.
 			 */
-			ReleaseBuffer(buffer);
+			/* hari */
+			if (BufferIsValid(buffer))
+				ReleaseBuffer(buffer);
 
 			return slot;
 		}
diff --git a/src/backend/utils/adt/tid.c b/src/backend/utils/adt/tid.c
index 5ed5fdaffe..8d1c9aa336 100644
--- a/src/backend/utils/adt/tid.c
+++ b/src/backend/utils/adt/tid.c
@@ -22,6 +22,7 @@
 
 #include "access/heapam.h"
 #include "access/sysattr.h"
+#include "access/tableam.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_type.h"
 #include "libpq/pqformat.h"
@@ -352,7 +353,7 @@ currtid_byreloid(PG_FUNCTION_ARGS)
 	ItemPointerCopy(tid, result);
 
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	heap_get_latest_tid(rel, snapshot, result);
+	table_get_latest_tid(rel, snapshot, result);
 	UnregisterSnapshot(snapshot);
 
 	heap_close(rel, AccessShareLock);
@@ -387,7 +388,7 @@ currtid_byrelname(PG_FUNCTION_ARGS)
 	ItemPointerCopy(tid, result);
 
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	heap_get_latest_tid(rel, snapshot, result);
+	table_get_latest_tid(rel, snapshot, result);
 	UnregisterSnapshot(snapshot);
 
 	heap_close(rel, AccessShareLock);
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 9118e5c991..d8fa9d668a 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -134,7 +134,7 @@ extern void heap_parallelscan_initialize(ParallelHeapScanDesc target,
 extern void heap_parallelscan_reinitialize(ParallelHeapScanDesc parallel_scan);
 extern HeapScanDesc heap_beginscan_parallel(Relation, ParallelHeapScanDesc);
 
-extern bool heap_fetch(Relation relation, Snapshot snapshot,
+extern bool heap_fetch(Relation relation, ItemPointer tid, Snapshot snapshot,
 		   HeapTuple tuple, Buffer *userbuf, bool keep_buf,
 		   Relation stats_relation);
 extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation,
@@ -142,7 +142,6 @@ extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation,
 					   bool *all_dead, bool first_call);
 extern bool heap_hot_search(ItemPointer tid, Relation relation,
 				Snapshot snapshot, bool *all_dead);
-
 extern void heap_get_latest_tid(Relation relation, Snapshot snapshot,
 					ItemPointer tid);
 extern void setLastTid(const ItemPointer tid);
@@ -158,8 +157,8 @@ extern void heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 extern HTSU_Result heap_delete(Relation relation, ItemPointer tid,
 			CommandId cid, Snapshot crosscheck, bool wait,
 			HeapUpdateFailureData *hufd);
-extern void heap_finish_speculative(Relation relation, HeapTuple tuple);
-extern void heap_abort_speculative(Relation relation, HeapTuple tuple);
+extern void heap_finish_speculative(Relation relation, TupleTableSlot *slot);
+extern void heap_abort_speculative(Relation relation, TupleTableSlot *slot);
 extern HTSU_Result heap_update(Relation relation, ItemPointer otid,
 			HeapTuple newtup,
 			CommandId cid, Snapshot crosscheck, bool wait,
@@ -168,6 +167,7 @@ extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 				CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 				bool follow_update,
 				Buffer *buffer, HeapUpdateFailureData *hufd);
+
 extern void heap_inplace_update(Relation relation, HeapTuple tuple);
 extern bool heap_freeze_tuple(HeapTupleHeader tuple,
 				  TransactionId relfrozenxid, TransactionId relminmxid,
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index b1ceb854cd..3eb93c9112 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -816,6 +816,7 @@ extern Datum heap_getsysattr(HeapTuple tup, int attnum, TupleDesc tupleDesc,
 extern HeapTuple heap_copytuple(HeapTuple tuple);
 extern void heap_copytuple_with_tuple(HeapTuple src, HeapTuple dest);
 extern Datum heap_copy_tuple_as_datum(HeapTuple tuple, TupleDesc tupleDesc);
+extern HeapTuple heap_form_tuple_by_datum(Datum data, Oid relid);
 extern HeapTuple heap_form_tuple(TupleDesc tupleDescriptor,
 				Datum *values, bool *isnull);
 extern HeapTuple heap_modify_tuple(HeapTuple tuple,
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
new file mode 100644
index 0000000000..1c5416235f
--- /dev/null
+++ b/src/include/access/tableam.h
@@ -0,0 +1,84 @@
+/*-------------------------------------------------------------------------
+ *
+ * tableam.h
+ *	  POSTGRES table access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/tableam.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef TABLEAM_H
+#define TABLEAM_H
+
+#include "access/heapam.h"
+#include "access/tableam_common.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
+
+typedef union tuple_data
+{
+	TransactionId xid;
+	CommandId	cid;
+	ItemPointerData tid;
+}			tuple_data;
+
+typedef enum tuple_data_flags
+{
+	XMIN = 0,
+	UPDATED_XID,
+	CMIN,
+	TID,
+	CTID
+}			tuple_data_flags;
+
+/* Function pointer to let the index tuple insert from storage am */
+typedef List *(*InsertIndexTuples) (TupleTableSlot *slot, EState *estate, bool noDupErr,
+									bool *specConflict, List *arbiterIndexes);
+
+/* Function pointer to let the index tuple delete from storage am */
+typedef void (*DeleteIndexTuples) (Relation rel, ItemPointer tid, TransactionId old_xmin);
+
+extern bool table_fetch(Relation relation,
+			  ItemPointer tid,
+			  Snapshot snapshot,
+			  TableTuple * stuple,
+			  Buffer *userbuf,
+			  bool keep_buf,
+			  Relation stats_relation);
+
+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 Oid table_insert(Relation relation, TupleTableSlot *slot, CommandId cid,
+			   int options, BulkInsertState bistate, InsertIndexTuples IndexFunc,
+			   EState *estate, List *arbiterIndexes, List **recheckIndexes);
+
+extern HTSU_Result table_delete(Relation relation, ItemPointer tid, CommandId cid,
+			   Snapshot crosscheck, bool wait, DeleteIndexTuples IndexFunc,
+			   HeapUpdateFailureData *hufd);
+
+extern HTSU_Result table_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
+			   EState *estate, CommandId cid, Snapshot crosscheck, bool wait,
+			   HeapUpdateFailureData *hufd, LockTupleMode *lockmode,
+			   InsertIndexTuples IndexFunc, List **recheckIndexes);
+
+extern void table_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
+					 CommandId cid, int options, BulkInsertState bistate);
+
+extern tuple_data table_tuple_get_data(Relation relation, TableTuple tuple, tuple_data_flags flags);
+
+extern TableTuple table_tuple_by_datum(Relation relation, Datum data, Oid tableoid);
+
+extern void table_get_latest_tid(Relation relation,
+					   Snapshot snapshot,
+					   ItemPointer tid);
+
+extern void table_sync(Relation rel);
+
+#endif		/* TABLEAM_H */
diff --git a/src/include/access/tableam_common.h b/src/include/access/tableam_common.h
index f4f279a7bc..26541abbde 100644
--- a/src/include/access/tableam_common.h
+++ b/src/include/access/tableam_common.h
@@ -56,9 +56,6 @@ typedef MinimalTuple (*SlotGetMinTuple_function) (TupleTableSlot *slot, bool pal
 
 typedef void (*SlotUpdateTableoid_function) (TupleTableSlot *slot, Oid tableoid);
 
-typedef void (*SpeculativeAbort_function) (Relation rel,
-										   TupleTableSlot *slot);
-
 typedef struct SlotTableAmRoutine
 {
 	/* Operations on TupleTableSlot */
diff --git a/src/include/access/tableamapi.h b/src/include/access/tableamapi.h
index 03d6cd42f3..d6293fc44d 100644
--- a/src/include/access/tableamapi.h
+++ b/src/include/access/tableamapi.h
@@ -11,7 +11,9 @@
 #ifndef TABLEEAMAPI_H
 #define TABLEEAMAPI_H
 
-#include "access/tableam_common.h"
+#include "access/heapam.h"
+#include "access/tableam.h"
+#include "nodes/execnodes.h"
 #include "nodes/nodes.h"
 #include "fmgr.h"
 #include "utils/snapshot.h"
@@ -24,6 +26,61 @@ typedef bool (*SnapshotSatisfies_function) (TableTuple htup, Snapshot snapshot,
 typedef HTSU_Result (*SnapshotSatisfiesUpdate_function) (TableTuple htup, CommandId curcid, Buffer buffer);
 typedef HTSV_Result (*SnapshotSatisfiesVacuum_function) (TableTuple htup, TransactionId OldestXmin, Buffer buffer);
 
+typedef Oid (*TupleInsert_function) (Relation rel, TupleTableSlot *slot, CommandId cid,
+									 int options, BulkInsertState bistate, InsertIndexTuples IndexFunc,
+									 EState *estate, List *arbiterIndexes, List **recheckIndexes);
+
+typedef HTSU_Result (*TupleDelete_function) (Relation relation,
+											 ItemPointer tid,
+											 CommandId cid,
+											 Snapshot crosscheck,
+											 bool wait,
+											 DeleteIndexTuples IndexFunc,
+											 HeapUpdateFailureData *hufd);
+
+typedef HTSU_Result (*TupleUpdate_function) (Relation relation,
+											 ItemPointer otid,
+											 TupleTableSlot *slot,
+											 EState *estate,
+											 CommandId cid,
+											 Snapshot crosscheck,
+											 bool wait,
+											 HeapUpdateFailureData *hufd,
+											 LockTupleMode *lockmode,
+											 InsertIndexTuples IndexFunc,
+											 List **recheckIndexes);
+
+typedef bool (*TupleFetch_function) (Relation relation,
+									 ItemPointer tid,
+									 Snapshot snapshot,
+									 TableTuple * tuple,
+									 Buffer *userbuf,
+									 bool keep_buf,
+									 Relation stats_relation);
+
+typedef HTSU_Result (*TupleLock_function) (Relation relation,
+										   ItemPointer tid,
+										   TableTuple * tuple,
+										   CommandId cid,
+										   LockTupleMode mode,
+										   LockWaitPolicy wait_policy,
+										   bool follow_update,
+										   Buffer *buffer,
+										   HeapUpdateFailureData *hufd);
+
+typedef void (*MultiInsert_function) (Relation relation, HeapTuple *tuples, int ntuples,
+									  CommandId cid, int options, BulkInsertState bistate);
+
+typedef void (*TupleGetLatestTid_function) (Relation relation,
+											Snapshot snapshot,
+											ItemPointer tid);
+
+typedef tuple_data(*GetTupleData_function) (TableTuple tuple, tuple_data_flags flags);
+
+typedef TableTuple(*TupleFromDatum_function) (Datum data, Oid tableoid);
+
+typedef void (*RelationSync_function) (Relation relation);
+
 /*
  * API struct for a table AM.  Note this must be stored in a single palloc'd
  * chunk of memory.
@@ -43,6 +100,20 @@ typedef struct TableAmRoutine
 
 	slot_tableam_hook slot_storageam;
 
+	/* Operations on physical tuples */
+	TupleInsert_function tuple_insert;	/* heap_insert */
+	TupleUpdate_function tuple_update;	/* heap_update */
+	TupleDelete_function tuple_delete;	/* heap_delete */
+	TupleFetch_function tuple_fetch;	/* heap_fetch */
+	TupleLock_function tuple_lock;	/* heap_lock_tuple */
+	MultiInsert_function multi_insert;	/* heap_multi_insert */
+	TupleGetLatestTid_function tuple_get_latest_tid;	/* heap_get_latest_tid */
+
+	GetTupleData_function get_tuple_data;
+	TupleFromDatum_function tuple_from_datum;
+
+	RelationSync_function relation_sync;	/* heap_sync */
+
 }			TableAmRoutine;
 
 extern TableAmRoutine * GetTableAmRoutine(Oid amhandler);
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index ff5546cf28..093d1ae112 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -190,7 +190,7 @@ extern TupleTableSlot *ExecBRInsertTriggers(EState *estate,
 					 TupleTableSlot *slot);
 extern void ExecARInsertTriggers(EState *estate,
 					 ResultRelInfo *relinfo,
-					 HeapTuple trigtuple,
+					 TupleTableSlot *slot,
 					 List *recheckIndexes,
 					 TransitionCaptureState *transition_capture);
 extern TupleTableSlot *ExecIRInsertTriggers(EState *estate,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index a782fae0f8..00cf705c05 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -201,16 +201,16 @@ 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 HeapTuple EvalPlanQualFetch(EState *estate, Relation relation,
-				  int lockmode, LockWaitPolicy wait_policy, ItemPointer tid,
-				  TransactionId priorXmax);
+extern TableTuple EvalPlanQualFetch(EState *estate, Relation relation,
+									  int lockmode, LockWaitPolicy wait_policy, ItemPointer tid,
+									  TransactionId priorXmax);
 extern void EvalPlanQualInit(EPQState *epqstate, EState *estate,
 				 Plan *subplan, List *auxrowmarks, int epqParam);
 extern void EvalPlanQualSetPlan(EPQState *epqstate,
 					Plan *subplan, List *auxrowmarks);
 extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
-					 HeapTuple tuple);
-extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+		TableTuple tuple);
+extern TableTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
 
 #define EvalPlanQualSetSlot(epqstate, slot)  ((epqstate)->origslot = (slot))
 extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
@@ -525,9 +525,8 @@ extern int	ExecCleanTargetListLength(List *targetlist);
  */
 extern void ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative);
 extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
-extern List *ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid,
-					  EState *estate, bool noDupErr, bool *specConflict,
-					  List *arbiterIndexes);
+extern List *ExecInsertIndexTuples(TupleTableSlot *slot, EState *estate, bool noDupErr,
+					  bool *specConflict, List *arbiterIndexes);
 extern bool ExecCheckIndexConstraints(TupleTableSlot *slot, EState *estate,
 						  ItemPointer conflictTid, List *arbiterIndexes);
 extern void check_exclusion_constraint(Relation heap, Relation index,
diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h
index 79878c2cb9..8358899923 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -133,6 +133,7 @@ typedef struct TupleTableSlot
 	Oid			tts_tupleOid;	/* XXX describe */
 	int			tts_nvalid;		/* # of valid values in tts_values */
 	uint32		tts_speculativeToken;	/* XXX describe */
+	bool		tts_specConflict;	/* XXX describe */
 	bool		tts_shouldFree;
 	bool		tts_shouldFreeMin;
 	Datum	   *tts_values;		/* current per-attribute values */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 2a4f7407a1..384a33b516 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -514,7 +514,7 @@ typedef struct EState
 	 * remember if the tuple has been returned already.  Arrays are of size
 	 * list_length(es_range_table) and are indexed by scan node scanrelid - 1.
 	 */
-	HeapTuple  *es_epqTuple;	/* array of EPQ substitute tuples */
+	TableTuple *es_epqTuple;	/* array of EPQ substitute tuples */
 	bool	   *es_epqTupleSet; /* true if EPQ tuple is provided */
 	bool	   *es_epqScanDone; /* true if EPQ tuple has been fetched */
 
@@ -2004,7 +2004,7 @@ typedef struct HashInstrumentation
 	int			nbatch;			/* number of batches at end of execution */
 	int			nbatch_original;	/* planned number of batches */
 	size_t		space_peak;		/* speak memory usage in bytes */
-} HashInstrumentation;
+}			HashInstrumentation;
 
 /* ----------------
  *	 Shared memory container for per-worker hash information
@@ -2014,7 +2014,7 @@ typedef struct SharedHashInfo
 {
 	int			num_workers;
 	HashInstrumentation hinstrument[FLEXIBLE_ARRAY_MEMBER];
-} SharedHashInfo;
+}			SharedHashInfo;
 
 /* ----------------
  *	 HashState information
@@ -2075,7 +2075,7 @@ typedef struct LockRowsState
 	PlanState	ps;				/* its first field is NodeTag */
 	List	   *lr_arowMarks;	/* List of ExecAuxRowMarks */
 	EPQState	lr_epqstate;	/* for evaluating EvalPlanQual rechecks */
-	HeapTuple  *lr_curtuples;	/* locked tuples (one entry per RT entry) */
+	TableTuple 	   *lr_curtuples; /* locked tuples (one entry per RT entry) */
 	int			lr_ntables;		/* length of lr_curtuples[] array */
 } LockRowsState;
 
-- 
2.15.0.windows.1



  [application/octet-stream] 0007-Scan-functions-are-added-to-table-AM.patch (103.2K, ../../CAJrrPGdY_hgvu06R_vCibUktKRLp=b8nBfeZkh=KRc95tq4euA@mail.gmail.com/12-0007-Scan-functions-are-added-to-table-AM.patch)
  download | inline diff:
From c8823a3da61731a204c5620dab728dd0d007843f Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Tue, 9 Jan 2018 16:59:20 +1100
Subject: [PATCH 07/12] Scan functions are added to table AM

Replaced HeapTuple with StorageTuple wherever
possible.

Currently directly returning slot functionality
instead of tuple is added only to limited number
of places.
---
 contrib/pgrowlocks/pgrowlocks.c            |   6 +-
 contrib/pgstattuple/pgstattuple.c          |   6 +-
 src/backend/access/heap/heapam.c           | 212 ++++++++++-----------------
 src/backend/access/heap/heapam_handler.c   |   9 ++
 src/backend/access/index/genam.c           |  11 +-
 src/backend/access/index/indexam.c         |  13 +-
 src/backend/access/nbtree/nbtinsert.c      |   7 +-
 src/backend/access/table/tableam.c         | 223 +++++++++++++++++++++++++++++
 src/backend/bootstrap/bootstrap.c          |  25 ++--
 src/backend/catalog/aclchk.c               |  13 +-
 src/backend/catalog/index.c                |  59 ++++----
 src/backend/catalog/partition.c            |   7 +-
 src/backend/catalog/pg_conversion.c        |   7 +-
 src/backend/catalog/pg_db_role_setting.c   |   7 +-
 src/backend/catalog/pg_publication.c       |   7 +-
 src/backend/catalog/pg_subscription.c      |   7 +-
 src/backend/commands/cluster.c             |  12 +-
 src/backend/commands/constraint.c          |   3 +-
 src/backend/commands/copy.c                |   6 +-
 src/backend/commands/dbcommands.c          |  19 +--
 src/backend/commands/indexcmds.c           |   7 +-
 src/backend/commands/tablecmds.c           |  30 ++--
 src/backend/commands/tablespace.c          |  39 ++---
 src/backend/commands/typecmds.c            |  13 +-
 src/backend/commands/vacuum.c              |  13 +-
 src/backend/executor/execAmi.c             |   2 +-
 src/backend/executor/execIndexing.c        |  13 +-
 src/backend/executor/execReplication.c     |  15 +-
 src/backend/executor/execTuples.c          |   8 +-
 src/backend/executor/functions.c           |   4 +-
 src/backend/executor/nodeAgg.c             |   4 +-
 src/backend/executor/nodeBitmapHeapscan.c  |  19 +--
 src/backend/executor/nodeForeignscan.c     |   6 +-
 src/backend/executor/nodeGather.c          |   8 +-
 src/backend/executor/nodeGatherMerge.c     |  14 +-
 src/backend/executor/nodeIndexonlyscan.c   |   8 +-
 src/backend/executor/nodeIndexscan.c       |  16 +--
 src/backend/executor/nodeSamplescan.c      |  34 ++---
 src/backend/executor/nodeSeqscan.c         |  45 ++----
 src/backend/executor/nodeWindowAgg.c       |   4 +-
 src/backend/executor/spi.c                 |  20 +--
 src/backend/executor/tqueue.c              |   2 +-
 src/backend/postmaster/autovacuum.c        |  18 +--
 src/backend/postmaster/pgstat.c            |   7 +-
 src/backend/replication/logical/launcher.c |   7 +-
 src/backend/rewrite/rewriteDefine.c        |   7 +-
 src/backend/utils/init/postinit.c          |   7 +-
 src/include/access/heapam.h                |  27 ++--
 src/include/access/tableam.h               |  35 +++++
 src/include/access/tableam_common.h        |   1 +
 src/include/access/tableamapi.h            |  44 ++++++
 src/include/executor/functions.h           |   2 +-
 src/include/executor/spi.h                 |  12 +-
 src/include/executor/tqueue.h              |   4 +-
 src/include/funcapi.h                      |   2 +-
 55 files changed, 711 insertions(+), 445 deletions(-)

diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index 03710c68c3..d3b7170972 100644
--- a/contrib/pgrowlocks/pgrowlocks.c
+++ b/contrib/pgrowlocks/pgrowlocks.c
@@ -125,7 +125,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
 			aclcheck_error(aclresult, ACL_KIND_CLASS,
 						   RelationGetRelationName(rel));
 
-		scan = heap_beginscan(rel, GetActiveSnapshot(), 0, NULL);
+		scan = table_beginscan(rel, GetActiveSnapshot(), 0, NULL);
 		mydata = palloc(sizeof(*mydata));
 		mydata->rel = rel;
 		mydata->scan = scan;
@@ -141,7 +141,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
 	scan = mydata->scan;
 
 	/* scan the relation */
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = table_scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		HTSU_Result htsu;
 		TransactionId xmax;
@@ -306,7 +306,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
 		}
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 	heap_close(mydata->rel, AccessShareLock);
 
 	SRF_RETURN_DONE(funcctx);
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index 88583b1e57..7340c4f3dd 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -325,13 +325,13 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 	TableAmRoutine *method = rel->rd_tableamroutine;
 
 	/* Disable syncscan because we assume we scan from block zero upwards */
-	scan = heap_beginscan_strat(rel, SnapshotAny, 0, NULL, true, false);
+	scan = table_beginscan_strat(rel, SnapshotAny, 0, NULL, true, false);
 	InitDirtySnapshot(SnapshotDirty);
 
 	nblocks = scan->rs_nblocks; /* # blocks to be scanned */
 
 	/* scan the relation */
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = table_scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		CHECK_FOR_INTERRUPTS();
 
@@ -384,7 +384,7 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 		block++;
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 	relation_close(rel, AccessShareLock);
 
 	stat.table_len = (uint64) nblocks * BLCKSZ;
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index a74b96d6cd..c1f3d9d3d3 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -80,17 +80,6 @@
 /* GUC variable */
 bool		synchronize_seqscans = true;
 
-
-static HeapScanDesc heap_beginscan_internal(Relation relation,
-						Snapshot snapshot,
-						int nkeys, ScanKey key,
-						ParallelHeapScanDesc parallel_scan,
-						bool allow_strat,
-						bool allow_sync,
-						bool allow_pagemode,
-						bool is_bitmapscan,
-						bool is_samplescan,
-						bool temp_snap);
 static void heap_parallelscan_startblock_init(HeapScanDesc scan);
 static BlockNumber heap_parallelscan_nextpage(HeapScanDesc scan);
 static HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup,
@@ -1387,87 +1376,16 @@ heap_openrv_extended(const RangeVar *relation, LOCKMODE lockmode,
 	return r;
 }
 
-
-/* ----------------
- *		heap_beginscan	- begin relation scan
- *
- * heap_beginscan is the "standard" case.
- *
- * heap_beginscan_catalog differs in setting up its own temporary snapshot.
- *
- * heap_beginscan_strat offers an extended API that lets the caller control
- * whether a nondefault buffer access strategy can be used, and whether
- * syncscan can be chosen (possibly resulting in the scan not starting from
- * block zero).  Both of these default to true with plain heap_beginscan.
- *
- * heap_beginscan_bm is an alternative entry point for setting up a
- * HeapScanDesc for a bitmap heap scan.  Although that scan technology is
- * really quite unlike a standard seqscan, there is just enough commonality
- * to make it worth using the same data structure.
- *
- * heap_beginscan_sampling is an alternative entry point for setting up a
- * HeapScanDesc for a TABLESAMPLE scan.  As with bitmap scans, it's worth
- * using the same data structure although the behavior is rather different.
- * In addition to the options offered by heap_beginscan_strat, this call
- * also allows control of whether page-mode visibility checking is used.
- * ----------------
- */
 HeapScanDesc
 heap_beginscan(Relation relation, Snapshot snapshot,
-			   int nkeys, ScanKey key)
-{
-	return heap_beginscan_internal(relation, snapshot, nkeys, key, NULL,
-								   true, true, true, false, false, false);
-}
-
-HeapScanDesc
-heap_beginscan_catalog(Relation relation, int nkeys, ScanKey key)
-{
-	Oid			relid = RelationGetRelid(relation);
-	Snapshot	snapshot = RegisterSnapshot(GetCatalogSnapshot(relid));
-
-	return heap_beginscan_internal(relation, snapshot, nkeys, key, NULL,
-								   true, true, true, false, false, true);
-}
-
-HeapScanDesc
-heap_beginscan_strat(Relation relation, Snapshot snapshot,
-					 int nkeys, ScanKey key,
-					 bool allow_strat, bool allow_sync)
-{
-	return heap_beginscan_internal(relation, snapshot, nkeys, key, NULL,
-								   allow_strat, allow_sync, true,
-								   false, false, false);
-}
-
-HeapScanDesc
-heap_beginscan_bm(Relation relation, Snapshot snapshot,
-				  int nkeys, ScanKey key)
-{
-	return heap_beginscan_internal(relation, snapshot, nkeys, key, NULL,
-								   false, false, true, true, false, false);
-}
-
-HeapScanDesc
-heap_beginscan_sampling(Relation relation, Snapshot snapshot,
-						int nkeys, ScanKey key,
-						bool allow_strat, bool allow_sync, bool allow_pagemode)
-{
-	return heap_beginscan_internal(relation, snapshot, nkeys, key, NULL,
-								   allow_strat, allow_sync, allow_pagemode,
-								   false, true, false);
-}
-
-static HeapScanDesc
-heap_beginscan_internal(Relation relation, Snapshot snapshot,
-						int nkeys, ScanKey key,
-						ParallelHeapScanDesc parallel_scan,
-						bool allow_strat,
-						bool allow_sync,
-						bool allow_pagemode,
-						bool is_bitmapscan,
-						bool is_samplescan,
-						bool temp_snap)
+			   int nkeys, ScanKey key,
+			   ParallelHeapScanDesc parallel_scan,
+			   bool allow_strat,
+			   bool allow_sync,
+			   bool allow_pagemode,
+			   bool is_bitmapscan,
+			   bool is_samplescan,
+			   bool temp_snap)
 {
 	HeapScanDesc scan;
 
@@ -1537,9 +1455,16 @@ heap_beginscan_internal(Relation relation, Snapshot snapshot,
  * ----------------
  */
 void
-heap_rescan(HeapScanDesc scan,
-			ScanKey key)
+heap_rescan(HeapScanDesc scan, ScanKey key, bool set_params,
+			bool allow_strat, bool allow_sync, bool allow_pagemode)
 {
+	if (set_params)
+	{
+		scan->rs_allow_strat = allow_strat;
+		scan->rs_allow_sync = allow_sync;
+		scan->rs_pageatatime = allow_pagemode && IsMVCCSnapshot(scan->rs_snapshot);
+	}
+
 	/*
 	 * unpin scan buffers
 	 */
@@ -1550,27 +1475,21 @@ heap_rescan(HeapScanDesc scan,
 	 * reinitialize scan descriptor
 	 */
 	initscan(scan, key, true);
-}
 
-/* ----------------
- *		heap_rescan_set_params	- restart a relation scan after changing params
- *
- * This call allows changing the buffer strategy, syncscan, and pagemode
- * options before starting a fresh scan.  Note that although the actual use
- * of syncscan might change (effectively, enabling or disabling reporting),
- * the previously selected startblock will be kept.
- * ----------------
- */
-void
-heap_rescan_set_params(HeapScanDesc scan, ScanKey key,
-					   bool allow_strat, bool allow_sync, bool allow_pagemode)
-{
-	/* adjust parameters */
-	scan->rs_allow_strat = allow_strat;
-	scan->rs_allow_sync = allow_sync;
-	scan->rs_pageatatime = allow_pagemode && IsMVCCSnapshot(scan->rs_snapshot);
-	/* ... and rescan */
-	heap_rescan(scan, key);
+	/*
+	 * reset parallel scan, if present
+	 */
+	if (scan->rs_parallel != NULL)
+	{
+		ParallelHeapScanDesc parallel_scan;
+
+		/*
+		 * Caller is responsible for making sure that all workers have
+		 * finished the scan before calling this.
+		 */
+		parallel_scan = scan->rs_parallel;
+		pg_atomic_write_u64(&parallel_scan->phs_nallocated, 0);
+	}
 }
 
 /* ----------------
@@ -1659,25 +1578,6 @@ heap_parallelscan_reinitialize(ParallelHeapScanDesc parallel_scan)
 	pg_atomic_write_u64(&parallel_scan->phs_nallocated, 0);
 }
 
-/* ----------------
- *		heap_beginscan_parallel - join a parallel scan
- *
- *		Caller must hold a suitable lock on the correct relation.
- * ----------------
- */
-HeapScanDesc
-heap_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan)
-{
-	Snapshot	snapshot;
-
-	Assert(RelationGetRelid(relation) == parallel_scan->phs_relid);
-	snapshot = RestoreSnapshot(parallel_scan->phs_snapshot_data);
-	RegisterSnapshot(snapshot);
-
-	return heap_beginscan_internal(relation, snapshot, 0, NULL, parallel_scan,
-								   true, true, true, false, false, true);
-}
-
 /* ----------------
  *		heap_parallelscan_startblock_init - find and set the scan's startblock
  *
@@ -1822,8 +1722,7 @@ heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
 #define HEAPDEBUG_3
 #endif							/* !defined(HEAPDEBUGALL) */
 
-
-HeapTuple
+TableTuple
 heap_getnext(HeapScanDesc scan, ScanDirection direction)
 {
 	/* Note: no locking manipulations needed */
@@ -1853,6 +1752,53 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 	return heap_copytuple(&(scan->rs_ctup));
 }
 
+#ifdef HEAPAMSLOTDEBUGALL
+#define HEAPAMSLOTDEBUG_1 \
+	elog(DEBUG2, "heapam_getnext([%s,nkeys=%d],dir=%d) called", \
+		 RelationGetRelationName(scan->rs_rd), scan->rs_nkeys, (int) direction)
+#define HEAPAMSLOTDEBUG_2 \
+	elog(DEBUG2, "heapam_getnext returning EOS")
+#define HEAPAMSLOTDEBUG_3 \
+	elog(DEBUG2, "heapam_getnext returning tuple")
+#else
+#define HEAPAMSLOTDEBUG_1
+#define HEAPAMSLOTDEBUG_2
+#define HEAPAMSLOTDEBUG_3
+#endif
+
+TupleTableSlot *
+heap_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
+{
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
+	/* Note: no locking manipulations needed */
+
+	HEAPAMSLOTDEBUG_1;			/* heap_getnext( info ) */
+
+	if (scan->rs_pageatatime)
+		heapgettup_pagemode(scan, direction,
+							scan->rs_nkeys, scan->rs_key);
+	else
+		heapgettup(scan, direction, scan->rs_nkeys, scan->rs_key);
+
+	if (scan->rs_ctup.t_data == NULL)
+	{
+		HEAPAMSLOTDEBUG_2;		/* heap_getnext returning EOS */
+		ExecClearTuple(slot);
+		return slot;
+	}
+
+	/*
+	 * if we get here it means we have a new current scan tuple, so point to
+	 * the proper return buffer and return the tuple.
+	 */
+	HEAPAMSLOTDEBUG_3;			/* heap_getnext returning tuple */
+
+	pgstat_count_heap_getnext(scan->rs_rd);
+	return ExecStoreTuple(heap_copytuple(&(scan->rs_ctup)),
+						  slot, InvalidBuffer, true);
+}
+
 /*
  *	heap_fetch		- retrieve tuple with given tid
  *
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0470b8639b..3e57f77611 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -310,6 +310,15 @@ heap_tableam_handler(PG_FUNCTION_ARGS)
 
 	amroutine->slot_storageam = slot_tableam_handler;
 
+	amroutine->scan_begin = heap_beginscan;
+	amroutine->scansetlimits = heap_setscanlimits;
+	amroutine->scan_getnext = heap_getnext;
+	amroutine->scan_getnextslot = heap_getnextslot;
+	amroutine->scan_end = heap_endscan;
+	amroutine->scan_rescan = heap_rescan;
+	amroutine->scan_update_snapshot = heap_update_snapshot;
+	amroutine->hot_search_buffer = heap_hot_search_buffer;
+
 	amroutine->tuple_fetch = heapam_fetch;
 	amroutine->tuple_insert = heapam_heap_insert;
 	amroutine->tuple_delete = heapam_heap_delete;
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 8760905e72..105631ad38 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -20,6 +20,7 @@
 #include "postgres.h"
 
 #include "access/relscan.h"
+#include "access/tableam.h"
 #include "access/transam.h"
 #include "catalog/index.h"
 #include "lib/stringinfo.h"
@@ -394,9 +395,9 @@ systable_beginscan(Relation heapRelation,
 		 * disadvantage; and there are no compensating advantages, because
 		 * it's unlikely that such scans will occur in parallel.
 		 */
-		sysscan->scan = heap_beginscan_strat(heapRelation, snapshot,
-											 nkeys, key,
-											 true, false);
+		sysscan->scan = table_beginscan_strat(heapRelation, snapshot,
+												nkeys, key,
+												true, false);
 		sysscan->iscan = NULL;
 	}
 
@@ -432,7 +433,7 @@ systable_getnext(SysScanDesc sysscan)
 			elog(ERROR, "system catalog scans with lossy index conditions are not implemented");
 	}
 	else
-		htup = heap_getnext(sysscan->scan, ForwardScanDirection);
+		htup = table_scan_getnext(sysscan->scan, ForwardScanDirection);
 
 	return htup;
 }
@@ -504,7 +505,7 @@ systable_endscan(SysScanDesc sysscan)
 		index_close(sysscan->irel, AccessShareLock);
 	}
 	else
-		heap_endscan(sysscan->scan);
+		table_endscan(sysscan->scan);
 
 	if (sysscan->snapshot)
 		UnregisterSnapshot(sysscan->snapshot);
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index 1b61cd9515..f1029aca00 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -71,6 +71,7 @@
 
 #include "access/amapi.h"
 #include "access/relscan.h"
+#include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "catalog/catalog.h"
@@ -605,12 +606,12 @@ index_fetch_heap(IndexScanDesc scan)
 
 	/* Obtain share-lock on the buffer so we can examine visibility */
 	LockBuffer(scan->xs_cbuf, BUFFER_LOCK_SHARE);
-	got_heap_tuple = heap_hot_search_buffer(tid, scan->heapRelation,
-											scan->xs_cbuf,
-											scan->xs_snapshot,
-											&scan->xs_ctup,
-											&all_dead,
-											!scan->xs_continue_hot);
+	got_heap_tuple = table_hot_search_buffer(tid, scan->heapRelation,
+											   scan->xs_cbuf,
+											   scan->xs_snapshot,
+											   &scan->xs_ctup,
+											   &all_dead,
+											   !scan->xs_continue_hot);
 	LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK);
 
 	if (got_heap_tuple)
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index 51059c0c7d..1c01d8ae12 100644
--- a/src/backend/access/nbtree/nbtinsert.c
+++ b/src/backend/access/nbtree/nbtinsert.c
@@ -18,6 +18,7 @@
 #include "access/heapam.h"
 #include "access/nbtree.h"
 #include "access/nbtxlog.h"
+#include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xloginsert.h"
 #include "miscadmin.h"
@@ -325,8 +326,8 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
 				 * that satisfies SnapshotDirty.  This is necessary because we
 				 * have just a single index entry for the entire chain.
 				 */
-				else if (heap_hot_search(&htid, heapRel, &SnapshotDirty,
-										 &all_dead))
+				else if (table_hot_search(&htid, heapRel, &SnapshotDirty,
+											&all_dead))
 				{
 					TransactionId xwait;
 
@@ -379,7 +380,7 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
 					 * entry.
 					 */
 					htid = itup->t_tid;
-					if (heap_hot_search(&htid, heapRel, SnapshotSelf, NULL))
+					if (table_hot_search(&htid, heapRel, SnapshotSelf, NULL))
 					{
 						/* Normal case --- it's still live */
 					}
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index eaeb888b55..e0a09757ed 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -16,7 +16,9 @@
 
 #include "access/tableam.h"
 #include "access/tableamapi.h"
+#include "access/relscan.h"
 #include "utils/rel.h"
+#include "utils/tqual.h"
 
 /*
  *	table_fetch		- retrieve tuple with given tid
@@ -48,6 +50,174 @@ table_lock_tuple(Relation relation, ItemPointer tid, TableTuple * stuple,
 												follow_updates, buffer, hufd);
 }
 
+/* ----------------
+ *		heap_beginscan_parallel - join a parallel scan
+ *
+ *		Caller must hold a suitable lock on the correct relation.
+ * ----------------
+ */
+HeapScanDesc
+table_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan)
+{
+	Snapshot	snapshot;
+
+	Assert(RelationGetRelid(relation) == parallel_scan->phs_relid);
+	snapshot = RestoreSnapshot(parallel_scan->phs_snapshot_data);
+	RegisterSnapshot(snapshot);
+
+	return relation->rd_tableamroutine->scan_begin(relation, snapshot, 0, NULL, parallel_scan,
+												true, true, true, false, false, true);
+}
+
+/*
+ * heap_setscanlimits - restrict range of a heapscan
+ *
+ * startBlk is the page to start at
+ * numBlks is number of pages to scan (InvalidBlockNumber means "all")
+ */
+void
+table_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
+{
+	sscan->rs_rd->rd_tableamroutine->scansetlimits(sscan, startBlk, numBlks);
+}
+
+
+/* ----------------
+ *		heap_beginscan	- begin relation scan
+ *
+ * heap_beginscan is the "standard" case.
+ *
+ * heap_beginscan_catalog differs in setting up its own temporary snapshot.
+ *
+ * heap_beginscan_strat offers an extended API that lets the caller control
+ * whether a nondefault buffer access strategy can be used, and whether
+ * syncscan can be chosen (possibly resulting in the scan not starting from
+ * block zero).  Both of these default to true with plain heap_beginscan.
+ *
+ * heap_beginscan_bm is an alternative entry point for setting up a
+ * HeapScanDesc for a bitmap heap scan.  Although that scan technology is
+ * really quite unlike a standard seqscan, there is just enough commonality
+ * to make it worth using the same data structure.
+ *
+ * heap_beginscan_sampling is an alternative entry point for setting up a
+ * HeapScanDesc for a TABLESAMPLE scan.  As with bitmap scans, it's worth
+ * using the same data structure although the behavior is rather different.
+ * In addition to the options offered by heap_beginscan_strat, this call
+ * also allows control of whether page-mode visibility checking is used.
+ * ----------------
+ */
+HeapScanDesc
+table_beginscan(Relation relation, Snapshot snapshot,
+				  int nkeys, ScanKey key)
+{
+	return relation->rd_tableamroutine->scan_begin(relation, snapshot, nkeys, key, NULL,
+												true, true, true, false, false, false);
+}
+
+HeapScanDesc
+table_beginscan_catalog(Relation relation, int nkeys, ScanKey key)
+{
+	Oid			relid = RelationGetRelid(relation);
+	Snapshot	snapshot = RegisterSnapshot(GetCatalogSnapshot(relid));
+
+	return relation->rd_tableamroutine->scan_begin(relation, snapshot, nkeys, key, NULL,
+												true, true, true, false, false, true);
+}
+
+HeapScanDesc
+table_beginscan_strat(Relation relation, Snapshot snapshot,
+						int nkeys, ScanKey key,
+						bool allow_strat, bool allow_sync)
+{
+	return relation->rd_tableamroutine->scan_begin(relation, snapshot, nkeys, key, NULL,
+												allow_strat, allow_sync, true,
+												false, false, false);
+}
+
+HeapScanDesc
+table_beginscan_bm(Relation relation, Snapshot snapshot,
+					 int nkeys, ScanKey key)
+{
+	return relation->rd_tableamroutine->scan_begin(relation, snapshot, nkeys, key, NULL,
+												false, false, true, true, false, false);
+}
+
+HeapScanDesc
+table_beginscan_sampling(Relation relation, Snapshot snapshot,
+						   int nkeys, ScanKey key,
+						   bool allow_strat, bool allow_sync, bool allow_pagemode)
+{
+	return relation->rd_tableamroutine->scan_begin(relation, snapshot, nkeys, key, NULL,
+												allow_strat, allow_sync, allow_pagemode,
+												false, true, false);
+}
+
+/* ----------------
+ *		heap_rescan		- restart a relation scan
+ * ----------------
+ */
+void
+table_rescan(HeapScanDesc scan,
+			   ScanKey key)
+{
+	scan->rs_rd->rd_tableamroutine->scan_rescan(scan, key, false, false, false, false);
+}
+
+/* ----------------
+ *		heap_rescan_set_params	- restart a relation scan after changing params
+ *
+ * This call allows changing the buffer strategy, syncscan, and pagemode
+ * options before starting a fresh scan.  Note that although the actual use
+ * of syncscan might change (effectively, enabling or disabling reporting),
+ * the previously selected startblock will be kept.
+ * ----------------
+ */
+void
+table_rescan_set_params(HeapScanDesc scan, ScanKey key,
+						  bool allow_strat, bool allow_sync, bool allow_pagemode)
+{
+	scan->rs_rd->rd_tableamroutine->scan_rescan(scan, key, true,
+											 allow_strat, allow_sync, (allow_pagemode && IsMVCCSnapshot(scan->rs_snapshot)));
+}
+
+/* ----------------
+ *		heap_endscan	- end relation scan
+ *
+ *		See how to integrate with index scans.
+ *		Check handling if reldesc caching.
+ * ----------------
+ */
+void
+table_endscan(HeapScanDesc scan)
+{
+	scan->rs_rd->rd_tableamroutine->scan_end(scan);
+}
+
+
+/* ----------------
+ *		heap_update_snapshot
+ *
+ *		Update snapshot info in heap scan descriptor.
+ * ----------------
+ */
+void
+table_scan_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
+{
+	scan->rs_rd->rd_tableamroutine->scan_update_snapshot(scan, snapshot);
+}
+
+TableTuple
+table_scan_getnext(HeapScanDesc sscan, ScanDirection direction)
+{
+	return sscan->rs_rd->rd_tableamroutine->scan_getnext(sscan, direction);
+}
+
+TupleTableSlot *
+table_scan_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
+{
+	return sscan->rs_rd->rd_tableamroutine->scan_getnextslot(sscan, direction, slot);
+}
+
 /*
  * Insert a tuple from a slot into table AM routine
  */
@@ -87,6 +257,59 @@ table_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 												  lockmode, IndexFunc, recheckIndexes);
 }
 
+/*
+ *	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
+ * of a HOT chain), and buffer is the buffer holding this tuple.  We search
+ * for the first chain member satisfying the given snapshot.  If one is
+ * found, we update *tid to reference that tuple's offset number, and
+ * return true.  If no match, return false without modifying *tid.
+ *
+ * heapTuple is a caller-supplied buffer.  When a match is found, we return
+ * the tuple here, in addition to updating *tid.  If no match is found, the
+ * contents of this buffer on return are undefined.
+ *
+ * If all_dead is not NULL, we check non-visible tuples to see if they are
+ * globally dead; *all_dead is set true if all members of the HOT chain
+ * are vacuumable, false if not.
+ *
+ * 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.
+ */
+bool
+table_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
+						  Snapshot snapshot, HeapTuple heapTuple,
+						  bool *all_dead, bool first_call)
+{
+	return relation->rd_tableamroutine->hot_search_buffer(tid, relation, buffer,
+													   snapshot, heapTuple, all_dead, first_call);
+}
+
+/*
+ *	heap_hot_search		- search HOT chain for tuple satisfying snapshot
+ *
+ * This has the same API as heap_hot_search_buffer, except that the caller
+ * does not provide the buffer containing the page, rather we access it
+ * locally.
+ */
+bool
+table_hot_search(ItemPointer tid, Relation relation, Snapshot snapshot,
+				   bool *all_dead)
+{
+	bool		result;
+	Buffer		buffer;
+	HeapTupleData heapTuple;
+
+	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
+	LockBuffer(buffer, BUFFER_LOCK_SHARE);
+	result = relation->rd_tableamroutine->hot_search_buffer(tid, relation, buffer,
+														 snapshot, &heapTuple, all_dead, true);
+	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+	ReleaseBuffer(buffer);
+	return result;
+}
 
 /*
  *	table_multi_insert	- insert multiple tuple into a table
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 80860128fb..a982fc641e 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -18,6 +18,7 @@
 #include <signal.h>
 
 #include "access/htup_details.h"
+#include "access/tableam.h"
 #include "access/xact.h"
 #include "access/xlog_internal.h"
 #include "bootstrap/bootstrap.h"
@@ -586,18 +587,18 @@ boot_openrel(char *relname)
 	{
 		/* We can now load the pg_type data */
 		rel = heap_open(TypeRelationId, NoLock);
-		scan = heap_beginscan_catalog(rel, 0, NULL);
+		scan = table_beginscan_catalog(rel, 0, NULL);
 		i = 0;
-		while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tup = table_scan_getnext(scan, ForwardScanDirection)) != NULL)
 			++i;
-		heap_endscan(scan);
+		table_endscan(scan);
 		app = Typ = ALLOC(struct typmap *, i + 1);
 		while (i-- > 0)
 			*app++ = ALLOC(struct typmap, 1);
 		*app = NULL;
-		scan = heap_beginscan_catalog(rel, 0, NULL);
+		scan = table_beginscan_catalog(rel, 0, NULL);
 		app = Typ;
-		while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tup = table_scan_getnext(scan, ForwardScanDirection)) != NULL)
 		{
 			(*app)->am_oid = HeapTupleGetOid(tup);
 			memcpy((char *) &(*app)->am_typ,
@@ -605,7 +606,7 @@ boot_openrel(char *relname)
 				   sizeof((*app)->am_typ));
 			app++;
 		}
-		heap_endscan(scan);
+		table_endscan(scan);
 		heap_close(rel, NoLock);
 	}
 
@@ -916,25 +917,25 @@ gettype(char *type)
 		}
 		elog(DEBUG4, "external type: %s", type);
 		rel = heap_open(TypeRelationId, NoLock);
-		scan = heap_beginscan_catalog(rel, 0, NULL);
+		scan = table_beginscan_catalog(rel, 0, NULL);
 		i = 0;
-		while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tup = table_scan_getnext(scan, ForwardScanDirection)) != NULL)
 			++i;
-		heap_endscan(scan);
+		table_endscan(scan);
 		app = Typ = ALLOC(struct typmap *, i + 1);
 		while (i-- > 0)
 			*app++ = ALLOC(struct typmap, 1);
 		*app = NULL;
-		scan = heap_beginscan_catalog(rel, 0, NULL);
+		scan = table_beginscan_catalog(rel, 0, NULL);
 		app = Typ;
-		while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tup = table_scan_getnext(scan, ForwardScanDirection)) != NULL)
 		{
 			(*app)->am_oid = HeapTupleGetOid(tup);
 			memmove((char *) &(*app++)->am_typ,
 					(char *) GETSTRUCT(tup),
 					sizeof((*app)->am_typ));
 		}
-		heap_endscan(scan);
+		table_endscan(scan);
 		heap_close(rel, NoLock);
 		return gettype(type);
 	}
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index fac80612b8..48922f4809 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -20,6 +20,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/tableam.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "catalog/binary_upgrade.h"
@@ -847,14 +848,14 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 									InvalidOid);
 
 					rel = heap_open(ProcedureRelationId, AccessShareLock);
-					scan = heap_beginscan_catalog(rel, keycount, key);
+					scan = table_beginscan_catalog(rel, keycount, key);
 
-					while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+					while ((tuple = table_scan_getnext(scan, ForwardScanDirection)) != NULL)
 					{
 						objects = lappend_oid(objects, HeapTupleGetOid(tuple));
 					}
 
-					heap_endscan(scan);
+					table_endscan(scan);
 					heap_close(rel, AccessShareLock);
 				}
 				break;
@@ -892,14 +893,14 @@ getRelationsInNamespace(Oid namespaceId, char relkind)
 				CharGetDatum(relkind));
 
 	rel = heap_open(RelationRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(rel, 2, key);
+	scan = table_beginscan_catalog(rel, 2, key);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = table_scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		relations = lappend_oid(relations, HeapTupleGetOid(tuple));
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 	heap_close(rel, AccessShareLock);
 
 	return relations;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 10c7207314..bae262dcab 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -26,6 +26,7 @@
 #include "access/amapi.h"
 #include "access/multixact.h"
 #include "access/relscan.h"
+#include "access/tableam.h"
 #include "access/sysattr.h"
 #include "access/transam.h"
 #include "access/visibilitymap.h"
@@ -1907,10 +1908,10 @@ index_update_stats(Relation rel,
 					BTEqualStrategyNumber, F_OIDEQ,
 					ObjectIdGetDatum(relid));
 
-		pg_class_scan = heap_beginscan_catalog(pg_class, 1, key);
-		tuple = heap_getnext(pg_class_scan, ForwardScanDirection);
+		pg_class_scan = table_beginscan_catalog(pg_class, 1, key);
+		tuple = table_scan_getnext(pg_class_scan, ForwardScanDirection);
 		tuple = heap_copytuple(tuple);
-		heap_endscan(pg_class_scan);
+		table_endscan(pg_class_scan);
 	}
 	else
 	{
@@ -2282,16 +2283,16 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	}
 
 	method = heapRelation->rd_tableamroutine;
-	scan = heap_beginscan_strat(heapRelation,	/* relation */
-								snapshot,	/* snapshot */
-								0,	/* number of keys */
-								NULL,	/* scan key */
-								true,	/* buffer access strategy OK */
-								allow_sync);	/* syncscan OK? */
+	scan = table_beginscan_strat(heapRelation,	/* relation */
+								   snapshot,	/* snapshot */
+								   0,	/* number of keys */
+								   NULL,	/* scan key */
+								   true,	/* buffer access strategy OK */
+								   allow_sync); /* syncscan OK? */
 
 	/* set our scan endpoints */
 	if (!allow_sync)
-		heap_setscanlimits(scan, start_blockno, numblocks);
+		table_setscanlimits(scan, start_blockno, numblocks);
 	else
 	{
 		/* syncscan can only be requested on whole relation */
@@ -2304,7 +2305,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	/*
 	 * Scan all tuples in the base relation.
 	 */
-	while ((heapTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((heapTuple = table_scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		bool		tupleIsAlive;
 
@@ -2616,7 +2617,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 		}
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 
 	/* we can now forget our snapshot, if set */
 	if (IsBootstrapProcessingMode() || indexInfo->ii_Concurrent)
@@ -2687,14 +2688,14 @@ IndexCheckExclusion(Relation heapRelation,
 	 * Scan all live tuples in the base relation.
 	 */
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	scan = heap_beginscan_strat(heapRelation,	/* relation */
-								snapshot,	/* snapshot */
-								0,	/* number of keys */
-								NULL,	/* scan key */
-								true,	/* buffer access strategy OK */
-								true);	/* syncscan OK */
-
-	while ((heapTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	scan = table_beginscan_strat(heapRelation,	/* relation */
+								   snapshot,	/* snapshot */
+								   0,	/* number of keys */
+								   NULL,	/* scan key */
+								   true,	/* buffer access strategy OK */
+								   true);	/* syncscan OK */
+
+	while ((heapTuple = table_scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		CHECK_FOR_INTERRUPTS();
 
@@ -2730,7 +2731,7 @@ IndexCheckExclusion(Relation heapRelation,
 								   estate, true);
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 	UnregisterSnapshot(snapshot);
 
 	ExecDropSingleTupleTableSlot(slot);
@@ -3007,17 +3008,17 @@ validate_index_heapscan(Relation heapRelation,
 	 * here, because it's critical that we read from block zero forward to
 	 * match the sorted TIDs.
 	 */
-	scan = heap_beginscan_strat(heapRelation,	/* relation */
-								snapshot,	/* snapshot */
-								0,	/* number of keys */
-								NULL,	/* scan key */
-								true,	/* buffer access strategy OK */
-								false); /* syncscan not OK */
+	scan = table_beginscan_strat(heapRelation,	/* relation */
+								   snapshot,	/* snapshot */
+								   0,	/* number of keys */
+								   NULL,	/* scan key */
+								   true,	/* buffer access strategy OK */
+								   false);	/* syncscan not OK */
 
 	/*
 	 * Scan all tuples matching the snapshot.
 	 */
-	while ((heapTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((heapTuple = table_scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		ItemPointer heapcursor = &heapTuple->t_self;
 		ItemPointerData rootTuple;
@@ -3174,7 +3175,7 @@ validate_index_heapscan(Relation heapRelation,
 		}
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 
 	ExecDropSingleTupleTableSlot(slot);
 
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 8adc4ee977..5dce6d0041 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -19,6 +19,7 @@
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/nbtree.h"
+#include "access/tableam.h"
 #include "access/sysattr.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
@@ -1324,7 +1325,7 @@ check_default_allows_bound(Relation parent, Relation default_rel,
 
 		econtext = GetPerTupleExprContext(estate);
 		snapshot = RegisterSnapshot(GetLatestSnapshot());
-		scan = heap_beginscan(part_rel, snapshot, 0, NULL);
+		scan = table_beginscan(part_rel, snapshot, 0, NULL);
 		tupslot = MakeSingleTupleTableSlot(tupdesc);
 
 		/*
@@ -1333,7 +1334,7 @@ check_default_allows_bound(Relation parent, Relation default_rel,
 		 */
 		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
 
-		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tuple = table_scan_getnext(scan, ForwardScanDirection)) != NULL)
 		{
 			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
 			econtext->ecxt_scantuple = tupslot;
@@ -1349,7 +1350,7 @@ check_default_allows_bound(Relation parent, Relation default_rel,
 		}
 
 		MemoryContextSwitchTo(oldCxt);
-		heap_endscan(scan);
+		table_endscan(scan);
 		UnregisterSnapshot(snapshot);
 		ExecDropSingleTupleTableSlot(tupslot);
 		FreeExecutorState(estate);
diff --git a/src/backend/catalog/pg_conversion.c b/src/backend/catalog/pg_conversion.c
index 76fcd8fd9c..f2b6a75e1b 100644
--- a/src/backend/catalog/pg_conversion.c
+++ b/src/backend/catalog/pg_conversion.c
@@ -16,6 +16,7 @@
 
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/tableam.h"
 #include "access/sysattr.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
@@ -161,14 +162,14 @@ RemoveConversionById(Oid conversionOid)
 	/* open pg_conversion */
 	rel = heap_open(ConversionRelationId, RowExclusiveLock);
 
-	scan = heap_beginscan_catalog(rel, 1, &scanKeyData);
+	scan = table_beginscan_catalog(rel, 1, &scanKeyData);
 
 	/* search for the target tuple */
-	if (HeapTupleIsValid(tuple = heap_getnext(scan, ForwardScanDirection)))
+	if (HeapTupleIsValid(tuple = table_scan_getnext(scan, ForwardScanDirection)))
 		CatalogTupleDelete(rel, &tuple->t_self);
 	else
 		elog(ERROR, "could not find tuple for conversion %u", conversionOid);
-	heap_endscan(scan);
+	table_endscan(scan);
 	heap_close(rel, RowExclusiveLock);
 }
 
diff --git a/src/backend/catalog/pg_db_role_setting.c b/src/backend/catalog/pg_db_role_setting.c
index e123691923..7450bf0278 100644
--- a/src/backend/catalog/pg_db_role_setting.c
+++ b/src/backend/catalog/pg_db_role_setting.c
@@ -13,6 +13,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/tableam.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_db_role_setting.h"
@@ -196,12 +197,12 @@ DropSetting(Oid databaseid, Oid roleid)
 		numkeys++;
 	}
 
-	scan = heap_beginscan_catalog(relsetting, numkeys, keys);
-	while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+	scan = table_beginscan_catalog(relsetting, numkeys, keys);
+	while (HeapTupleIsValid(tup = table_scan_getnext(scan, ForwardScanDirection)))
 	{
 		CatalogTupleDelete(relsetting, &tup->t_self);
 	}
-	heap_endscan(scan);
+	table_endscan(scan);
 
 	heap_close(relsetting, RowExclusiveLock);
 }
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index b4a5f48b4e..8277d19ec5 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -21,6 +21,7 @@
 #include "access/hash.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/tableam.h"
 #include "access/xact.h"
 
 #include "catalog/catalog.h"
@@ -324,9 +325,9 @@ GetAllTablesPublicationRelations(void)
 				BTEqualStrategyNumber, F_CHAREQ,
 				CharGetDatum(RELKIND_RELATION));
 
-	scan = heap_beginscan_catalog(classRel, 1, key);
+	scan = table_beginscan_catalog(classRel, 1, key);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = table_scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Oid			relid = HeapTupleGetOid(tuple);
 		Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
@@ -335,7 +336,7 @@ GetAllTablesPublicationRelations(void)
 			result = lappend_oid(result, relid);
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 	heap_close(classRel, AccessShareLock);
 
 	return result;
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index 8e16d3b7bc..6cab833509 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -19,6 +19,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/tableam.h"
 #include "access/xact.h"
 
 #include "catalog/indexing.h"
@@ -402,12 +403,12 @@ RemoveSubscriptionRel(Oid subid, Oid relid)
 	}
 
 	/* Do the search and delete what we found. */
-	scan = heap_beginscan_catalog(rel, nkeys, skey);
-	while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+	scan = table_beginscan_catalog(rel, nkeys, skey);
+	while (HeapTupleIsValid(tup = table_scan_getnext(scan, ForwardScanDirection)))
 	{
 		CatalogTupleDelete(rel, &tup->t_self);
 	}
-	heap_endscan(scan);
+	table_endscan(scan);
 
 	heap_close(rel, RowExclusiveLock);
 }
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 45608e7dfc..eda0410282 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -913,7 +913,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	}
 	else
 	{
-		heapScan = heap_beginscan(OldHeap, SnapshotAny, 0, (ScanKey) NULL);
+		heapScan = table_beginscan(OldHeap, SnapshotAny, 0, (ScanKey) NULL);
 		indexScan = NULL;
 	}
 
@@ -963,7 +963,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 		}
 		else
 		{
-			tuple = heap_getnext(heapScan, ForwardScanDirection);
+			tuple = table_scan_getnext(heapScan, ForwardScanDirection);
 			if (tuple == NULL)
 				break;
 
@@ -1049,7 +1049,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	if (indexScan != NULL)
 		index_endscan(indexScan);
 	if (heapScan != NULL)
-		heap_endscan(heapScan);
+		table_endscan(heapScan);
 
 	/*
 	 * In scan-and-sort mode, complete the sort, then read out all live tuples
@@ -1686,8 +1686,8 @@ get_tables_to_cluster(MemoryContext cluster_context)
 				Anum_pg_index_indisclustered,
 				BTEqualStrategyNumber, F_BOOLEQ,
 				BoolGetDatum(true));
-	scan = heap_beginscan_catalog(indRelation, 1, &entry);
-	while ((indexTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	scan = table_beginscan_catalog(indRelation, 1, &entry);
+	while ((indexTuple = table_scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		index = (Form_pg_index) GETSTRUCT(indexTuple);
 
@@ -1707,7 +1707,7 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 		MemoryContextSwitchTo(old_context);
 	}
-	heap_endscan(scan);
+	table_endscan(scan);
 
 	relation_close(indRelation, AccessShareLock);
 
diff --git a/src/backend/commands/constraint.c b/src/backend/commands/constraint.c
index 90f19ad3dd..21c3b38969 100644
--- a/src/backend/commands/constraint.c
+++ b/src/backend/commands/constraint.c
@@ -13,6 +13,7 @@
  */
 #include "postgres.h"
 
+#include "access/tableam.h"
 #include "catalog/index.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
@@ -102,7 +103,7 @@ unique_key_recheck(PG_FUNCTION_ARGS)
 	 * removed.
 	 */
 	tmptid = new_row->t_self;
-	if (!heap_hot_search(&tmptid, trigdata->tg_relation, SnapshotSelf, NULL))
+	if (!table_hot_search(&tmptid, trigdata->tg_relation, SnapshotSelf, NULL))
 	{
 		/*
 		 * 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 245766e7cb..a946dcef4b 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2035,10 +2035,10 @@ CopyTo(CopyState cstate)
 		values = (Datum *) palloc(num_phys_attrs * sizeof(Datum));
 		nulls = (bool *) palloc(num_phys_attrs * sizeof(bool));
 
-		scandesc = heap_beginscan(cstate->rel, GetActiveSnapshot(), 0, NULL);
+		scandesc = table_beginscan(cstate->rel, GetActiveSnapshot(), 0, NULL);
 
 		processed = 0;
-		while ((tuple = heap_getnext(scandesc, ForwardScanDirection)) != NULL)
+		while ((tuple = table_scan_getnext(scandesc, ForwardScanDirection)) != NULL)
 		{
 			CHECK_FOR_INTERRUPTS();
 
@@ -2050,7 +2050,7 @@ CopyTo(CopyState cstate)
 			processed++;
 		}
 
-		heap_endscan(scandesc);
+		table_endscan(scandesc);
 
 		pfree(values);
 		pfree(nulls);
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 0b111fc5cf..0667516ff1 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -26,6 +26,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/tableam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -590,8 +591,8 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 		 * each one to the new database.
 		 */
 		rel = heap_open(TableSpaceRelationId, AccessShareLock);
-		scan = heap_beginscan_catalog(rel, 0, NULL);
-		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		scan = table_beginscan_catalog(rel, 0, NULL);
+		while ((tuple = table_scan_getnext(scan, ForwardScanDirection)) != NULL)
 		{
 			Oid			srctablespace = HeapTupleGetOid(tuple);
 			Oid			dsttablespace;
@@ -643,7 +644,7 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 								  XLOG_DBASE_CREATE | XLR_SPECIAL_REL_UPDATE);
 			}
 		}
-		heap_endscan(scan);
+		table_endscan(scan);
 		heap_close(rel, AccessShareLock);
 
 		/*
@@ -1875,8 +1876,8 @@ remove_dbtablespaces(Oid db_id)
 	HeapTuple	tuple;
 
 	rel = heap_open(TableSpaceRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(rel, 0, NULL);
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	scan = table_beginscan_catalog(rel, 0, NULL);
+	while ((tuple = table_scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Oid			dsttablespace = HeapTupleGetOid(tuple);
 		char	   *dstpath;
@@ -1917,7 +1918,7 @@ remove_dbtablespaces(Oid db_id)
 		pfree(dstpath);
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 	heap_close(rel, AccessShareLock);
 }
 
@@ -1942,8 +1943,8 @@ check_db_file_conflict(Oid db_id)
 	HeapTuple	tuple;
 
 	rel = heap_open(TableSpaceRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(rel, 0, NULL);
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	scan = table_beginscan_catalog(rel, 0, NULL);
+	while ((tuple = table_scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Oid			dsttablespace = HeapTupleGetOid(tuple);
 		char	   *dstpath;
@@ -1966,7 +1967,7 @@ check_db_file_conflict(Oid db_id)
 		pfree(dstpath);
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 	heap_close(rel, AccessShareLock);
 
 	return result;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9e6ba92008..e3c8b9166c 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -18,6 +18,7 @@
 #include "access/amapi.h"
 #include "access/htup_details.h"
 #include "access/reloptions.h"
+#include "access/tableam.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
@@ -1967,8 +1968,8 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 	 * rels will be processed indirectly by reindex_relation).
 	 */
 	relationRelation = heap_open(RelationRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(relationRelation, num_keys, scan_keys);
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	scan = table_beginscan_catalog(relationRelation, num_keys, scan_keys);
+	while ((tuple = table_scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Form_pg_class classtuple = (Form_pg_class) GETSTRUCT(tuple);
 		Oid			relid = HeapTupleGetOid(tuple);
@@ -2008,7 +2009,7 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 
 		MemoryContextSwitchTo(old);
 	}
-	heap_endscan(scan);
+	table_endscan(scan);
 	heap_close(relationRelation, AccessShareLock);
 
 	/* Now reindex each rel in a separate transaction */
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 32eb9d9624..fa321365a8 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4550,7 +4550,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		 * checking all the constraints.
 		 */
 		snapshot = RegisterSnapshot(GetLatestSnapshot());
-		scan = heap_beginscan(oldrel, snapshot, 0, NULL);
+		scan = table_beginscan(oldrel, snapshot, 0, NULL);
 
 		/*
 		 * Switch to per-tuple memory context and reset it for each tuple
@@ -4558,7 +4558,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		 */
 		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
 
-		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tuple = table_scan_getnext(scan, ForwardScanDirection)) != NULL)
 		{
 			if (tab->rewrite > 0)
 			{
@@ -4672,7 +4672,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 
 		MemoryContextSwitchTo(oldCxt);
-		heap_endscan(scan);
+		table_endscan(scan);
 		UnregisterSnapshot(snapshot);
 
 		ExecDropSingleTupleTableSlot(oldslot);
@@ -5075,9 +5075,9 @@ find_typed_table_dependencies(Oid typeOid, const char *typeName, DropBehavior be
 				BTEqualStrategyNumber, F_OIDEQ,
 				ObjectIdGetDatum(typeOid));
 
-	scan = heap_beginscan_catalog(classRel, 1, key);
+	scan = table_beginscan_catalog(classRel, 1, key);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = table_scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		if (behavior == DROP_RESTRICT)
 			ereport(ERROR,
@@ -5089,7 +5089,7 @@ find_typed_table_dependencies(Oid typeOid, const char *typeName, DropBehavior be
 			result = lappend_oid(result, HeapTupleGetOid(tuple));
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 	heap_close(classRel, AccessShareLock);
 
 	return result;
@@ -8199,7 +8199,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	econtext->ecxt_scantuple = slot;
 
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	scan = heap_beginscan(rel, snapshot, 0, NULL);
+	scan = table_beginscan(rel, snapshot, 0, NULL);
 
 	/*
 	 * Switch to per-tuple memory context and reset it for each tuple
@@ -8207,7 +8207,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	 */
 	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = table_scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
@@ -8222,7 +8222,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	}
 
 	MemoryContextSwitchTo(oldcxt);
-	heap_endscan(scan);
+	table_endscan(scan);
 	UnregisterSnapshot(snapshot);
 	ExecDropSingleTupleTableSlot(slot);
 	FreeExecutorState(estate);
@@ -8277,9 +8277,9 @@ validateForeignKeyConstraint(char *conname,
 	 * ereport(ERROR) and that's that.
 	 */
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	scan = heap_beginscan(rel, snapshot, 0, NULL);
+	scan = table_beginscan(rel, snapshot, 0, NULL);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = table_scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		FunctionCallInfoData fcinfo;
 		TriggerData trigdata;
@@ -8308,7 +8308,7 @@ validateForeignKeyConstraint(char *conname,
 		RI_FKey_check_ins(&fcinfo);
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 	UnregisterSnapshot(snapshot);
 }
 
@@ -10815,8 +10815,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 				ObjectIdGetDatum(orig_tablespaceoid));
 
 	rel = heap_open(RelationRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(rel, 1, key);
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	scan = table_beginscan_catalog(rel, 1, key);
+	while ((tuple = table_scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Oid			relOid = HeapTupleGetOid(tuple);
 		Form_pg_class relForm;
@@ -10875,7 +10875,7 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 		relations = lappend_oid(relations, relOid);
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 	heap_close(rel, AccessShareLock);
 
 	if (relations == NIL)
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 8cb834c271..128d38758b 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -53,6 +53,7 @@
 #include "access/heapam.h"
 #include "access/reloptions.h"
 #include "access/htup_details.h"
+#include "access/tableam.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "access/xlog.h"
@@ -416,8 +417,8 @@ DropTableSpace(DropTableSpaceStmt *stmt)
 				Anum_pg_tablespace_spcname,
 				BTEqualStrategyNumber, F_NAMEEQ,
 				CStringGetDatum(tablespacename));
-	scandesc = heap_beginscan_catalog(rel, 1, entry);
-	tuple = heap_getnext(scandesc, ForwardScanDirection);
+	scandesc = table_beginscan_catalog(rel, 1, entry);
+	tuple = table_scan_getnext(scandesc, ForwardScanDirection);
 
 	if (!HeapTupleIsValid(tuple))
 	{
@@ -434,7 +435,7 @@ DropTableSpace(DropTableSpaceStmt *stmt)
 					(errmsg("tablespace \"%s\" does not exist, skipping",
 							tablespacename)));
 			/* XXX I assume I need one or both of these next two calls */
-			heap_endscan(scandesc);
+			table_endscan(scandesc);
 			heap_close(rel, NoLock);
 		}
 		return;
@@ -461,7 +462,7 @@ DropTableSpace(DropTableSpaceStmt *stmt)
 	 */
 	CatalogTupleDelete(rel, &tuple->t_self);
 
-	heap_endscan(scandesc);
+	table_endscan(scandesc);
 
 	/*
 	 * Remove any comments or security labels on this tablespace.
@@ -925,8 +926,8 @@ RenameTableSpace(const char *oldname, const char *newname)
 				Anum_pg_tablespace_spcname,
 				BTEqualStrategyNumber, F_NAMEEQ,
 				CStringGetDatum(oldname));
-	scan = heap_beginscan_catalog(rel, 1, entry);
-	tup = heap_getnext(scan, ForwardScanDirection);
+	scan = table_beginscan_catalog(rel, 1, entry);
+	tup = table_scan_getnext(scan, ForwardScanDirection);
 	if (!HeapTupleIsValid(tup))
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
@@ -937,7 +938,7 @@ RenameTableSpace(const char *oldname, const char *newname)
 	newtuple = heap_copytuple(tup);
 	newform = (Form_pg_tablespace) GETSTRUCT(newtuple);
 
-	heap_endscan(scan);
+	table_endscan(scan);
 
 	/* Must be owner */
 	if (!pg_tablespace_ownercheck(HeapTupleGetOid(newtuple), GetUserId()))
@@ -955,15 +956,15 @@ RenameTableSpace(const char *oldname, const char *newname)
 				Anum_pg_tablespace_spcname,
 				BTEqualStrategyNumber, F_NAMEEQ,
 				CStringGetDatum(newname));
-	scan = heap_beginscan_catalog(rel, 1, entry);
-	tup = heap_getnext(scan, ForwardScanDirection);
+	scan = table_beginscan_catalog(rel, 1, entry);
+	tup = table_scan_getnext(scan, ForwardScanDirection);
 	if (HeapTupleIsValid(tup))
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("tablespace \"%s\" already exists",
 						newname)));
 
-	heap_endscan(scan);
+	table_endscan(scan);
 
 	/* OK, update the entry */
 	namestrcpy(&(newform->spcname), newname);
@@ -1005,8 +1006,8 @@ AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt)
 				Anum_pg_tablespace_spcname,
 				BTEqualStrategyNumber, F_NAMEEQ,
 				CStringGetDatum(stmt->tablespacename));
-	scandesc = heap_beginscan_catalog(rel, 1, entry);
-	tup = heap_getnext(scandesc, ForwardScanDirection);
+	scandesc = table_beginscan_catalog(rel, 1, entry);
+	tup = table_scan_getnext(scandesc, ForwardScanDirection);
 	if (!HeapTupleIsValid(tup))
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
@@ -1047,7 +1048,7 @@ AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt)
 	heap_freetuple(newtuple);
 
 	/* Conclude heap scan. */
-	heap_endscan(scandesc);
+	table_endscan(scandesc);
 	heap_close(rel, NoLock);
 
 	return tablespaceoid;
@@ -1396,8 +1397,8 @@ get_tablespace_oid(const char *tablespacename, bool missing_ok)
 				Anum_pg_tablespace_spcname,
 				BTEqualStrategyNumber, F_NAMEEQ,
 				CStringGetDatum(tablespacename));
-	scandesc = heap_beginscan_catalog(rel, 1, entry);
-	tuple = heap_getnext(scandesc, ForwardScanDirection);
+	scandesc = table_beginscan_catalog(rel, 1, entry);
+	tuple = table_scan_getnext(scandesc, ForwardScanDirection);
 
 	/* We assume that there can be at most one matching tuple */
 	if (HeapTupleIsValid(tuple))
@@ -1405,7 +1406,7 @@ get_tablespace_oid(const char *tablespacename, bool missing_ok)
 	else
 		result = InvalidOid;
 
-	heap_endscan(scandesc);
+	table_endscan(scandesc);
 	heap_close(rel, AccessShareLock);
 
 	if (!OidIsValid(result) && !missing_ok)
@@ -1442,8 +1443,8 @@ get_tablespace_name(Oid spc_oid)
 				ObjectIdAttributeNumber,
 				BTEqualStrategyNumber, F_OIDEQ,
 				ObjectIdGetDatum(spc_oid));
-	scandesc = heap_beginscan_catalog(rel, 1, entry);
-	tuple = heap_getnext(scandesc, ForwardScanDirection);
+	scandesc = table_beginscan_catalog(rel, 1, entry);
+	tuple = table_scan_getnext(scandesc, ForwardScanDirection);
 
 	/* We assume that there can be at most one matching tuple */
 	if (HeapTupleIsValid(tuple))
@@ -1451,7 +1452,7 @@ get_tablespace_name(Oid spc_oid)
 	else
 		result = NULL;
 
-	heap_endscan(scandesc);
+	table_endscan(scandesc);
 	heap_close(rel, AccessShareLock);
 
 	return result;
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index a40b3cf752..4a0db17815 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -32,6 +32,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/binary_upgrade.h"
 #include "catalog/catalog.h"
@@ -2387,8 +2388,8 @@ AlterDomainNotNull(List *names, bool notNull)
 
 			/* Scan all tuples in this relation */
 			snapshot = RegisterSnapshot(GetLatestSnapshot());
-			scan = heap_beginscan(testrel, snapshot, 0, NULL);
-			while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+			scan = table_beginscan(testrel, snapshot, 0, NULL);
+			while ((tuple = table_scan_getnext(scan, ForwardScanDirection)) != NULL)
 			{
 				int			i;
 
@@ -2417,7 +2418,7 @@ AlterDomainNotNull(List *names, bool notNull)
 					}
 				}
 			}
-			heap_endscan(scan);
+			table_endscan(scan);
 			UnregisterSnapshot(snapshot);
 
 			/* Close each rel after processing, but keep lock */
@@ -2783,8 +2784,8 @@ validateDomainConstraint(Oid domainoid, char *ccbin)
 
 		/* Scan all tuples in this relation */
 		snapshot = RegisterSnapshot(GetLatestSnapshot());
-		scan = heap_beginscan(testrel, snapshot, 0, NULL);
-		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		scan = table_beginscan(testrel, snapshot, 0, NULL);
+		while ((tuple = table_scan_getnext(scan, ForwardScanDirection)) != NULL)
 		{
 			int			i;
 
@@ -2827,7 +2828,7 @@ validateDomainConstraint(Oid domainoid, char *ccbin)
 
 			ResetExprContext(econtext);
 		}
-		heap_endscan(scan);
+		table_endscan(scan);
 		UnregisterSnapshot(snapshot);
 
 		/* Hold relation lock till commit (XXX bad for concurrency) */
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7aca69a0ba..24a2d258f4 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -28,6 +28,7 @@
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/multixact.h"
+#include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
@@ -533,9 +534,9 @@ get_all_vacuum_rels(void)
 
 	pgclass = heap_open(RelationRelationId, AccessShareLock);
 
-	scan = heap_beginscan_catalog(pgclass, 0, NULL);
+	scan = table_beginscan_catalog(pgclass, 0, NULL);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = table_scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
 		MemoryContext oldcontext;
@@ -562,7 +563,7 @@ get_all_vacuum_rels(void)
 		MemoryContextSwitchTo(oldcontext);
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 	heap_close(pgclass, AccessShareLock);
 
 	return vacrels;
@@ -1214,9 +1215,9 @@ vac_truncate_clog(TransactionId frozenXID,
 	 */
 	relation = heap_open(DatabaseRelationId, AccessShareLock);
 
-	scan = heap_beginscan_catalog(relation, 0, NULL);
+	scan = table_beginscan_catalog(relation, 0, NULL);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = table_scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		volatile FormData_pg_database *dbform = (Form_pg_database) GETSTRUCT(tuple);
 		TransactionId datfrozenxid = dbform->datfrozenxid;
@@ -1253,7 +1254,7 @@ vac_truncate_clog(TransactionId frozenXID,
 		}
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 
 	heap_close(relation, AccessShareLock);
 
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index 9e78421978..f4e35b5289 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -544,7 +544,7 @@ static bool
 IndexSupportsBackwardScan(Oid indexid)
 {
 	bool		result;
-	HeapTuple	ht_idxrel;
+	TableTuple ht_idxrel;
 	Form_pg_class idxrelrec;
 	IndexAmRoutine *amroutine;
 
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 1038957c59..95bfa97502 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -650,7 +650,7 @@ check_exclusion_or_unique_constraint(Relation heap, Relation index,
 	Oid		   *index_collations = index->rd_indcollation;
 	int			index_natts = index->rd_index->indnatts;
 	IndexScanDesc index_scan;
-	HeapTuple	tup;
+	TableTuple tup;
 	ScanKeyData scankeys[INDEX_MAX_KEYS];
 	SnapshotData DirtySnapshot;
 	int			i;
@@ -732,12 +732,13 @@ retry:
 		bool		existing_isnull[INDEX_MAX_KEYS];
 		char	   *error_new;
 		char	   *error_existing;
+		tuple_data	t_data = table_tuple_get_data(heap, tup, TID);
 
 		/*
 		 * Ignore the entry for the tuple we're trying to check.
 		 */
 		if (ItemPointerIsValid(tupleid) &&
-			ItemPointerEquals(tupleid, &tup->t_self))
+			ItemPointerEquals(tupleid, &(t_data.tid)))
 		{
 			if (found_self)		/* should not happen */
 				elog(ERROR, "found self tuple multiple times in index \"%s\"",
@@ -785,7 +786,8 @@ retry:
 			  DirtySnapshot.speculativeToken &&
 			  TransactionIdPrecedes(GetCurrentTransactionId(), xwait))))
 		{
-			ctid_wait = tup->t_data->t_ctid;
+			t_data = table_tuple_get_data(heap, tup, CTID);
+			ctid_wait = t_data.tid;
 			reason_wait = indexInfo->ii_ExclusionOps ?
 				XLTW_RecheckExclusionConstr : XLTW_InsertIndex;
 			index_endscan(index_scan);
@@ -805,7 +807,10 @@ retry:
 		{
 			conflict = true;
 			if (conflictTid)
-				*conflictTid = tup->t_self;
+			{
+				t_data = table_tuple_get_data(heap, tup, TID);
+				*conflictTid = t_data.tid;
+			}
 			break;
 		}
 
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index cbd1e06a6a..6561f52792 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -118,7 +118,7 @@ RelationFindReplTupleByIndex(Relation rel, Oid idxoid,
 							 TupleTableSlot *searchslot,
 							 TupleTableSlot *outslot)
 {
-	HeapTuple	scantuple;
+	TableTuple scantuple;
 	ScanKeyData skey[INDEX_MAX_KEYS];
 	IndexScanDesc scan;
 	SnapshotData snap;
@@ -228,8 +228,7 @@ RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 						 TupleTableSlot *searchslot, TupleTableSlot *outslot)
 {
 	TupleTableSlot *scanslot;
-	HeapTuple	scantuple;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	SnapshotData snap;
 	TransactionId xwait;
 	bool		found;
@@ -239,19 +238,19 @@ RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 
 	/* Start a heap scan. */
 	InitDirtySnapshot(snap);
-	scan = heap_beginscan(rel, &snap, 0, NULL);
+	scan = table_beginscan(rel, &snap, 0, NULL);
 
 	scanslot = MakeSingleTupleTableSlot(desc);
 
 retry:
 	found = false;
 
-	heap_rescan(scan, NULL);
+	table_rescan(scan, NULL);
 
 	/* Try to find the tuple */
-	while ((scantuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((scanslot = table_scan_getnextslot(scan, ForwardScanDirection, scanslot))
+		   && !TupIsNull(scanslot))
 	{
-		ExecStoreTuple(scantuple, scanslot, InvalidBuffer, false);
 		if (!ExecSlotCompare(scanslot, searchslot))
 			continue;
 
@@ -313,7 +312,7 @@ retry:
 		}
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 	ExecDropSingleTupleTableSlot(scanslot);
 
 	return found;
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index b0f818f07f..eb098e684c 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -682,7 +682,7 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 Datum
 ExecFetchSlotTupleDatum(TupleTableSlot *slot)
 {
-	HeapTuple	tup;
+	TableTuple tup;
 	TupleDesc	tupdesc;
 
 	/* Fetch slot's contents in regular-physical-tuple form */
@@ -766,7 +766,7 @@ ExecHeapifySlot(TupleTableSlot *slot)
 TupleTableSlot *
 ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
 {
-	HeapTuple	newTuple;
+	TableTuple newTuple;
 	MemoryContext oldContext;
 
 	/*
@@ -1086,7 +1086,7 @@ TupleDescGetAttInMetadata(TupleDesc tupdesc)
  * values is an array of C strings, one for each attribute of the return tuple.
  * A NULL string pointer indicates we want to create a NULL field.
  */
-HeapTuple
+TableTuple
 BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values)
 {
 	TupleDesc	tupdesc = attinmeta->tupdesc;
@@ -1094,7 +1094,7 @@ BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values)
 	Datum	   *dvalues;
 	bool	   *nulls;
 	int			i;
-	HeapTuple	tuple;
+	TableTuple tuple;
 
 	dvalues = (Datum *) palloc(natts * sizeof(Datum));
 	nulls = (bool *) palloc(natts * sizeof(bool));
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 7e249f575f..d026934445 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -181,7 +181,7 @@ static void sqlfunction_destroy(DestReceiver *self);
  * polymorphic arguments.
  */
 SQLFunctionParseInfoPtr
-prepare_sql_fn_parse_info(HeapTuple procedureTuple,
+prepare_sql_fn_parse_info(TableTuple procedureTuple,
 						  Node *call_expr,
 						  Oid inputCollation)
 {
@@ -598,7 +598,7 @@ init_sql_fcache(FmgrInfo *finfo, Oid collation, bool lazyEvalOK)
 	MemoryContext fcontext;
 	MemoryContext oldcontext;
 	Oid			rettype;
-	HeapTuple	procedureTuple;
+	TableTuple procedureTuple;
 	Form_pg_proc procedureStruct;
 	SQLFunctionCachePtr fcache;
 	List	   *raw_parsetree_list;
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 46ee880415..9425f2a0c5 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -3139,7 +3139,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 		Oid			inputTypes[FUNC_MAX_ARGS];
 		int			numArguments;
 		int			numDirectArgs;
-		HeapTuple	aggTuple;
+		TableTuple aggTuple;
 		Form_pg_aggregate aggform;
 		AclResult	aclresult;
 		Oid			transfn_oid,
@@ -3264,7 +3264,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 
 		/* Check that aggregate owner has permission to call component fns */
 		{
-			HeapTuple	procTuple;
+			TableTuple procTuple;
 			Oid			aggOwner;
 
 			procTuple = SearchSysCache1(PROCOID,
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index 1220ae4769..5187e319ca 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -38,6 +38,7 @@
 #include <math.h>
 
 #include "access/relscan.h"
+#include "access/tableam.h"
 #include "access/transam.h"
 #include "access/visibilitymap.h"
 #include "executor/execdebug.h"
@@ -433,8 +434,8 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 			HeapTupleData heapTuple;
 
 			ItemPointerSet(&tid, page, offnum);
-			if (heap_hot_search_buffer(&tid, scan->rs_rd, buffer, snapshot,
-									   &heapTuple, NULL, true))
+			if (table_hot_search_buffer(&tid, scan->rs_rd, buffer, snapshot,
+										  &heapTuple, NULL, true))
 				scan->rs_vistuples[ntup++] = ItemPointerGetOffsetNumber(&tid);
 		}
 	}
@@ -747,7 +748,7 @@ ExecReScanBitmapHeapScan(BitmapHeapScanState *node)
 	PlanState  *outerPlan = outerPlanState(node);
 
 	/* rescan to release any page pin */
-	heap_rescan(node->ss.ss_currentScanDesc, NULL);
+	table_rescan(node->ss.ss_currentScanDesc, NULL);
 
 	/* release bitmaps and buffers if any */
 	if (node->tbmiterator)
@@ -837,7 +838,7 @@ ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 	/*
 	 * close heap scan
 	 */
-	heap_endscan(scanDesc);
+	table_endscan(scanDesc);
 
 	/*
 	 * close the heap relation.
@@ -952,10 +953,10 @@ ExecInitBitmapHeapScan(BitmapHeapScan *node, EState *estate, int eflags)
 	 * Even though we aren't going to do a conventional seqscan, it is useful
 	 * to create a HeapScanDesc --- most of the fields in it are usable.
 	 */
-	scanstate->ss.ss_currentScanDesc = heap_beginscan_bm(currentRelation,
-														 estate->es_snapshot,
-														 0,
-														 NULL);
+	scanstate->ss.ss_currentScanDesc = table_beginscan_bm(currentRelation,
+															estate->es_snapshot,
+															0,
+															NULL);
 
 	/*
 	 * get the scan type from the relation descriptor.
@@ -1123,5 +1124,5 @@ ExecBitmapHeapInitializeWorker(BitmapHeapScanState *node,
 	node->pstate = pstate;
 
 	snapshot = RestoreSnapshot(pstate->phs_snapshot_data);
-	heap_update_snapshot(node->ss.ss_currentScanDesc, snapshot);
+	table_scan_update_snapshot(node->ss.ss_currentScanDesc, snapshot);
 }
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index b9cf3037b1..4f2e23e1e7 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -62,9 +62,9 @@ ForeignNext(ForeignScanState *node)
 	 */
 	if (plan->fsSystemCol && !TupIsNull(slot))
 	{
-		HeapTuple	tup = ExecHeapifySlot(slot);
-
-		tup->t_tableOid = RelationGetRelid(node->ss.ss_currentRelation);
+		ExecMaterializeSlot(slot);
+		ExecSlotUpdateTupleTableoid(slot,
+									RelationGetRelid(node->ss.ss_currentRelation));
 	}
 
 	return slot;
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index 89266b5371..64fc55b49e 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -46,7 +46,7 @@
 
 static TupleTableSlot *ExecGather(PlanState *pstate);
 static TupleTableSlot *gather_getnext(GatherState *gatherstate);
-static HeapTuple gather_readnext(GatherState *gatherstate);
+static TableTuple gather_readnext(GatherState *gatherstate);
 static void ExecShutdownGatherWorkers(GatherState *node);
 
 
@@ -254,7 +254,7 @@ gather_getnext(GatherState *gatherstate)
 	PlanState  *outerPlan = outerPlanState(gatherstate);
 	TupleTableSlot *outerTupleSlot;
 	TupleTableSlot *fslot = gatherstate->funnel_slot;
-	HeapTuple	tup;
+	TableTuple tup;
 
 	while (gatherstate->nreaders > 0 || gatherstate->need_to_scan_locally)
 	{
@@ -298,7 +298,7 @@ gather_getnext(GatherState *gatherstate)
 /*
  * Attempt to read a tuple from one of our parallel workers.
  */
-static HeapTuple
+static TableTuple
 gather_readnext(GatherState *gatherstate)
 {
 	int			nvisited = 0;
@@ -306,7 +306,7 @@ gather_readnext(GatherState *gatherstate)
 	for (;;)
 	{
 		TupleQueueReader *reader;
-		HeapTuple	tup;
+		TableTuple tup;
 		bool		readerdone;
 
 		/* Check for async events, particularly messages from workers. */
diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c
index a3e34c6980..409313da36 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -45,7 +45,7 @@
  */
 typedef struct GMReaderTupleBuffer
 {
-	HeapTuple  *tuple;			/* array of length MAX_TUPLE_STORE */
+	TableTuple *tuple;		/* array of length MAX_TUPLE_STORE */
 	int			nTuples;		/* number of tuples currently stored */
 	int			readCounter;	/* index of next tuple to extract */
 	bool		done;			/* true if reader is known exhausted */
@@ -54,8 +54,8 @@ typedef struct GMReaderTupleBuffer
 static TupleTableSlot *ExecGatherMerge(PlanState *pstate);
 static int32 heap_compare_slots(Datum a, Datum b, void *arg);
 static TupleTableSlot *gather_merge_getnext(GatherMergeState *gm_state);
-static HeapTuple gm_readnext_tuple(GatherMergeState *gm_state, int nreader,
-				  bool nowait, bool *done);
+static TableTuple gm_readnext_tuple(GatherMergeState *gm_state, int nreader,
+									  bool nowait, bool *done);
 static void ExecShutdownGatherMergeWorkers(GatherMergeState *node);
 static void gather_merge_setup(GatherMergeState *gm_state);
 static void gather_merge_init(GatherMergeState *gm_state);
@@ -407,7 +407,7 @@ gather_merge_setup(GatherMergeState *gm_state)
 	{
 		/* Allocate the tuple array with length MAX_TUPLE_STORE */
 		gm_state->gm_tuple_buffers[i].tuple =
-			(HeapTuple *) palloc0(sizeof(HeapTuple) * MAX_TUPLE_STORE);
+			(TableTuple *) palloc0(sizeof(TableTuple) * MAX_TUPLE_STORE);
 
 		/* Initialize tuple slot for worker */
 		gm_state->gm_slots[i + 1] = ExecInitExtraTupleSlot(gm_state->ps.state);
@@ -625,7 +625,7 @@ static bool
 gather_merge_readnext(GatherMergeState *gm_state, int reader, bool nowait)
 {
 	GMReaderTupleBuffer *tuple_buffer;
-	HeapTuple	tup;
+	TableTuple tup;
 
 	/*
 	 * If we're being asked to generate a tuple from the leader, then we just
@@ -700,12 +700,12 @@ gather_merge_readnext(GatherMergeState *gm_state, int reader, bool nowait)
 /*
  * Attempt to read a tuple from given worker.
  */
-static HeapTuple
+static TableTuple
 gm_readnext_tuple(GatherMergeState *gm_state, int nreader, bool nowait,
 				  bool *done)
 {
 	TupleQueueReader *reader;
-	HeapTuple	tup;
+	TableTuple tup;
 
 	/* Check for async events, particularly messages from workers. */
 	CHECK_FOR_INTERRUPTS();
diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c
index 9b7f470ee2..6b9ef27568 100644
--- a/src/backend/executor/nodeIndexonlyscan.c
+++ b/src/backend/executor/nodeIndexonlyscan.c
@@ -117,7 +117,7 @@ IndexOnlyNext(IndexOnlyScanState *node)
 	 */
 	while ((tid = index_getnext_tid(scandesc, direction)) != NULL)
 	{
-		HeapTuple	tuple = NULL;
+		TableTuple tuple = NULL;
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -186,9 +186,9 @@ IndexOnlyNext(IndexOnlyScanState *node)
 
 		/*
 		 * Fill the scan tuple slot with data from the index.  This might be
-		 * provided in either HeapTuple or IndexTuple format.  Conceivably an
-		 * index AM might fill both fields, in which case we prefer the heap
-		 * format, since it's probably a bit cheaper to fill a slot from.
+		 * provided in either TableTuple or IndexTuple format.  Conceivably
+		 * an index AM might fill both fields, in which case we prefer the
+		 * heap format, since it's probably a bit cheaper to fill a slot from.
 		 */
 		if (scandesc->xs_hitup)
 		{
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 54fafa5033..ead5dba84b 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -51,7 +51,7 @@
 typedef struct
 {
 	pairingheap_node ph_node;
-	HeapTuple	htup;
+	TableTuple htup;
 	Datum	   *orderbyvals;
 	bool	   *orderbynulls;
 } ReorderTuple;
@@ -65,9 +65,9 @@ static int cmp_orderbyvals(const Datum *adist, const bool *anulls,
 				IndexScanState *node);
 static int reorderqueue_cmp(const pairingheap_node *a,
 				 const pairingheap_node *b, void *arg);
-static void reorderqueue_push(IndexScanState *node, HeapTuple tuple,
+static void reorderqueue_push(IndexScanState *node, TableTuple tuple,
 				  Datum *orderbyvals, bool *orderbynulls);
-static HeapTuple reorderqueue_pop(IndexScanState *node);
+static TableTuple reorderqueue_pop(IndexScanState *node);
 
 
 /* ----------------------------------------------------------------
@@ -84,7 +84,7 @@ IndexNext(IndexScanState *node)
 	ExprContext *econtext;
 	ScanDirection direction;
 	IndexScanDesc scandesc;
-	HeapTuple	tuple;
+	TableTuple tuple;
 	TupleTableSlot *slot;
 
 	/*
@@ -185,7 +185,7 @@ IndexNextWithReorder(IndexScanState *node)
 	EState	   *estate;
 	ExprContext *econtext;
 	IndexScanDesc scandesc;
-	HeapTuple	tuple;
+	TableTuple tuple;
 	TupleTableSlot *slot;
 	ReorderTuple *topmost = NULL;
 	bool		was_exact;
@@ -483,7 +483,7 @@ reorderqueue_cmp(const pairingheap_node *a, const pairingheap_node *b,
  * Helper function to push a tuple to the reorder queue.
  */
 static void
-reorderqueue_push(IndexScanState *node, HeapTuple tuple,
+reorderqueue_push(IndexScanState *node, TableTuple tuple,
 				  Datum *orderbyvals, bool *orderbynulls)
 {
 	IndexScanDesc scandesc = node->iss_ScanDesc;
@@ -516,10 +516,10 @@ reorderqueue_push(IndexScanState *node, HeapTuple tuple,
 /*
  * Helper function to pop the next tuple from the reorder queue.
  */
-static HeapTuple
+static TableTuple
 reorderqueue_pop(IndexScanState *node)
 {
-	HeapTuple	result;
+	TableTuple result;
 	ReorderTuple *topmost;
 	int			i;
 
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index 9750509eae..d54b27196c 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -29,10 +29,12 @@
 static void InitScanRelation(SampleScanState *node, EState *estate, int eflags);
 static TupleTableSlot *SampleNext(SampleScanState *node);
 static void tablesample_init(SampleScanState *scanstate);
-static HeapTuple tablesample_getnext(SampleScanState *scanstate);
-static bool SampleTupleVisible(HeapTuple tuple, OffsetNumber tupoffset,
+static TableTuple tablesample_getnext(SampleScanState *scanstate);
+static bool SampleTupleVisible(TableTuple tuple, OffsetNumber tupoffset,
 				   HeapScanDesc scan);
 
+/* hari */
+
 /* ----------------------------------------------------------------
  *						Scan Support
  * ----------------------------------------------------------------
@@ -47,7 +49,7 @@ static bool SampleTupleVisible(HeapTuple tuple, OffsetNumber tupoffset,
 static TupleTableSlot *
 SampleNext(SampleScanState *node)
 {
-	HeapTuple	tuple;
+	TableTuple tuple;
 	TupleTableSlot *slot;
 
 	/*
@@ -244,7 +246,7 @@ ExecEndSampleScan(SampleScanState *node)
 	 * close heap scan
 	 */
 	if (node->ss.ss_currentScanDesc)
-		heap_endscan(node->ss.ss_currentScanDesc);
+		table_endscan(node->ss.ss_currentScanDesc);
 
 	/*
 	 * close the heap relation.
@@ -349,19 +351,19 @@ tablesample_init(SampleScanState *scanstate)
 	if (scanstate->ss.ss_currentScanDesc == NULL)
 	{
 		scanstate->ss.ss_currentScanDesc =
-			heap_beginscan_sampling(scanstate->ss.ss_currentRelation,
-									scanstate->ss.ps.state->es_snapshot,
-									0, NULL,
-									scanstate->use_bulkread,
-									allow_sync,
-									scanstate->use_pagemode);
+			table_beginscan_sampling(scanstate->ss.ss_currentRelation,
+									   scanstate->ss.ps.state->es_snapshot,
+									   0, NULL,
+									   scanstate->use_bulkread,
+									   allow_sync,
+									   scanstate->use_pagemode);
 	}
 	else
 	{
-		heap_rescan_set_params(scanstate->ss.ss_currentScanDesc, NULL,
-							   scanstate->use_bulkread,
-							   allow_sync,
-							   scanstate->use_pagemode);
+		table_rescan_set_params(scanstate->ss.ss_currentScanDesc, NULL,
+								  scanstate->use_bulkread,
+								  allow_sync,
+								  scanstate->use_pagemode);
 	}
 
 	pfree(params);
@@ -376,7 +378,7 @@ tablesample_init(SampleScanState *scanstate)
  * Note: an awful lot of this is copied-and-pasted from heapam.c.  It would
  * perhaps be better to refactor to share more code.
  */
-static HeapTuple
+static TableTuple
 tablesample_getnext(SampleScanState *scanstate)
 {
 	TsmRoutine *tsm = scanstate->tsmroutine;
@@ -554,7 +556,7 @@ tablesample_getnext(SampleScanState *scanstate)
  * Check visibility of the tuple.
  */
 static bool
-SampleTupleVisible(HeapTuple tuple, OffsetNumber tupoffset, HeapScanDesc scan)
+SampleTupleVisible(TableTuple tuple, OffsetNumber tupoffset, HeapScanDesc scan) //hari
 {
 	if (scan->rs_pageatatime)
 	{
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index 58631378d5..f6903ac962 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -28,6 +28,7 @@
 #include "postgres.h"
 
 #include "access/relscan.h"
+#include "access/tableam.h"
 #include "executor/execdebug.h"
 #include "executor/nodeSeqscan.h"
 #include "utils/rel.h"
@@ -49,8 +50,7 @@ static TupleTableSlot *SeqNext(SeqScanState *node);
 static TupleTableSlot *
 SeqNext(SeqScanState *node)
 {
-	HeapTuple	tuple;
-	HeapScanDesc scandesc;
+	TableScanDesc scandesc;
 	EState	   *estate;
 	ScanDirection direction;
 	TupleTableSlot *slot;
@@ -69,35 +69,16 @@ SeqNext(SeqScanState *node)
 		 * We reach here if the scan is not parallel, or if we're executing a
 		 * scan that was intended to be parallel serially.
 		 */
-		scandesc = heap_beginscan(node->ss.ss_currentRelation,
-								  estate->es_snapshot,
-								  0, NULL);
+		scandesc = table_beginscan(node->ss.ss_currentRelation,
+									 estate->es_snapshot,
+									 0, NULL);
 		node->ss.ss_currentScanDesc = scandesc;
 	}
 
 	/*
 	 * get the next tuple from the table
 	 */
-	tuple = heap_getnext(scandesc, direction);
-
-	/*
-	 * save the tuple and the buffer returned to us by the access methods in
-	 * our scan tuple slot and return the slot.  Note: we pass 'false' because
-	 * tuples returned by heap_getnext() are pointers onto disk pages and were
-	 * not created with palloc() and so should not be pfree()'d.  Note also
-	 * that ExecStoreTuple will increment the refcount of the buffer; the
-	 * refcount will not be dropped until the tuple table slot is cleared.
-	 */
-	if (tuple)
-		ExecStoreTuple(tuple,	/* tuple to store */
-					   slot,	/* slot to store in */
-					   scandesc->rs_cbuf,	/* buffer associated with this
-											 * tuple */
-					   false);	/* don't pfree this pointer */
-	else
-		ExecClearTuple(slot);
-
-	return slot;
+	return table_scan_getnextslot(scandesc, direction, slot);
 }
 
 /*
@@ -225,7 +206,7 @@ void
 ExecEndSeqScan(SeqScanState *node)
 {
 	Relation	relation;
-	HeapScanDesc scanDesc;
+	TableScanDesc scanDesc;
 
 	/*
 	 * get information from node
@@ -248,7 +229,7 @@ ExecEndSeqScan(SeqScanState *node)
 	 * close heap scan
 	 */
 	if (scanDesc != NULL)
-		heap_endscan(scanDesc);
+		table_endscan(scanDesc);
 
 	/*
 	 * close the heap relation.
@@ -270,13 +251,13 @@ ExecEndSeqScan(SeqScanState *node)
 void
 ExecReScanSeqScan(SeqScanState *node)
 {
-	HeapScanDesc scan;
+	TableScanDesc scan;
 
 	scan = node->ss.ss_currentScanDesc;
 
 	if (scan != NULL)
-		heap_rescan(scan,		/* scan desc */
-					NULL);		/* new scan keys */
+		table_rescan(scan,	/* scan desc */
+					   NULL);	/* new scan keys */
 
 	ExecScanReScan((ScanState *) node);
 }
@@ -323,7 +304,7 @@ ExecSeqScanInitializeDSM(SeqScanState *node,
 								 estate->es_snapshot);
 	shm_toc_insert(pcxt->toc, node->ss.ps.plan->plan_node_id, pscan);
 	node->ss.ss_currentScanDesc =
-		heap_beginscan_parallel(node->ss.ss_currentRelation, pscan);
+		table_beginscan_parallel(node->ss.ss_currentRelation, pscan);
 }
 
 /* ----------------------------------------------------------------
@@ -355,5 +336,5 @@ ExecSeqScanInitializeWorker(SeqScanState *node,
 
 	pscan = shm_toc_lookup(pwcxt->toc, node->ss.ps.plan->plan_node_id, false);
 	node->ss.ss_currentScanDesc =
-		heap_beginscan_parallel(node->ss.ss_currentRelation, pscan);
+		table_beginscan_parallel(node->ss.ss_currentRelation, pscan);
 }
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 5492fb3369..b44decfc8e 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -2092,7 +2092,7 @@ initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc,
 {
 	Oid			inputTypes[FUNC_MAX_ARGS];
 	int			numArguments;
-	HeapTuple	aggTuple;
+	TableTuple aggTuple;
 	Form_pg_aggregate aggform;
 	Oid			aggtranstype;
 	AttrNumber	initvalAttNo;
@@ -2175,7 +2175,7 @@ initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc,
 
 	/* Check that aggregate owner has permission to call component fns */
 	{
-		HeapTuple	procTuple;
+		TableTuple procTuple;
 		Oid			aggOwner;
 
 		procTuple = SearchSysCache1(PROCOID,
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 4d9b51b947..830aecb31b 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -633,11 +633,11 @@ SPI_freeplan(SPIPlanPtr plan)
 	return 0;
 }
 
-HeapTuple
-SPI_copytuple(HeapTuple tuple)
+TableTuple
+SPI_copytuple(TableTuple tuple)
 {
 	MemoryContext oldcxt;
-	HeapTuple	ctuple;
+	TableTuple ctuple;
 
 	if (tuple == NULL)
 	{
@@ -661,7 +661,7 @@ SPI_copytuple(HeapTuple tuple)
 }
 
 HeapTupleHeader
-SPI_returntuple(HeapTuple tuple, TupleDesc tupdesc)
+SPI_returntuple(TableTuple tuple, TupleDesc tupdesc)
 {
 	MemoryContext oldcxt;
 	HeapTupleHeader dtup;
@@ -692,7 +692,7 @@ SPI_returntuple(HeapTuple tuple, TupleDesc tupdesc)
 	return dtup;
 }
 
-HeapTuple
+TableTuple
 SPI_modifytuple(Relation rel, HeapTuple tuple, int natts, int *attnum,
 				Datum *Values, const char *Nulls)
 {
@@ -860,7 +860,7 @@ char *
 SPI_gettype(TupleDesc tupdesc, int fnumber)
 {
 	Oid			typoid;
-	HeapTuple	typeTuple;
+	TableTuple typeTuple;
 	char	   *result;
 
 	SPI_result = 0;
@@ -968,7 +968,7 @@ SPI_datumTransfer(Datum value, bool typByVal, int typLen)
 }
 
 void
-SPI_freetuple(HeapTuple tuple)
+SPI_freetuple(TableTuple tuple)
 {
 	/* No longer need to worry which context tuple was in... */
 	heap_freetuple(tuple);
@@ -1689,7 +1689,7 @@ spi_dest_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 
 	/* set up initial allocations */
 	tuptable->alloced = tuptable->free = 128;
-	tuptable->vals = (HeapTuple *) palloc(tuptable->alloced * sizeof(HeapTuple));
+	tuptable->vals = (TableTuple *) palloc(tuptable->alloced * sizeof(TableTuple));
 	tuptable->tupdesc = CreateTupleDescCopy(typeinfo);
 
 	MemoryContextSwitchTo(oldcxt);
@@ -1720,8 +1720,8 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self)
 		/* Double the size of the pointer array */
 		tuptable->free = tuptable->alloced;
 		tuptable->alloced += tuptable->free;
-		tuptable->vals = (HeapTuple *) repalloc_huge(tuptable->vals,
-													 tuptable->alloced * sizeof(HeapTuple));
+		tuptable->vals = (TableTuple *) repalloc_huge(tuptable->vals,
+														tuptable->alloced * sizeof(TableTuple));
 	}
 
 	tuptable->vals[tuptable->alloced - tuptable->free] =
diff --git a/src/backend/executor/tqueue.c b/src/backend/executor/tqueue.c
index 12b9fef894..40ab871227 100644
--- a/src/backend/executor/tqueue.c
+++ b/src/backend/executor/tqueue.c
@@ -168,7 +168,7 @@ DestroyTupleQueueReader(TupleQueueReader *reader)
  * accumulate bytes from a partially-read message, so it's useful to call
  * this with nowait = true even if nothing is returned.
  */
-HeapTuple
+TableTuple
 TupleQueueReaderNext(TupleQueueReader *reader, bool nowait, bool *done)
 {
 	HeapTupleData htup;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 75c2362f46..d697289c0d 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1883,9 +1883,9 @@ get_database_list(void)
 	(void) GetTransactionSnapshot();
 
 	rel = heap_open(DatabaseRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(rel, 0, NULL);
+	scan = table_beginscan_catalog(rel, 0, NULL);
 
-	while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+	while (HeapTupleIsValid(tup = table_scan_getnext(scan, ForwardScanDirection)))
 	{
 		Form_pg_database pgdatabase = (Form_pg_database) GETSTRUCT(tup);
 		avw_dbase  *avdb;
@@ -1912,7 +1912,7 @@ get_database_list(void)
 		MemoryContextSwitchTo(oldcxt);
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 	heap_close(rel, AccessShareLock);
 
 	CommitTransactionCommand();
@@ -2043,13 +2043,13 @@ do_autovacuum(void)
 	 * wide tables there might be proportionally much more activity in the
 	 * TOAST table than in its parent.
 	 */
-	relScan = heap_beginscan_catalog(classRel, 0, NULL);
+	relScan = table_beginscan_catalog(classRel, 0, NULL);
 
 	/*
 	 * On the first pass, we collect main tables to vacuum, and also the main
 	 * table relid to TOAST relid mapping.
 	 */
-	while ((tuple = heap_getnext(relScan, ForwardScanDirection)) != NULL)
+	while ((tuple = table_scan_getnext(relScan, ForwardScanDirection)) != NULL)
 	{
 		Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
 		PgStat_StatTabEntry *tabentry;
@@ -2135,7 +2135,7 @@ do_autovacuum(void)
 		}
 	}
 
-	heap_endscan(relScan);
+	table_endscan(relScan);
 
 	/* second pass: check TOAST tables */
 	ScanKeyInit(&key,
@@ -2143,8 +2143,8 @@ do_autovacuum(void)
 				BTEqualStrategyNumber, F_CHAREQ,
 				CharGetDatum(RELKIND_TOASTVALUE));
 
-	relScan = heap_beginscan_catalog(classRel, 1, &key);
-	while ((tuple = heap_getnext(relScan, ForwardScanDirection)) != NULL)
+	relScan = table_beginscan_catalog(classRel, 1, &key);
+	while ((tuple = table_scan_getnext(relScan, ForwardScanDirection)) != NULL)
 	{
 		Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
 		PgStat_StatTabEntry *tabentry;
@@ -2190,7 +2190,7 @@ do_autovacuum(void)
 			table_oids = lappend_oid(table_oids, relid);
 	}
 
-	heap_endscan(relScan);
+	table_endscan(relScan);
 	heap_close(classRel, AccessShareLock);
 
 	/*
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index d13011454c..7d683ad49a 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -36,6 +36,7 @@
 
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/tableam.h"
 #include "access/transam.h"
 #include "access/twophase_rmgr.h"
 #include "access/xact.h"
@@ -1221,8 +1222,8 @@ pgstat_collect_oids(Oid catalogid)
 
 	rel = heap_open(catalogid, AccessShareLock);
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	scan = heap_beginscan(rel, snapshot, 0, NULL);
-	while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	scan = table_beginscan(rel, snapshot, 0, NULL);
+	while ((tup = table_scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Oid			thisoid = HeapTupleGetOid(tup);
 
@@ -1230,7 +1231,7 @@ pgstat_collect_oids(Oid catalogid)
 
 		(void) hash_search(htab, (void *) &thisoid, HASH_ENTER, NULL);
 	}
-	heap_endscan(scan);
+	table_endscan(scan);
 	UnregisterSnapshot(snapshot);
 	heap_close(rel, AccessShareLock);
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 2da9129562..6837cc4be2 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -24,6 +24,7 @@
 #include "access/heapam.h"
 #include "access/htup.h"
 #include "access/htup_details.h"
+#include "access/tableam.h"
 #include "access/xact.h"
 
 #include "catalog/pg_subscription.h"
@@ -124,9 +125,9 @@ get_subscription_list(void)
 	(void) GetTransactionSnapshot();
 
 	rel = heap_open(SubscriptionRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(rel, 0, NULL);
+	scan = table_beginscan_catalog(rel, 0, NULL);
 
-	while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+	while (HeapTupleIsValid(tup = table_scan_getnext(scan, ForwardScanDirection)))
 	{
 		Form_pg_subscription subform = (Form_pg_subscription) GETSTRUCT(tup);
 		Subscription *sub;
@@ -152,7 +153,7 @@ get_subscription_list(void)
 		MemoryContextSwitchTo(oldcxt);
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 	heap_close(rel, AccessShareLock);
 
 	CommitTransactionCommand();
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index abf45fa82b..8c9e506926 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -17,6 +17,7 @@
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/multixact.h"
+#include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
@@ -435,13 +436,13 @@ DefineQueryRewrite(const char *rulename,
 								RelationGetRelationName(event_relation))));
 
 			snapshot = RegisterSnapshot(GetLatestSnapshot());
-			scanDesc = heap_beginscan(event_relation, snapshot, 0, NULL);
-			if (heap_getnext(scanDesc, ForwardScanDirection) != NULL)
+			scanDesc = table_beginscan(event_relation, snapshot, 0, NULL);
+			if (table_scan_getnext(scanDesc, ForwardScanDirection) != NULL)
 				ereport(ERROR,
 						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 						 errmsg("could not convert table \"%s\" to a view because it is not empty",
 								RelationGetRelationName(event_relation))));
-			heap_endscan(scanDesc);
+			table_endscan(scanDesc);
 			UnregisterSnapshot(snapshot);
 
 			if (event_relation->rd_rel->relhastriggers)
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index f9b330998d..8d1663d6a4 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -22,6 +22,7 @@
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/session.h"
+#include "access/tableam.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "access/xlog.h"
@@ -1212,10 +1213,10 @@ ThereIsAtLeastOneRole(void)
 
 	pg_authid_rel = heap_open(AuthIdRelationId, AccessShareLock);
 
-	scan = heap_beginscan_catalog(pg_authid_rel, 0, NULL);
-	result = (heap_getnext(scan, ForwardScanDirection) != NULL);
+	scan = table_beginscan_catalog(pg_authid_rel, 0, NULL);
+	result = (table_scan_getnext(scan, ForwardScanDirection) != NULL);
 
-	heap_endscan(scan);
+	table_endscan(scan);
 	heap_close(pg_authid_rel, AccessShareLock);
 
 	return result;
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index d8fa9d668a..a1baaa96e1 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -108,26 +108,25 @@ typedef struct ParallelHeapScanDescData *ParallelHeapScanDesc;
 #define HeapScanIsValid(scan) PointerIsValid(scan)
 
 extern HeapScanDesc heap_beginscan(Relation relation, Snapshot snapshot,
-			   int nkeys, ScanKey key);
-extern HeapScanDesc heap_beginscan_catalog(Relation relation, int nkeys,
-					   ScanKey key);
-extern HeapScanDesc heap_beginscan_strat(Relation relation, Snapshot snapshot,
-					 int nkeys, ScanKey key,
-					 bool allow_strat, bool allow_sync);
-extern HeapScanDesc heap_beginscan_bm(Relation relation, Snapshot snapshot,
-				  int nkeys, ScanKey key);
-extern HeapScanDesc heap_beginscan_sampling(Relation relation,
-						Snapshot snapshot, int nkeys, ScanKey key,
-						bool allow_strat, bool allow_sync, bool allow_pagemode);
+			   int nkeys, ScanKey key,
+			   ParallelHeapScanDesc parallel_scan,
+			   bool allow_strat,
+			   bool allow_sync,
+			   bool allow_pagemode,
+			   bool is_bitmapscan,
+			   bool is_samplescan,
+			   bool temp_snap);
 extern void heap_setscanlimits(HeapScanDesc scan, BlockNumber startBlk,
 				   BlockNumber endBlk);
 extern void heapgetpage(HeapScanDesc scan, BlockNumber page);
-extern void heap_rescan(HeapScanDesc scan, ScanKey key);
+extern void heap_rescan(HeapScanDesc scan, ScanKey key, bool set_params,
+			bool allow_strat, bool allow_sync, bool allow_pagemode);
 extern void heap_rescan_set_params(HeapScanDesc scan, ScanKey key,
 					   bool allow_strat, bool allow_sync, bool allow_pagemode);
 extern void heap_endscan(HeapScanDesc scan);
-extern HeapTuple heap_getnext(HeapScanDesc scan, ScanDirection direction);
-
+extern TableTuple heap_getnext(HeapScanDesc scan, ScanDirection direction);
+extern TupleTableSlot *heap_getnextslot(HeapScanDesc sscan, ScanDirection direction,
+				 TupleTableSlot *slot);
 extern Size heap_parallelscan_estimate(Snapshot snapshot);
 extern void heap_parallelscan_initialize(ParallelHeapScanDesc target,
 							 Relation relation, Snapshot snapshot);
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 1c5416235f..4fd353dd05 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -42,6 +42,34 @@ typedef List *(*InsertIndexTuples) (TupleTableSlot *slot, EState *estate, bool n
 /* Function pointer to let the index tuple delete from storage am */
 typedef void (*DeleteIndexTuples) (Relation rel, ItemPointer tid, TransactionId old_xmin);
 
+extern HeapScanDesc table_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan);
+
+extern void table_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
+extern HeapScanDesc table_beginscan(Relation relation, Snapshot snapshot,
+				  int nkeys, ScanKey key);
+extern HeapScanDesc table_beginscan_catalog(Relation relation, int nkeys, ScanKey key);
+extern HeapScanDesc table_beginscan_strat(Relation relation, Snapshot snapshot,
+						int nkeys, ScanKey key,
+						bool allow_strat, bool allow_sync);
+extern HeapScanDesc table_beginscan_bm(Relation relation, Snapshot snapshot,
+					 int nkeys, ScanKey key);
+extern HeapScanDesc table_beginscan_sampling(Relation relation, Snapshot snapshot,
+						   int nkeys, ScanKey key,
+						   bool allow_strat, bool allow_sync, bool allow_pagemode);
+
+extern void table_endscan(HeapScanDesc scan);
+extern void table_rescan(HeapScanDesc scan, ScanKey key);
+extern void table_rescan_set_params(HeapScanDesc scan, ScanKey key,
+						  bool allow_strat, bool allow_sync, bool allow_pagemode);
+extern void table_scan_update_snapshot(HeapScanDesc scan, Snapshot snapshot);
+
+extern TableTuple table_scan_getnext(HeapScanDesc sscan, ScanDirection direction);
+extern TupleTableSlot *table_scan_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *slot);
+
+extern void storage_get_latest_tid(Relation relation,
+					   Snapshot snapshot,
+					   ItemPointer tid);
+
 extern bool table_fetch(Relation relation,
 			  ItemPointer tid,
 			  Snapshot snapshot,
@@ -50,6 +78,13 @@ extern bool table_fetch(Relation relation,
 			  bool keep_buf,
 			  Relation stats_relation);
 
+extern bool table_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
+						  Snapshot snapshot, HeapTuple heapTuple,
+						  bool *all_dead, bool first_call);
+
+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,
diff --git a/src/include/access/tableam_common.h b/src/include/access/tableam_common.h
index 26541abbde..a473c67e86 100644
--- a/src/include/access/tableam_common.h
+++ b/src/include/access/tableam_common.h
@@ -28,6 +28,7 @@
 
 /* A physical tuple coming from a table AM scan */
 typedef void *TableTuple;
+typedef void *TableScanDesc;
 
 /* Result codes for HeapTupleSatisfiesVacuum */
 typedef enum
diff --git a/src/include/access/tableamapi.h b/src/include/access/tableamapi.h
index d6293fc44d..8acbde32c3 100644
--- a/src/include/access/tableamapi.h
+++ b/src/include/access/tableamapi.h
@@ -81,6 +81,39 @@ typedef TableTuple(*TupleFromDatum_function) (Datum data, Oid tableoid);
 
 typedef void (*RelationSync_function) (Relation relation);
 
+
+typedef HeapScanDesc (*ScanBegin_function) (Relation relation,
+											Snapshot snapshot,
+											int nkeys, ScanKey key,
+											ParallelHeapScanDesc parallel_scan,
+											bool allow_strat,
+											bool allow_sync,
+											bool allow_pagemode,
+											bool is_bitmapscan,
+											bool is_samplescan,
+											bool temp_snap);
+typedef void (*ScanSetlimits_function) (HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
+
+/* must return a TupleTableSlot? */
+typedef TableTuple(*ScanGetnext_function) (HeapScanDesc scan,
+											 ScanDirection direction);
+
+typedef TupleTableSlot *(*ScanGetnextSlot_function) (HeapScanDesc scan,
+													 ScanDirection direction, TupleTableSlot *slot);
+
+typedef void (*ScanEnd_function) (HeapScanDesc scan);
+
+
+typedef void (*ScanGetpage_function) (HeapScanDesc scan, BlockNumber page);
+typedef void (*ScanRescan_function) (HeapScanDesc scan, ScanKey key, bool set_params,
+									 bool allow_strat, bool allow_sync, bool allow_pagemode);
+typedef void (*ScanUpdateSnapshot_function) (HeapScanDesc scan, Snapshot snapshot);
+
+typedef bool (*HotSearchBuffer_function) (ItemPointer tid, Relation relation,
+										  Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
+										  bool *all_dead, bool first_call);
+
+
 /*
  * API struct for a table AM.  Note this must be stored in a single palloc'd
  * chunk of memory.
@@ -114,6 +147,17 @@ typedef struct TableAmRoutine
 
 	RelationSync_function relation_sync;	/* heap_sync */
 
+	/* Operations on relation scans */
+	ScanBegin_function scan_begin;
+	ScanSetlimits_function scansetlimits;
+	ScanGetnext_function scan_getnext;
+	ScanGetnextSlot_function scan_getnextslot;
+	ScanEnd_function scan_end;
+	ScanGetpage_function scan_getpage;
+	ScanRescan_function scan_rescan;
+	ScanUpdateSnapshot_function scan_update_snapshot;
+	HotSearchBuffer_function hot_search_buffer; /* heap_hot_search_buffer */
+
 }			TableAmRoutine;
 
 extern TableAmRoutine * GetTableAmRoutine(Oid amhandler);
diff --git a/src/include/executor/functions.h b/src/include/executor/functions.h
index e7454ee790..ad225cd179 100644
--- a/src/include/executor/functions.h
+++ b/src/include/executor/functions.h
@@ -22,7 +22,7 @@ typedef struct SQLFunctionParseInfo *SQLFunctionParseInfoPtr;
 
 extern Datum fmgr_sql(PG_FUNCTION_ARGS);
 
-extern SQLFunctionParseInfoPtr prepare_sql_fn_parse_info(HeapTuple procedureTuple,
+extern SQLFunctionParseInfoPtr prepare_sql_fn_parse_info(TableTuple procedureTuple,
 						  Node *call_expr,
 						  Oid inputCollation);
 
diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h
index 43580c5158..09b52b05f6 100644
--- a/src/include/executor/spi.h
+++ b/src/include/executor/spi.h
@@ -25,7 +25,7 @@ typedef struct SPITupleTable
 	uint64		alloced;		/* # of alloced vals */
 	uint64		free;			/* # of free vals */
 	TupleDesc	tupdesc;		/* tuple descriptor */
-	HeapTuple  *vals;			/* tuples */
+	TableTuple *vals;			/* tuples */
 	slist_node	next;			/* link for internal bookkeeping */
 	SubTransactionId subid;		/* subxact in which tuptable was created */
 } SPITupleTable;
@@ -117,10 +117,10 @@ extern const char *SPI_result_code_string(int code);
 extern List *SPI_plan_get_plan_sources(SPIPlanPtr plan);
 extern CachedPlan *SPI_plan_get_cached_plan(SPIPlanPtr plan);
 
-extern HeapTuple SPI_copytuple(HeapTuple tuple);
-extern HeapTupleHeader SPI_returntuple(HeapTuple tuple, TupleDesc tupdesc);
-extern HeapTuple SPI_modifytuple(Relation rel, HeapTuple tuple, int natts,
-				int *attnum, Datum *Values, const char *Nulls);
+extern TableTuple SPI_copytuple(TableTuple tuple);
+extern HeapTupleHeader SPI_returntuple(TableTuple tuple, TupleDesc tupdesc);
+extern TableTuple SPI_modifytuple(Relation rel, HeapTuple tuple, int natts,
+									int *attnum, Datum *Values, const char *Nulls);
 extern int	SPI_fnumber(TupleDesc tupdesc, const char *fname);
 extern char *SPI_fname(TupleDesc tupdesc, int fnumber);
 extern char *SPI_getvalue(HeapTuple tuple, TupleDesc tupdesc, int fnumber);
@@ -133,7 +133,7 @@ extern void *SPI_palloc(Size size);
 extern void *SPI_repalloc(void *pointer, Size size);
 extern void SPI_pfree(void *pointer);
 extern Datum SPI_datumTransfer(Datum value, bool typByVal, int typLen);
-extern void SPI_freetuple(HeapTuple pointer);
+extern void SPI_freetuple(TableTuple pointer);
 extern void SPI_freetuptable(SPITupleTable *tuptable);
 
 extern Portal SPI_cursor_open(const char *name, SPIPlanPtr plan,
diff --git a/src/include/executor/tqueue.h b/src/include/executor/tqueue.h
index 0fe3639252..4635985222 100644
--- a/src/include/executor/tqueue.h
+++ b/src/include/executor/tqueue.h
@@ -26,7 +26,7 @@ extern DestReceiver *CreateTupleQueueDestReceiver(shm_mq_handle *handle);
 /* Use these to receive tuples from a shm_mq. */
 extern TupleQueueReader *CreateTupleQueueReader(shm_mq_handle *handle);
 extern void DestroyTupleQueueReader(TupleQueueReader *reader);
-extern HeapTuple TupleQueueReaderNext(TupleQueueReader *reader,
-					 bool nowait, bool *done);
+extern TableTuple TupleQueueReaderNext(TupleQueueReader *reader,
+										 bool nowait, bool *done);
 
 #endif							/* TQUEUE_H */
diff --git a/src/include/funcapi.h b/src/include/funcapi.h
index c2da2eb157..e30af27405 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -237,7 +237,7 @@ extern TupleDesc TypeGetTupleDesc(Oid typeoid, List *colaliases);
 /* from execTuples.c */
 extern TupleDesc BlessTupleDesc(TupleDesc tupdesc);
 extern AttInMetadata *TupleDescGetAttInMetadata(TupleDesc tupdesc);
-extern HeapTuple BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values);
+extern TableTuple BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values);
 extern Datum HeapTupleHeaderGetDatum(HeapTupleHeader tuple);
 extern TupleTableSlot *TupleDescGetSlot(TupleDesc tupdesc);
 
-- 
2.15.0.windows.1



  [application/octet-stream] 0008-Remove-HeapScanDesc-usage-outside-heap.patch (95.8K, ../../CAJrrPGdY_hgvu06R_vCibUktKRLp=b8nBfeZkh=KRc95tq4euA@mail.gmail.com/13-0008-Remove-HeapScanDesc-usage-outside-heap.patch)
  download | inline diff:
From e244c06c9987f5e866a8e132091fa04434f94166 Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Tue, 9 Jan 2018 17:39:51 +1100
Subject: [PATCH 08/12] Remove HeapScanDesc usage outside heap

HeapScanDesc is divided into two scan descriptors.
TableScanDesc and HeapPageScanDesc.

TableScanDesc has common members that are should
be available across all the storage routines and
HeapPageScanDesc is avaiable only for the table AM
routine that supports Heap storage with page format.
The HeapPageScanDesc is used internally by the heapam
storage routine and also this is exposed to Bitmap Heap
and Sample scan's as they depend on the Heap page format.

while generating the Bitmap Heap and Sample scan's,
the planner now checks whether the storage routine
supports returning HeapPageScanDesc or not? Based on
this decision, the planner plans above two plans.
---
 contrib/pgrowlocks/pgrowlocks.c            |   4 +-
 contrib/pgstattuple/pgstattuple.c          |  10 +-
 contrib/tsm_system_rows/tsm_system_rows.c  |  18 +-
 contrib/tsm_system_time/tsm_system_time.c  |   8 +-
 src/backend/access/heap/heapam.c           | 424 +++++++++++++++--------------
 src/backend/access/heap/heapam_handler.c   |  53 ++++
 src/backend/access/index/genam.c           |   4 +-
 src/backend/access/table/tableam.c         |  56 +++-
 src/backend/access/tablesample/system.c    |   2 +-
 src/backend/bootstrap/bootstrap.c          |   4 +-
 src/backend/catalog/aclchk.c               |   4 +-
 src/backend/catalog/index.c                |   8 +-
 src/backend/catalog/partition.c            |   2 +-
 src/backend/catalog/pg_conversion.c        |   2 +-
 src/backend/catalog/pg_db_role_setting.c   |   2 +-
 src/backend/catalog/pg_publication.c       |   2 +-
 src/backend/catalog/pg_subscription.c      |   2 +-
 src/backend/commands/cluster.c             |   4 +-
 src/backend/commands/copy.c                |   2 +-
 src/backend/commands/dbcommands.c          |   6 +-
 src/backend/commands/indexcmds.c           |   2 +-
 src/backend/commands/tablecmds.c           |  10 +-
 src/backend/commands/tablespace.c          |  10 +-
 src/backend/commands/typecmds.c            |   4 +-
 src/backend/commands/vacuum.c              |   4 +-
 src/backend/executor/nodeBitmapHeapscan.c  |  70 +++--
 src/backend/executor/nodeSamplescan.c      |  49 ++--
 src/backend/executor/nodeSeqscan.c         |   5 +-
 src/backend/optimizer/util/plancat.c       |   4 +-
 src/backend/postmaster/autovacuum.c        |   4 +-
 src/backend/postmaster/pgstat.c            |   2 +-
 src/backend/replication/logical/launcher.c |   2 +-
 src/backend/rewrite/rewriteDefine.c        |   2 +-
 src/backend/utils/init/postinit.c          |   2 +-
 src/include/access/heapam.h                |  22 +-
 src/include/access/relscan.h               |  47 ++--
 src/include/access/tableam.h               |  30 +-
 src/include/access/tableam_common.h        |   1 -
 src/include/access/tableamapi.h            |  26 +-
 src/include/nodes/execnodes.h              |   4 +-
 40 files changed, 518 insertions(+), 399 deletions(-)

diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index d3b7170972..7fa2540563 100644
--- a/contrib/pgrowlocks/pgrowlocks.c
+++ b/contrib/pgrowlocks/pgrowlocks.c
@@ -56,7 +56,7 @@ PG_FUNCTION_INFO_V1(pgrowlocks);
 typedef struct
 {
 	Relation	rel;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	int			ncolumns;
 } MyData;
 
@@ -71,7 +71,7 @@ Datum
 pgrowlocks(PG_FUNCTION_ARGS)
 {
 	FuncCallContext *funcctx;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tuple;
 	TupleDesc	tupdesc;
 	AttInMetadata *attinmeta;
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index 7340c4f3dd..d3b2720fac 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -314,7 +314,8 @@ pgstat_relation(Relation rel, FunctionCallInfo fcinfo)
 static Datum
 pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 {
-	HeapScanDesc scan;
+	TableScanDesc scan;
+	HeapPageScanDesc pagescan;
 	HeapTuple	tuple;
 	BlockNumber nblocks;
 	BlockNumber block = 0;		/* next block to count free space in */
@@ -328,7 +329,8 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 	scan = table_beginscan_strat(rel, SnapshotAny, 0, NULL, true, false);
 	InitDirtySnapshot(SnapshotDirty);
 
-	nblocks = scan->rs_nblocks; /* # blocks to be scanned */
+	pagescan = tableam_get_heappagescandesc(scan);
+	nblocks = pagescan->rs_nblocks; /* # blocks to be scanned */
 
 	/* scan the relation */
 	while ((tuple = table_scan_getnext(scan, ForwardScanDirection)) != NULL)
@@ -364,7 +366,7 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 			CHECK_FOR_INTERRUPTS();
 
 			buffer = ReadBufferExtended(rel, MAIN_FORKNUM, block,
-										RBM_NORMAL, scan->rs_strategy);
+										RBM_NORMAL, pagescan->rs_strategy);
 			LockBuffer(buffer, BUFFER_LOCK_SHARE);
 			stat.free_space += PageGetHeapFreeSpace((Page) BufferGetPage(buffer));
 			UnlockReleaseBuffer(buffer);
@@ -377,7 +379,7 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 		CHECK_FOR_INTERRUPTS();
 
 		buffer = ReadBufferExtended(rel, MAIN_FORKNUM, block,
-									RBM_NORMAL, scan->rs_strategy);
+									RBM_NORMAL, pagescan->rs_strategy);
 		LockBuffer(buffer, BUFFER_LOCK_SHARE);
 		stat.free_space += PageGetHeapFreeSpace((Page) BufferGetPage(buffer));
 		UnlockReleaseBuffer(buffer);
diff --git a/contrib/tsm_system_rows/tsm_system_rows.c b/contrib/tsm_system_rows/tsm_system_rows.c
index 83f841f0c2..a2a1141d6f 100644
--- a/contrib/tsm_system_rows/tsm_system_rows.c
+++ b/contrib/tsm_system_rows/tsm_system_rows.c
@@ -71,7 +71,7 @@ static BlockNumber system_rows_nextsampleblock(SampleScanState *node);
 static OffsetNumber system_rows_nextsampletuple(SampleScanState *node,
 							BlockNumber blockno,
 							OffsetNumber maxoffset);
-static bool SampleOffsetVisible(OffsetNumber tupoffset, HeapScanDesc scan);
+static bool SampleOffsetVisible(OffsetNumber tupoffset, HeapPageScanDesc scan);
 static uint32 random_relative_prime(uint32 n, SamplerRandomState randstate);
 
 
@@ -209,7 +209,7 @@ static BlockNumber
 system_rows_nextsampleblock(SampleScanState *node)
 {
 	SystemRowsSamplerData *sampler = (SystemRowsSamplerData *) node->tsm_state;
-	HeapScanDesc scan = node->ss.ss_currentScanDesc;
+	HeapPageScanDesc pagescan = node->pagescan;
 
 	/* First call within scan? */
 	if (sampler->doneblocks == 0)
@@ -221,14 +221,14 @@ system_rows_nextsampleblock(SampleScanState *node)
 			SamplerRandomState randstate;
 
 			/* If relation is empty, there's nothing to scan */
-			if (scan->rs_nblocks == 0)
+			if (pagescan->rs_nblocks == 0)
 				return InvalidBlockNumber;
 
 			/* We only need an RNG during this setup step */
 			sampler_random_init_state(sampler->seed, randstate);
 
 			/* Compute nblocks/firstblock/step only once per query */
-			sampler->nblocks = scan->rs_nblocks;
+			sampler->nblocks = pagescan->rs_nblocks;
 
 			/* Choose random starting block within the relation */
 			/* (Actually this is the predecessor of the first block visited) */
@@ -258,7 +258,7 @@ system_rows_nextsampleblock(SampleScanState *node)
 	{
 		/* Advance lb, using uint64 arithmetic to forestall overflow */
 		sampler->lb = ((uint64) sampler->lb + sampler->step) % sampler->nblocks;
-	} while (sampler->lb >= scan->rs_nblocks);
+	} while (sampler->lb >= pagescan->rs_nblocks);
 
 	return sampler->lb;
 }
@@ -278,7 +278,7 @@ system_rows_nextsampletuple(SampleScanState *node,
 							OffsetNumber maxoffset)
 {
 	SystemRowsSamplerData *sampler = (SystemRowsSamplerData *) node->tsm_state;
-	HeapScanDesc scan = node->ss.ss_currentScanDesc;
+	HeapPageScanDesc pagescan = node->pagescan;
 	OffsetNumber tupoffset = sampler->lt;
 
 	/* Quit if we've returned all needed tuples */
@@ -291,7 +291,7 @@ system_rows_nextsampletuple(SampleScanState *node,
 	 */
 
 	/* We rely on the data accumulated in pagemode access */
-	Assert(scan->rs_pageatatime);
+	Assert(pagescan->rs_pageatatime);
 	for (;;)
 	{
 		/* Advance to next possible offset on page */
@@ -308,7 +308,7 @@ system_rows_nextsampletuple(SampleScanState *node,
 		}
 
 		/* Found a candidate? */
-		if (SampleOffsetVisible(tupoffset, scan))
+		if (SampleOffsetVisible(tupoffset, pagescan))
 		{
 			sampler->donetuples++;
 			break;
@@ -327,7 +327,7 @@ system_rows_nextsampletuple(SampleScanState *node,
  * so just look at the info it left in rs_vistuples[].
  */
 static bool
-SampleOffsetVisible(OffsetNumber tupoffset, HeapScanDesc scan)
+SampleOffsetVisible(OffsetNumber tupoffset, HeapPageScanDesc scan)
 {
 	int			start = 0,
 				end = scan->rs_ntuples - 1;
diff --git a/contrib/tsm_system_time/tsm_system_time.c b/contrib/tsm_system_time/tsm_system_time.c
index f0c220aa4a..f9925bb8b8 100644
--- a/contrib/tsm_system_time/tsm_system_time.c
+++ b/contrib/tsm_system_time/tsm_system_time.c
@@ -219,7 +219,7 @@ static BlockNumber
 system_time_nextsampleblock(SampleScanState *node)
 {
 	SystemTimeSamplerData *sampler = (SystemTimeSamplerData *) node->tsm_state;
-	HeapScanDesc scan = node->ss.ss_currentScanDesc;
+	HeapPageScanDesc pagescan = node->pagescan;
 	instr_time	cur_time;
 
 	/* First call within scan? */
@@ -232,14 +232,14 @@ system_time_nextsampleblock(SampleScanState *node)
 			SamplerRandomState randstate;
 
 			/* If relation is empty, there's nothing to scan */
-			if (scan->rs_nblocks == 0)
+			if (pagescan->rs_nblocks == 0)
 				return InvalidBlockNumber;
 
 			/* We only need an RNG during this setup step */
 			sampler_random_init_state(sampler->seed, randstate);
 
 			/* Compute nblocks/firstblock/step only once per query */
-			sampler->nblocks = scan->rs_nblocks;
+			sampler->nblocks = pagescan->rs_nblocks;
 
 			/* Choose random starting block within the relation */
 			/* (Actually this is the predecessor of the first block visited) */
@@ -275,7 +275,7 @@ system_time_nextsampleblock(SampleScanState *node)
 	{
 		/* Advance lb, using uint64 arithmetic to forestall overflow */
 		sampler->lb = ((uint64) sampler->lb + sampler->step) % sampler->nblocks;
-	} while (sampler->lb >= scan->rs_nblocks);
+	} while (sampler->lb >= pagescan->rs_nblocks);
 
 	return sampler->lb;
 }
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index c1f3d9d3d3..088799c70d 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -220,9 +220,9 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
 	 * lock that ensures the interesting tuple(s) won't change.)
 	 */
 	if (scan->rs_parallel != NULL)
-		scan->rs_nblocks = scan->rs_parallel->phs_nblocks;
+		scan->rs_pagescan.rs_nblocks = scan->rs_parallel->phs_nblocks;
 	else
-		scan->rs_nblocks = RelationGetNumberOfBlocks(scan->rs_rd);
+		scan->rs_pagescan.rs_nblocks = RelationGetNumberOfBlocks(scan->rs_scan.rs_rd);
 
 	/*
 	 * If the table is large relative to NBuffers, use a bulk-read access
@@ -236,8 +236,8 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
 	 * Note that heap_parallelscan_initialize has a very similar test; if you
 	 * change this, consider changing that one, too.
 	 */
-	if (!RelationUsesLocalBuffers(scan->rs_rd) &&
-		scan->rs_nblocks > NBuffers / 4)
+	if (!RelationUsesLocalBuffers(scan->rs_scan.rs_rd) &&
+		scan->rs_pagescan.rs_nblocks > NBuffers / 4)
 	{
 		allow_strat = scan->rs_allow_strat;
 		allow_sync = scan->rs_allow_sync;
@@ -248,20 +248,20 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
 	if (allow_strat)
 	{
 		/* During a rescan, keep the previous strategy object. */
-		if (scan->rs_strategy == NULL)
-			scan->rs_strategy = GetAccessStrategy(BAS_BULKREAD);
+		if (scan->rs_pagescan.rs_strategy == NULL)
+			scan->rs_pagescan.rs_strategy = GetAccessStrategy(BAS_BULKREAD);
 	}
 	else
 	{
-		if (scan->rs_strategy != NULL)
-			FreeAccessStrategy(scan->rs_strategy);
-		scan->rs_strategy = NULL;
+		if (scan->rs_pagescan.rs_strategy != NULL)
+			FreeAccessStrategy(scan->rs_pagescan.rs_strategy);
+		scan->rs_pagescan.rs_strategy = NULL;
 	}
 
 	if (scan->rs_parallel != NULL)
 	{
 		/* For parallel scan, believe whatever ParallelHeapScanDesc says. */
-		scan->rs_syncscan = scan->rs_parallel->phs_syncscan;
+		scan->rs_pagescan.rs_syncscan = scan->rs_parallel->phs_syncscan;
 	}
 	else if (keep_startblock)
 	{
@@ -270,25 +270,25 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
 		 * so that rewinding a cursor doesn't generate surprising results.
 		 * Reset the active syncscan setting, though.
 		 */
-		scan->rs_syncscan = (allow_sync && synchronize_seqscans);
+		scan->rs_pagescan.rs_syncscan = (allow_sync && synchronize_seqscans);
 	}
 	else if (allow_sync && synchronize_seqscans)
 	{
-		scan->rs_syncscan = true;
-		scan->rs_startblock = ss_get_location(scan->rs_rd, scan->rs_nblocks);
+		scan->rs_pagescan.rs_syncscan = true;
+		scan->rs_pagescan.rs_startblock = ss_get_location(scan->rs_scan.rs_rd, scan->rs_pagescan.rs_nblocks);
 	}
 	else
 	{
-		scan->rs_syncscan = false;
-		scan->rs_startblock = 0;
+		scan->rs_pagescan.rs_syncscan = false;
+		scan->rs_pagescan.rs_startblock = 0;
 	}
 
-	scan->rs_numblocks = InvalidBlockNumber;
-	scan->rs_inited = false;
+	scan->rs_pagescan.rs_numblocks = InvalidBlockNumber;
+	scan->rs_scan.rs_inited = false;
 	scan->rs_ctup.t_data = NULL;
 	ItemPointerSetInvalid(&scan->rs_ctup.t_self);
-	scan->rs_cbuf = InvalidBuffer;
-	scan->rs_cblock = InvalidBlockNumber;
+	scan->rs_scan.rs_cbuf = InvalidBuffer;
+	scan->rs_scan.rs_cblock = InvalidBlockNumber;
 
 	/* page-at-a-time fields are always invalid when not rs_inited */
 
@@ -296,7 +296,7 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
 	 * copy the scan key, if appropriate
 	 */
 	if (key != NULL)
-		memcpy(scan->rs_key, key, scan->rs_nkeys * sizeof(ScanKeyData));
+		memcpy(scan->rs_scan.rs_key, key, scan->rs_scan.rs_nkeys * sizeof(ScanKeyData));
 
 	/*
 	 * Currently, we don't have a stats counter for bitmap heap scans (but the
@@ -304,7 +304,7 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
 	 * update stats for tuple fetches there)
 	 */
 	if (!scan->rs_bitmapscan && !scan->rs_samplescan)
-		pgstat_count_heap_scan(scan->rs_rd);
+		pgstat_count_heap_scan(scan->rs_scan.rs_rd);
 }
 
 /*
@@ -314,16 +314,19 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
  * numBlks is number of pages to scan (InvalidBlockNumber means "all")
  */
 void
-heap_setscanlimits(HeapScanDesc scan, BlockNumber startBlk, BlockNumber numBlks)
+heap_setscanlimits(TableScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
 {
-	Assert(!scan->rs_inited);	/* else too late to change */
-	Assert(!scan->rs_syncscan); /* else rs_startblock is significant */
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
+	Assert(!scan->rs_scan.rs_inited);	/* else too late to change */
+	Assert(!scan->rs_pagescan.rs_syncscan); /* else rs_startblock is
+											 * significant */
 
 	/* Check startBlk is valid (but allow case of zero blocks...) */
-	Assert(startBlk == 0 || startBlk < scan->rs_nblocks);
+	Assert(startBlk == 0 || startBlk < scan->rs_pagescan.rs_nblocks);
 
-	scan->rs_startblock = startBlk;
-	scan->rs_numblocks = numBlks;
+	scan->rs_pagescan.rs_startblock = startBlk;
+	scan->rs_pagescan.rs_numblocks = numBlks;
 }
 
 /*
@@ -334,8 +337,9 @@ heap_setscanlimits(HeapScanDesc scan, BlockNumber startBlk, BlockNumber numBlks)
  * which tuples on the page are visible.
  */
 void
-heapgetpage(HeapScanDesc scan, BlockNumber page)
+heapgetpage(TableScanDesc sscan, BlockNumber page)
 {
+	HeapScanDesc scan = (HeapScanDesc) sscan;
 	Buffer		buffer;
 	Snapshot	snapshot;
 	Page		dp;
@@ -345,13 +349,13 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 	ItemId		lpp;
 	bool		all_visible;
 
-	Assert(page < scan->rs_nblocks);
+	Assert(page < scan->rs_pagescan.rs_nblocks);
 
 	/* release previous scan buffer, if any */
-	if (BufferIsValid(scan->rs_cbuf))
+	if (BufferIsValid(scan->rs_scan.rs_cbuf))
 	{
-		ReleaseBuffer(scan->rs_cbuf);
-		scan->rs_cbuf = InvalidBuffer;
+		ReleaseBuffer(scan->rs_scan.rs_cbuf);
+		scan->rs_scan.rs_cbuf = InvalidBuffer;
 	}
 
 	/*
@@ -362,20 +366,20 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 	CHECK_FOR_INTERRUPTS();
 
 	/* read page using selected strategy */
-	scan->rs_cbuf = ReadBufferExtended(scan->rs_rd, MAIN_FORKNUM, page,
-									   RBM_NORMAL, scan->rs_strategy);
-	scan->rs_cblock = page;
+	scan->rs_scan.rs_cbuf = ReadBufferExtended(scan->rs_scan.rs_rd, MAIN_FORKNUM, page,
+											   RBM_NORMAL, scan->rs_pagescan.rs_strategy);
+	scan->rs_scan.rs_cblock = page;
 
-	if (!scan->rs_pageatatime)
+	if (!scan->rs_pagescan.rs_pageatatime)
 		return;
 
-	buffer = scan->rs_cbuf;
-	snapshot = scan->rs_snapshot;
+	buffer = scan->rs_scan.rs_cbuf;
+	snapshot = scan->rs_scan.rs_snapshot;
 
 	/*
 	 * Prune and repair fragmentation for the whole page, if possible.
 	 */
-	heap_page_prune_opt(scan->rs_rd, buffer);
+	heap_page_prune_opt(scan->rs_scan.rs_rd, buffer);
 
 	/*
 	 * We must hold share lock on the buffer content while examining tuple
@@ -385,7 +389,7 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 	LockBuffer(buffer, BUFFER_LOCK_SHARE);
 
 	dp = BufferGetPage(buffer);
-	TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+	TestForOldSnapshot(snapshot, scan->rs_scan.rs_rd, dp);
 	lines = PageGetMaxOffsetNumber(dp);
 	ntup = 0;
 
@@ -420,7 +424,7 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 			HeapTupleData loctup;
 			bool		valid;
 
-			loctup.t_tableOid = RelationGetRelid(scan->rs_rd);
+			loctup.t_tableOid = RelationGetRelid(scan->rs_scan.rs_rd);
 			loctup.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
 			loctup.t_len = ItemIdGetLength(lpp);
 			ItemPointerSet(&(loctup.t_self), page, lineoff);
@@ -428,20 +432,20 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 			if (all_visible)
 				valid = true;
 			else
-				valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_tableamroutine, &loctup, snapshot, buffer);
+				valid = HeapTupleSatisfiesVisibility(scan->rs_scan.rs_rd->rd_tableamroutine, &loctup, snapshot, buffer);
 
-			CheckForSerializableConflictOut(valid, scan->rs_rd, &loctup,
+			CheckForSerializableConflictOut(valid, scan->rs_scan.rs_rd, &loctup,
 											buffer, snapshot);
 
 			if (valid)
-				scan->rs_vistuples[ntup++] = lineoff;
+				scan->rs_pagescan.rs_vistuples[ntup++] = lineoff;
 		}
 	}
 
 	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
 
 	Assert(ntup <= MaxHeapTuplesPerPage);
-	scan->rs_ntuples = ntup;
+	scan->rs_pagescan.rs_ntuples = ntup;
 }
 
 /* ----------------
@@ -474,7 +478,7 @@ heapgettup(HeapScanDesc scan,
 		   ScanKey key)
 {
 	HeapTuple	tuple = &(scan->rs_ctup);
-	Snapshot	snapshot = scan->rs_snapshot;
+	Snapshot	snapshot = scan->rs_scan.rs_snapshot;
 	bool		backward = ScanDirectionIsBackward(dir);
 	BlockNumber page;
 	bool		finished;
@@ -489,14 +493,14 @@ heapgettup(HeapScanDesc scan,
 	 */
 	if (ScanDirectionIsForward(dir))
 	{
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
 			/*
 			 * return null immediately if relation is empty
 			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			if (scan->rs_pagescan.rs_nblocks == 0 || scan->rs_pagescan.rs_numblocks == 0)
 			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
+				Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 				tuple->t_data = NULL;
 				return;
 			}
@@ -509,29 +513,29 @@ heapgettup(HeapScanDesc scan,
 				/* Other processes might have already finished the scan. */
 				if (page == InvalidBlockNumber)
 				{
-					Assert(!BufferIsValid(scan->rs_cbuf));
+					Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 					tuple->t_data = NULL;
 					return;
 				}
 			}
 			else
-				page = scan->rs_startblock; /* first page */
-			heapgetpage(scan, page);
+				page = scan->rs_pagescan.rs_startblock; /* first page */
+			heapgetpage((TableScanDesc) scan, page);
 			lineoff = FirstOffsetNumber;	/* first offnum */
-			scan->rs_inited = true;
+			scan->rs_scan.rs_inited = true;
 		}
 		else
 		{
 			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
+			page = scan->rs_scan.rs_cblock; /* current page */
 			lineoff =			/* next offnum */
 				OffsetNumberNext(ItemPointerGetOffsetNumber(&(tuple->t_self)));
 		}
 
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_SHARE);
 
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_scan.rs_rd, dp);
 		lines = PageGetMaxOffsetNumber(dp);
 		/* page and lineoff now reference the physically next tid */
 
@@ -542,14 +546,14 @@ heapgettup(HeapScanDesc scan,
 		/* backward parallel scan not supported */
 		Assert(scan->rs_parallel == NULL);
 
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
 			/*
 			 * return null immediately if relation is empty
 			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			if (scan->rs_pagescan.rs_nblocks == 0 || scan->rs_pagescan.rs_numblocks == 0)
 			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
+				Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 				tuple->t_data = NULL;
 				return;
 			}
@@ -560,30 +564,30 @@ heapgettup(HeapScanDesc scan,
 			 * time, and much more likely that we'll just bollix things for
 			 * forward scanners.
 			 */
-			scan->rs_syncscan = false;
+			scan->rs_pagescan.rs_syncscan = false;
 			/* start from last page of the scan */
-			if (scan->rs_startblock > 0)
-				page = scan->rs_startblock - 1;
+			if (scan->rs_pagescan.rs_startblock > 0)
+				page = scan->rs_pagescan.rs_startblock - 1;
 			else
-				page = scan->rs_nblocks - 1;
-			heapgetpage(scan, page);
+				page = scan->rs_pagescan.rs_nblocks - 1;
+			heapgetpage((TableScanDesc) scan, page);
 		}
 		else
 		{
 			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
+			page = scan->rs_scan.rs_cblock; /* current page */
 		}
 
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_SHARE);
 
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_scan.rs_rd, dp);
 		lines = PageGetMaxOffsetNumber(dp);
 
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
 			lineoff = lines;	/* final offnum */
-			scan->rs_inited = true;
+			scan->rs_scan.rs_inited = true;
 		}
 		else
 		{
@@ -599,20 +603,20 @@ heapgettup(HeapScanDesc scan,
 		/*
 		 * ``no movement'' scan direction: refetch prior tuple
 		 */
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
-			Assert(!BufferIsValid(scan->rs_cbuf));
+			Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 			tuple->t_data = NULL;
 			return;
 		}
 
 		page = ItemPointerGetBlockNumber(&(tuple->t_self));
-		if (page != scan->rs_cblock)
-			heapgetpage(scan, page);
+		if (page != scan->rs_scan.rs_cblock)
+			heapgetpage((TableScanDesc) scan, page);
 
 		/* Since the tuple was previously fetched, needn't lock page here */
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_scan.rs_rd, dp);
 		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
 		lpp = PageGetItemId(dp, lineoff);
 		Assert(ItemIdIsNormal(lpp));
@@ -643,21 +647,21 @@ heapgettup(HeapScanDesc scan,
 				/*
 				 * if current tuple qualifies, return it.
 				 */
-				valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_tableamroutine,
+				valid = HeapTupleSatisfiesVisibility(scan->rs_scan.rs_rd->rd_tableamroutine,
 													 tuple,
 													 snapshot,
-													 scan->rs_cbuf);
+													 scan->rs_scan.rs_cbuf);
 
-				CheckForSerializableConflictOut(valid, scan->rs_rd, tuple,
-												scan->rs_cbuf, snapshot);
+				CheckForSerializableConflictOut(valid, scan->rs_scan.rs_rd, tuple,
+												scan->rs_scan.rs_cbuf, snapshot);
 
 				if (valid && key != NULL)
-					HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
+					HeapKeyTest(tuple, RelationGetDescr(scan->rs_scan.rs_rd),
 								nkeys, key, valid);
 
 				if (valid)
 				{
-					LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+					LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_UNLOCK);
 					return;
 				}
 			}
@@ -682,17 +686,17 @@ heapgettup(HeapScanDesc scan,
 		 * if we get here, it means we've exhausted the items on this page and
 		 * it's time to move to the next.
 		 */
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_UNLOCK);
 
 		/*
 		 * advance to next/prior page and detect end of scan
 		 */
 		if (backward)
 		{
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+			finished = (page == scan->rs_pagescan.rs_startblock) ||
+				(scan->rs_pagescan.rs_numblocks != InvalidBlockNumber ? --scan->rs_pagescan.rs_numblocks == 0 : false);
 			if (page == 0)
-				page = scan->rs_nblocks;
+				page = scan->rs_pagescan.rs_nblocks;
 			page--;
 		}
 		else if (scan->rs_parallel != NULL)
@@ -703,10 +707,10 @@ heapgettup(HeapScanDesc scan,
 		else
 		{
 			page++;
-			if (page >= scan->rs_nblocks)
+			if (page >= scan->rs_pagescan.rs_nblocks)
 				page = 0;
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+			finished = (page == scan->rs_pagescan.rs_startblock) ||
+				(scan->rs_pagescan.rs_numblocks != InvalidBlockNumber ? --scan->rs_pagescan.rs_numblocks == 0 : false);
 
 			/*
 			 * Report our new scan position for synchronization purposes. We
@@ -720,8 +724,8 @@ heapgettup(HeapScanDesc scan,
 			 * a little bit backwards on every invocation, which is confusing.
 			 * We don't guarantee any specific ordering in general, though.
 			 */
-			if (scan->rs_syncscan)
-				ss_report_location(scan->rs_rd, page);
+			if (scan->rs_pagescan.rs_syncscan)
+				ss_report_location(scan->rs_scan.rs_rd, page);
 		}
 
 		/*
@@ -729,21 +733,21 @@ heapgettup(HeapScanDesc scan,
 		 */
 		if (finished)
 		{
-			if (BufferIsValid(scan->rs_cbuf))
-				ReleaseBuffer(scan->rs_cbuf);
-			scan->rs_cbuf = InvalidBuffer;
-			scan->rs_cblock = InvalidBlockNumber;
+			if (BufferIsValid(scan->rs_scan.rs_cbuf))
+				ReleaseBuffer(scan->rs_scan.rs_cbuf);
+			scan->rs_scan.rs_cbuf = InvalidBuffer;
+			scan->rs_scan.rs_cblock = InvalidBlockNumber;
 			tuple->t_data = NULL;
-			scan->rs_inited = false;
+			scan->rs_scan.rs_inited = false;
 			return;
 		}
 
-		heapgetpage(scan, page);
+		heapgetpage((TableScanDesc) scan, page);
 
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_SHARE);
 
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_scan.rs_rd, dp);
 		lines = PageGetMaxOffsetNumber((Page) dp);
 		linesleft = lines;
 		if (backward)
@@ -794,14 +798,14 @@ heapgettup_pagemode(HeapScanDesc scan,
 	 */
 	if (ScanDirectionIsForward(dir))
 	{
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
 			/*
 			 * return null immediately if relation is empty
 			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			if (scan->rs_pagescan.rs_nblocks == 0 || scan->rs_pagescan.rs_numblocks == 0)
 			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
+				Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 				tuple->t_data = NULL;
 				return;
 			}
@@ -814,28 +818,28 @@ heapgettup_pagemode(HeapScanDesc scan,
 				/* Other processes might have already finished the scan. */
 				if (page == InvalidBlockNumber)
 				{
-					Assert(!BufferIsValid(scan->rs_cbuf));
+					Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 					tuple->t_data = NULL;
 					return;
 				}
 			}
 			else
-				page = scan->rs_startblock; /* first page */
-			heapgetpage(scan, page);
+				page = scan->rs_pagescan.rs_startblock; /* first page */
+			heapgetpage((TableScanDesc) scan, page);
 			lineindex = 0;
-			scan->rs_inited = true;
+			scan->rs_scan.rs_inited = true;
 		}
 		else
 		{
 			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
-			lineindex = scan->rs_cindex + 1;
+			page = scan->rs_scan.rs_cblock; /* current page */
+			lineindex = scan->rs_pagescan.rs_cindex + 1;
 		}
 
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_SHARE);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(scan->rs_scan.rs_snapshot, scan->rs_scan.rs_rd, dp);
+		lines = scan->rs_pagescan.rs_ntuples;
 		/* page and lineindex now reference the next visible tid */
 
 		linesleft = lines - lineindex;
@@ -845,14 +849,14 @@ heapgettup_pagemode(HeapScanDesc scan,
 		/* backward parallel scan not supported */
 		Assert(scan->rs_parallel == NULL);
 
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
 			/*
 			 * return null immediately if relation is empty
 			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			if (scan->rs_pagescan.rs_nblocks == 0 || scan->rs_pagescan.rs_numblocks == 0)
 			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
+				Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 				tuple->t_data = NULL;
 				return;
 			}
@@ -863,33 +867,33 @@ heapgettup_pagemode(HeapScanDesc scan,
 			 * time, and much more likely that we'll just bollix things for
 			 * forward scanners.
 			 */
-			scan->rs_syncscan = false;
+			scan->rs_pagescan.rs_syncscan = false;
 			/* start from last page of the scan */
-			if (scan->rs_startblock > 0)
-				page = scan->rs_startblock - 1;
+			if (scan->rs_pagescan.rs_startblock > 0)
+				page = scan->rs_pagescan.rs_startblock - 1;
 			else
-				page = scan->rs_nblocks - 1;
-			heapgetpage(scan, page);
+				page = scan->rs_pagescan.rs_nblocks - 1;
+			heapgetpage((TableScanDesc) scan, page);
 		}
 		else
 		{
 			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
+			page = scan->rs_scan.rs_cblock; /* current page */
 		}
 
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_SHARE);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(scan->rs_scan.rs_snapshot, scan->rs_scan.rs_rd, dp);
+		lines = scan->rs_pagescan.rs_ntuples;
 
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
 			lineindex = lines - 1;
-			scan->rs_inited = true;
+			scan->rs_scan.rs_inited = true;
 		}
 		else
 		{
-			lineindex = scan->rs_cindex - 1;
+			lineindex = scan->rs_pagescan.rs_cindex - 1;
 		}
 		/* page and lineindex now reference the previous visible tid */
 
@@ -900,20 +904,20 @@ heapgettup_pagemode(HeapScanDesc scan,
 		/*
 		 * ``no movement'' scan direction: refetch prior tuple
 		 */
-		if (!scan->rs_inited)
+		if (!scan->rs_scan.rs_inited)
 		{
-			Assert(!BufferIsValid(scan->rs_cbuf));
+			Assert(!BufferIsValid(scan->rs_scan.rs_cbuf));
 			tuple->t_data = NULL;
 			return;
 		}
 
 		page = ItemPointerGetBlockNumber(&(tuple->t_self));
-		if (page != scan->rs_cblock)
-			heapgetpage(scan, page);
+		if (page != scan->rs_scan.rs_cblock)
+			heapgetpage((TableScanDesc) scan, page);
 
 		/* Since the tuple was previously fetched, needn't lock page here */
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(scan->rs_scan.rs_snapshot, scan->rs_scan.rs_rd, dp);
 		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
 		lpp = PageGetItemId(dp, lineoff);
 		Assert(ItemIdIsNormal(lpp));
@@ -922,8 +926,8 @@ heapgettup_pagemode(HeapScanDesc scan,
 		tuple->t_len = ItemIdGetLength(lpp);
 
 		/* check that rs_cindex is in sync */
-		Assert(scan->rs_cindex < scan->rs_ntuples);
-		Assert(lineoff == scan->rs_vistuples[scan->rs_cindex]);
+		Assert(scan->rs_pagescan.rs_cindex < scan->rs_pagescan.rs_ntuples);
+		Assert(lineoff == scan->rs_pagescan.rs_vistuples[scan->rs_pagescan.rs_cindex]);
 
 		return;
 	}
@@ -936,7 +940,7 @@ heapgettup_pagemode(HeapScanDesc scan,
 	{
 		while (linesleft > 0)
 		{
-			lineoff = scan->rs_vistuples[lineindex];
+			lineoff = scan->rs_pagescan.rs_vistuples[lineindex];
 			lpp = PageGetItemId(dp, lineoff);
 			Assert(ItemIdIsNormal(lpp));
 
@@ -947,7 +951,7 @@ heapgettup_pagemode(HeapScanDesc scan,
 			/*
 			 * if current tuple qualifies, return it.
 			 */
-			if (HeapTupleSatisfiesVisibility(scan->rs_rd->rd_tableamroutine, tuple, scan->rs_snapshot, scan->rs_cbuf))
+			if (HeapTupleSatisfiesVisibility(scan->rs_scan.rs_rd->rd_tableamroutine, tuple, scan->rs_scan.rs_snapshot, scan->rs_scan.rs_cbuf))
 			{
 				/*
 				 * if current tuple qualifies, return it.
@@ -956,19 +960,19 @@ heapgettup_pagemode(HeapScanDesc scan,
 				{
 					bool		valid;
 
-					HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
+					HeapKeyTest(tuple, RelationGetDescr(scan->rs_scan.rs_rd),
 								nkeys, key, valid);
 					if (valid)
 					{
-						scan->rs_cindex = lineindex;
-						LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+						scan->rs_pagescan.rs_cindex = lineindex;
+						LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_UNLOCK);
 						return;
 					}
 				}
 				else
 				{
-					scan->rs_cindex = lineindex;
-					LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+					scan->rs_pagescan.rs_cindex = lineindex;
+					LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_UNLOCK);
 					return;
 				}
 			}
@@ -987,7 +991,7 @@ heapgettup_pagemode(HeapScanDesc scan,
 		 * if we get here, it means we've exhausted the items on this page and
 		 * it's time to move to the next.
 		 */
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_UNLOCK);
 
 		/*
 		 * if we get here, it means we've exhausted the items on this page and
@@ -995,10 +999,10 @@ heapgettup_pagemode(HeapScanDesc scan,
 		 */
 		if (backward)
 		{
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+			finished = (page == scan->rs_pagescan.rs_startblock) ||
+				(scan->rs_pagescan.rs_numblocks != InvalidBlockNumber ? --scan->rs_pagescan.rs_numblocks == 0 : false);
 			if (page == 0)
-				page = scan->rs_nblocks;
+				page = scan->rs_pagescan.rs_nblocks;
 			page--;
 		}
 		else if (scan->rs_parallel != NULL)
@@ -1009,10 +1013,10 @@ heapgettup_pagemode(HeapScanDesc scan,
 		else
 		{
 			page++;
-			if (page >= scan->rs_nblocks)
+			if (page >= scan->rs_pagescan.rs_nblocks)
 				page = 0;
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+			finished = (page == scan->rs_pagescan.rs_startblock) ||
+				(scan->rs_pagescan.rs_numblocks != InvalidBlockNumber ? --scan->rs_pagescan.rs_numblocks == 0 : false);
 
 			/*
 			 * Report our new scan position for synchronization purposes. We
@@ -1026,8 +1030,8 @@ heapgettup_pagemode(HeapScanDesc scan,
 			 * a little bit backwards on every invocation, which is confusing.
 			 * We don't guarantee any specific ordering in general, though.
 			 */
-			if (scan->rs_syncscan)
-				ss_report_location(scan->rs_rd, page);
+			if (scan->rs_pagescan.rs_syncscan)
+				ss_report_location(scan->rs_scan.rs_rd, page);
 		}
 
 		/*
@@ -1035,21 +1039,21 @@ heapgettup_pagemode(HeapScanDesc scan,
 		 */
 		if (finished)
 		{
-			if (BufferIsValid(scan->rs_cbuf))
-				ReleaseBuffer(scan->rs_cbuf);
-			scan->rs_cbuf = InvalidBuffer;
-			scan->rs_cblock = InvalidBlockNumber;
+			if (BufferIsValid(scan->rs_scan.rs_cbuf))
+				ReleaseBuffer(scan->rs_scan.rs_cbuf);
+			scan->rs_scan.rs_cbuf = InvalidBuffer;
+			scan->rs_scan.rs_cblock = InvalidBlockNumber;
 			tuple->t_data = NULL;
-			scan->rs_inited = false;
+			scan->rs_scan.rs_inited = false;
 			return;
 		}
 
-		heapgetpage(scan, page);
+		heapgetpage((TableScanDesc) scan, page);
 
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
+		LockBuffer(scan->rs_scan.rs_cbuf, BUFFER_LOCK_SHARE);
+		dp = BufferGetPage(scan->rs_scan.rs_cbuf);
+		TestForOldSnapshot(scan->rs_scan.rs_snapshot, scan->rs_scan.rs_rd, dp);
+		lines = scan->rs_pagescan.rs_ntuples;
 		linesleft = lines;
 		if (backward)
 			lineindex = lines - 1;
@@ -1376,7 +1380,7 @@ heap_openrv_extended(const RangeVar *relation, LOCKMODE lockmode,
 	return r;
 }
 
-HeapScanDesc
+TableScanDesc
 heap_beginscan(Relation relation, Snapshot snapshot,
 			   int nkeys, ScanKey key,
 			   ParallelHeapScanDesc parallel_scan,
@@ -1403,12 +1407,12 @@ heap_beginscan(Relation relation, Snapshot snapshot,
 	 */
 	scan = (HeapScanDesc) palloc(sizeof(HeapScanDescData));
 
-	scan->rs_rd = relation;
-	scan->rs_snapshot = snapshot;
-	scan->rs_nkeys = nkeys;
+	scan->rs_scan.rs_rd = relation;
+	scan->rs_scan.rs_snapshot = snapshot;
+	scan->rs_scan.rs_nkeys = nkeys;
 	scan->rs_bitmapscan = is_bitmapscan;
 	scan->rs_samplescan = is_samplescan;
-	scan->rs_strategy = NULL;	/* set in initscan */
+	scan->rs_pagescan.rs_strategy = NULL;	/* set in initscan */
 	scan->rs_allow_strat = allow_strat;
 	scan->rs_allow_sync = allow_sync;
 	scan->rs_temp_snap = temp_snap;
@@ -1417,7 +1421,7 @@ heap_beginscan(Relation relation, Snapshot snapshot,
 	/*
 	 * we can use page-at-a-time mode if it's an MVCC-safe snapshot
 	 */
-	scan->rs_pageatatime = allow_pagemode && IsMVCCSnapshot(snapshot);
+	scan->rs_pagescan.rs_pageatatime = allow_pagemode && IsMVCCSnapshot(snapshot);
 
 	/*
 	 * For a seqscan in a serializable transaction, acquire a predicate lock
@@ -1441,13 +1445,13 @@ heap_beginscan(Relation relation, Snapshot snapshot,
 	 * initscan() and we don't want to allocate memory again
 	 */
 	if (nkeys > 0)
-		scan->rs_key = (ScanKey) palloc(sizeof(ScanKeyData) * nkeys);
+		scan->rs_scan.rs_key = (ScanKey) palloc(sizeof(ScanKeyData) * nkeys);
 	else
-		scan->rs_key = NULL;
+		scan->rs_scan.rs_key = NULL;
 
 	initscan(scan, key, false);
 
-	return scan;
+	return (TableScanDesc) scan;
 }
 
 /* ----------------
@@ -1455,21 +1459,23 @@ heap_beginscan(Relation relation, Snapshot snapshot,
  * ----------------
  */
 void
-heap_rescan(HeapScanDesc scan, ScanKey key, bool set_params,
+heap_rescan(TableScanDesc sscan, ScanKey key, bool set_params,
 			bool allow_strat, bool allow_sync, bool allow_pagemode)
 {
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
 	if (set_params)
 	{
 		scan->rs_allow_strat = allow_strat;
 		scan->rs_allow_sync = allow_sync;
-		scan->rs_pageatatime = allow_pagemode && IsMVCCSnapshot(scan->rs_snapshot);
+		scan->rs_pagescan.rs_pageatatime = allow_pagemode && IsMVCCSnapshot(scan->rs_scan.rs_snapshot);
 	}
 
 	/*
 	 * unpin scan buffers
 	 */
-	if (BufferIsValid(scan->rs_cbuf))
-		ReleaseBuffer(scan->rs_cbuf);
+	if (BufferIsValid(scan->rs_scan.rs_cbuf))
+		ReleaseBuffer(scan->rs_scan.rs_cbuf);
 
 	/*
 	 * reinitialize scan descriptor
@@ -1500,29 +1506,31 @@ heap_rescan(HeapScanDesc scan, ScanKey key, bool set_params,
  * ----------------
  */
 void
-heap_endscan(HeapScanDesc scan)
+heap_endscan(TableScanDesc sscan)
 {
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
 	/* Note: no locking manipulations needed */
 
 	/*
 	 * unpin scan buffers
 	 */
-	if (BufferIsValid(scan->rs_cbuf))
-		ReleaseBuffer(scan->rs_cbuf);
+	if (BufferIsValid(scan->rs_scan.rs_cbuf))
+		ReleaseBuffer(scan->rs_scan.rs_cbuf);
 
 	/*
 	 * decrement relation reference count and free scan descriptor storage
 	 */
-	RelationDecrementReferenceCount(scan->rs_rd);
+	RelationDecrementReferenceCount(scan->rs_scan.rs_rd);
 
-	if (scan->rs_key)
-		pfree(scan->rs_key);
+	if (scan->rs_scan.rs_key)
+		pfree(scan->rs_scan.rs_key);
 
-	if (scan->rs_strategy != NULL)
-		FreeAccessStrategy(scan->rs_strategy);
+	if (scan->rs_pagescan.rs_strategy != NULL)
+		FreeAccessStrategy(scan->rs_pagescan.rs_strategy);
 
 	if (scan->rs_temp_snap)
-		UnregisterSnapshot(scan->rs_snapshot);
+		UnregisterSnapshot(scan->rs_scan.rs_snapshot);
 
 	pfree(scan);
 }
@@ -1618,7 +1626,7 @@ retry:
 		else
 		{
 			SpinLockRelease(&parallel_scan->phs_mutex);
-			sync_startpage = ss_get_location(scan->rs_rd, scan->rs_nblocks);
+			sync_startpage = ss_get_location(scan->rs_scan.rs_rd, scan->rs_pagescan.rs_nblocks);
 			goto retry;
 		}
 	}
@@ -1660,10 +1668,10 @@ heap_parallelscan_nextpage(HeapScanDesc scan)
 	 * starting block number, modulo nblocks.
 	 */
 	nallocated = pg_atomic_fetch_add_u64(&parallel_scan->phs_nallocated, 1);
-	if (nallocated >= scan->rs_nblocks)
+	if (nallocated >= scan->rs_pagescan.rs_nblocks)
 		page = InvalidBlockNumber;	/* all blocks have been allocated */
 	else
-		page = (nallocated + parallel_scan->phs_startblock) % scan->rs_nblocks;
+		page = (nallocated + parallel_scan->phs_startblock) % scan->rs_pagescan.rs_nblocks;
 
 	/*
 	 * Report scan location.  Normally, we report the current page number.
@@ -1672,12 +1680,12 @@ heap_parallelscan_nextpage(HeapScanDesc scan)
 	 * doesn't slew backwards.  We only report the position at the end of the
 	 * scan once, though: subsequent callers will report nothing.
 	 */
-	if (scan->rs_syncscan)
+	if (scan->rs_pagescan.rs_syncscan)
 	{
 		if (page != InvalidBlockNumber)
-			ss_report_location(scan->rs_rd, page);
-		else if (nallocated == scan->rs_nblocks)
-			ss_report_location(scan->rs_rd, parallel_scan->phs_startblock);
+			ss_report_location(scan->rs_scan.rs_rd, page);
+		else if (nallocated == scan->rs_pagescan.rs_nblocks)
+			ss_report_location(scan->rs_scan.rs_rd, parallel_scan->phs_startblock);
 	}
 
 	return page;
@@ -1690,12 +1698,14 @@ heap_parallelscan_nextpage(HeapScanDesc scan)
  * ----------------
  */
 void
-heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
+heap_update_snapshot(TableScanDesc sscan, Snapshot snapshot)
 {
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
 	Assert(IsMVCCSnapshot(snapshot));
 
 	RegisterSnapshot(snapshot);
-	scan->rs_snapshot = snapshot;
+	scan->rs_scan.rs_snapshot = snapshot;
 	scan->rs_temp_snap = true;
 }
 
@@ -1723,17 +1733,19 @@ heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
 #endif							/* !defined(HEAPDEBUGALL) */
 
 TableTuple
-heap_getnext(HeapScanDesc scan, ScanDirection direction)
+heap_getnext(TableScanDesc sscan, ScanDirection direction)
 {
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
 	/* Note: no locking manipulations needed */
 
 	HEAPDEBUG_1;				/* heap_getnext( info ) */
 
-	if (scan->rs_pageatatime)
+	if (scan->rs_pagescan.rs_pageatatime)
 		heapgettup_pagemode(scan, direction,
-							scan->rs_nkeys, scan->rs_key);
+							scan->rs_scan.rs_nkeys, scan->rs_scan.rs_key);
 	else
-		heapgettup(scan, direction, scan->rs_nkeys, scan->rs_key);
+		heapgettup(scan, direction, scan->rs_scan.rs_nkeys, scan->rs_scan.rs_key);
 
 	if (scan->rs_ctup.t_data == NULL)
 	{
@@ -1747,7 +1759,7 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 	 */
 	HEAPDEBUG_3;				/* heap_getnext returning tuple */
 
-	pgstat_count_heap_getnext(scan->rs_rd);
+	pgstat_count_heap_getnext(scan->rs_scan.rs_rd);
 
 	return heap_copytuple(&(scan->rs_ctup));
 }
@@ -1755,7 +1767,7 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 #ifdef HEAPAMSLOTDEBUGALL
 #define HEAPAMSLOTDEBUG_1 \
 	elog(DEBUG2, "heapam_getnext([%s,nkeys=%d],dir=%d) called", \
-		 RelationGetRelationName(scan->rs_rd), scan->rs_nkeys, (int) direction)
+		 RelationGetRelationName(scan->rs_scan.rs_rd), scan->rs_scan.rs_nkeys, (int) direction)
 #define HEAPAMSLOTDEBUG_2 \
 	elog(DEBUG2, "heapam_getnext returning EOS")
 #define HEAPAMSLOTDEBUG_3 \
@@ -1767,7 +1779,7 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 #endif
 
 TupleTableSlot *
-heap_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
+heap_getnextslot(TableScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
 {
 	HeapScanDesc scan = (HeapScanDesc) sscan;
 
@@ -1775,11 +1787,11 @@ heap_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *sl
 
 	HEAPAMSLOTDEBUG_1;			/* heap_getnext( info ) */
 
-	if (scan->rs_pageatatime)
+	if (scan->rs_pagescan.rs_pageatatime)
 		heapgettup_pagemode(scan, direction,
-							scan->rs_nkeys, scan->rs_key);
+							scan->rs_scan.rs_nkeys, scan->rs_scan.rs_key);
 	else
-		heapgettup(scan, direction, scan->rs_nkeys, scan->rs_key);
+		heapgettup(scan, direction, scan->rs_scan.rs_nkeys, scan->rs_scan.rs_key);
 
 	if (scan->rs_ctup.t_data == NULL)
 	{
@@ -1794,7 +1806,7 @@ heap_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *sl
 	 */
 	HEAPAMSLOTDEBUG_3;			/* heap_getnext returning tuple */
 
-	pgstat_count_heap_getnext(scan->rs_rd);
+	pgstat_count_heap_getnext(scan->rs_scan.rs_rd);
 	return ExecStoreTuple(heap_copytuple(&(scan->rs_ctup)),
 						  slot, InvalidBuffer, true);
 }
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 3e57f77611..769febcd15 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -21,7 +21,9 @@
 #include "postgres.h"
 
 #include "access/heapam.h"
+#include "access/relscan.h"
 #include "access/tableamapi.h"
+#include "pgstat.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
 #include "utils/rel.h"
@@ -298,6 +300,44 @@ heapam_form_tuple_by_datum(Datum data, Oid tableoid)
 	return heap_form_tuple_by_datum(data, tableoid);
 }
 
+static ParallelHeapScanDesc
+heapam_get_parallelheapscandesc(TableScanDesc sscan)
+{
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
+	return scan->rs_parallel;
+}
+
+static HeapPageScanDesc
+heapam_get_heappagescandesc(TableScanDesc sscan)
+{
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
+	return &scan->rs_pagescan;
+}
+
+static TableTuple
+heapam_fetch_tuple_from_offset(TableScanDesc sscan, BlockNumber blkno, OffsetNumber offset)
+{
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+	Page		dp;
+	ItemId		lp;
+
+	dp = (Page) BufferGetPage(scan->rs_scan.rs_cbuf);
+	lp = PageGetItemId(dp, offset);
+	Assert(ItemIdIsNormal(lp));
+
+	scan->rs_ctup.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lp);
+	scan->rs_ctup.t_len = ItemIdGetLength(lp);
+	scan->rs_ctup.t_tableOid = scan->rs_scan.rs_rd->rd_id;
+	ItemPointerSet(&scan->rs_ctup.t_self, blkno, offset);
+
+	pgstat_count_heap_fetch(scan->rs_scan.rs_rd);
+
+	return &(scan->rs_ctup);
+}
+
+
 Datum
 heap_tableam_handler(PG_FUNCTION_ARGS)
 {
@@ -318,6 +358,19 @@ heap_tableam_handler(PG_FUNCTION_ARGS)
 	amroutine->scan_rescan = heap_rescan;
 	amroutine->scan_update_snapshot = heap_update_snapshot;
 	amroutine->hot_search_buffer = heap_hot_search_buffer;
+	amroutine->scan_fetch_tuple_from_offset = heapam_fetch_tuple_from_offset;
+
+	/*
+	 * The following routine needs to be provided when the storage support
+	 * parallel sequential scan
+	 */
+	amroutine->scan_get_parallelheapscandesc = heapam_get_parallelheapscandesc;
+
+	/*
+	 * The following routine needs to be provided when the storage support
+	 * BitmapHeap and Sample Scans
+	 */
+	amroutine->scan_get_heappagescandesc = heapam_get_heappagescandesc;
 
 	amroutine->tuple_fetch = heapam_fetch;
 	amroutine->tuple_insert = heapam_heap_insert;
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 105631ad38..ff79ab2f86 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -478,10 +478,10 @@ systable_recheck_tuple(SysScanDesc sysscan, HeapTuple tup)
 	}
 	else
 	{
-		HeapScanDesc scan = sysscan->scan;
+		TableScanDesc scan = sysscan->scan;
 
 		Assert(IsMVCCSnapshot(scan->rs_snapshot));
-		Assert(tup == &scan->rs_ctup);
+		/* hari Assert(tup == &scan->rs_ctup); */
 		Assert(BufferIsValid(scan->rs_cbuf));
 		/* must hold a buffer lock to call HeapTupleSatisfiesVisibility */
 		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index e0a09757ed..bef1b9d34f 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -56,7 +56,7 @@ table_lock_tuple(Relation relation, ItemPointer tid, TableTuple * stuple,
  *		Caller must hold a suitable lock on the correct relation.
  * ----------------
  */
-HeapScanDesc
+TableScanDesc
 table_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan)
 {
 	Snapshot	snapshot;
@@ -69,6 +69,25 @@ table_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan)
 												true, true, true, false, false, true);
 }
 
+ParallelHeapScanDesc
+tableam_get_parallelheapscandesc(TableScanDesc sscan)
+{
+	return sscan->rs_rd->rd_tableamroutine->scan_get_parallelheapscandesc(sscan);
+}
+
+HeapPageScanDesc
+tableam_get_heappagescandesc(TableScanDesc sscan)
+{
+	/*
+	 * Planner should have already validated whether the current storage
+	 * supports Page scans are not? This function will be called only from
+	 * Bitmap Heap scan and sample scan
+	 */
+	Assert(sscan->rs_rd->rd_tableamroutine->scan_get_heappagescandesc != NULL);
+
+	return sscan->rs_rd->rd_tableamroutine->scan_get_heappagescandesc(sscan);
+}
+
 /*
  * heap_setscanlimits - restrict range of a heapscan
  *
@@ -76,7 +95,7 @@ table_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan)
  * numBlks is number of pages to scan (InvalidBlockNumber means "all")
  */
 void
-table_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
+table_setscanlimits(TableScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
 {
 	sscan->rs_rd->rd_tableamroutine->scansetlimits(sscan, startBlk, numBlks);
 }
@@ -95,18 +114,18 @@ table_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlk
  * block zero).  Both of these default to true with plain heap_beginscan.
  *
  * heap_beginscan_bm is an alternative entry point for setting up a
- * HeapScanDesc for a bitmap heap scan.  Although that scan technology is
+ * TableScanDesc for a bitmap heap scan.  Although that scan technology is
  * really quite unlike a standard seqscan, there is just enough commonality
  * to make it worth using the same data structure.
  *
  * heap_beginscan_sampling is an alternative entry point for setting up a
- * HeapScanDesc for a TABLESAMPLE scan.  As with bitmap scans, it's worth
+ * TableScanDesc for a TABLESAMPLE scan.  As with bitmap scans, it's worth
  * using the same data structure although the behavior is rather different.
  * In addition to the options offered by heap_beginscan_strat, this call
  * also allows control of whether page-mode visibility checking is used.
  * ----------------
  */
-HeapScanDesc
+TableScanDesc
 table_beginscan(Relation relation, Snapshot snapshot,
 				  int nkeys, ScanKey key)
 {
@@ -114,7 +133,7 @@ table_beginscan(Relation relation, Snapshot snapshot,
 												true, true, true, false, false, false);
 }
 
-HeapScanDesc
+TableScanDesc
 table_beginscan_catalog(Relation relation, int nkeys, ScanKey key)
 {
 	Oid			relid = RelationGetRelid(relation);
@@ -124,7 +143,7 @@ table_beginscan_catalog(Relation relation, int nkeys, ScanKey key)
 												true, true, true, false, false, true);
 }
 
-HeapScanDesc
+TableScanDesc
 table_beginscan_strat(Relation relation, Snapshot snapshot,
 						int nkeys, ScanKey key,
 						bool allow_strat, bool allow_sync)
@@ -134,7 +153,7 @@ table_beginscan_strat(Relation relation, Snapshot snapshot,
 												false, false, false);
 }
 
-HeapScanDesc
+TableScanDesc
 table_beginscan_bm(Relation relation, Snapshot snapshot,
 					 int nkeys, ScanKey key)
 {
@@ -142,7 +161,7 @@ table_beginscan_bm(Relation relation, Snapshot snapshot,
 												false, false, true, true, false, false);
 }
 
-HeapScanDesc
+TableScanDesc
 table_beginscan_sampling(Relation relation, Snapshot snapshot,
 						   int nkeys, ScanKey key,
 						   bool allow_strat, bool allow_sync, bool allow_pagemode)
@@ -157,7 +176,7 @@ table_beginscan_sampling(Relation relation, Snapshot snapshot,
  * ----------------
  */
 void
-table_rescan(HeapScanDesc scan,
+table_rescan(TableScanDesc scan,
 			   ScanKey key)
 {
 	scan->rs_rd->rd_tableamroutine->scan_rescan(scan, key, false, false, false, false);
@@ -173,7 +192,7 @@ table_rescan(HeapScanDesc scan,
  * ----------------
  */
 void
-table_rescan_set_params(HeapScanDesc scan, ScanKey key,
+table_rescan_set_params(TableScanDesc scan, ScanKey key,
 						  bool allow_strat, bool allow_sync, bool allow_pagemode)
 {
 	scan->rs_rd->rd_tableamroutine->scan_rescan(scan, key, true,
@@ -188,7 +207,7 @@ table_rescan_set_params(HeapScanDesc scan, ScanKey key,
  * ----------------
  */
 void
-table_endscan(HeapScanDesc scan)
+table_endscan(TableScanDesc scan)
 {
 	scan->rs_rd->rd_tableamroutine->scan_end(scan);
 }
@@ -201,23 +220,30 @@ table_endscan(HeapScanDesc scan)
  * ----------------
  */
 void
-table_scan_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
+table_scan_update_snapshot(TableScanDesc scan, Snapshot snapshot)
 {
 	scan->rs_rd->rd_tableamroutine->scan_update_snapshot(scan, snapshot);
 }
 
 TableTuple
-table_scan_getnext(HeapScanDesc sscan, ScanDirection direction)
+table_scan_getnext(TableScanDesc sscan, ScanDirection direction)
 {
 	return sscan->rs_rd->rd_tableamroutine->scan_getnext(sscan, direction);
 }
 
 TupleTableSlot *
-table_scan_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
+table_scan_getnextslot(TableScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
 {
 	return sscan->rs_rd->rd_tableamroutine->scan_getnextslot(sscan, direction, slot);
 }
 
+TableTuple
+table_tuple_fetch_from_offset(TableScanDesc sscan, BlockNumber blkno, OffsetNumber offset)
+{
+	return sscan->rs_rd->rd_tableamroutine->scan_fetch_tuple_from_offset(sscan, blkno, offset);
+}
+
+
 /*
  * Insert a tuple from a slot into table AM routine
  */
diff --git a/src/backend/access/tablesample/system.c b/src/backend/access/tablesample/system.c
index f888e04f40..8a9e7056eb 100644
--- a/src/backend/access/tablesample/system.c
+++ b/src/backend/access/tablesample/system.c
@@ -183,7 +183,7 @@ static BlockNumber
 system_nextsampleblock(SampleScanState *node)
 {
 	SystemSamplerData *sampler = (SystemSamplerData *) node->tsm_state;
-	HeapScanDesc scan = node->ss.ss_currentScanDesc;
+	HeapPageScanDesc scan = node->pagescan;
 	BlockNumber nextblock = sampler->nextblock;
 	uint32		hashinput[2];
 
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index a982fc641e..8bdd5643dc 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -577,7 +577,7 @@ boot_openrel(char *relname)
 	int			i;
 	struct typmap **app;
 	Relation	rel;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tup;
 
 	if (strlen(relname) >= NAMEDATALEN)
@@ -893,7 +893,7 @@ gettype(char *type)
 {
 	int			i;
 	Relation	rel;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tup;
 	struct typmap **app;
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 48922f4809..6d2ab86046 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -822,7 +822,7 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 					ScanKeyData key[2];
 					int			keycount;
 					Relation	rel;
-					HeapScanDesc scan;
+					TableScanDesc scan;
 					HeapTuple	tuple;
 
 					keycount = 0;
@@ -880,7 +880,7 @@ getRelationsInNamespace(Oid namespaceId, char relkind)
 	List	   *relations = NIL;
 	ScanKeyData key[2];
 	Relation	rel;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tuple;
 
 	ScanKeyInit(&key[0],
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index bae262dcab..8611b8b54c 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1900,7 +1900,7 @@ index_update_stats(Relation rel,
 		ReindexIsProcessingHeap(RelationRelationId))
 	{
 		/* don't assume syscache will work */
-		HeapScanDesc pg_class_scan;
+		TableScanDesc pg_class_scan;
 		ScanKeyData key[1];
 
 		ScanKeyInit(&key[0],
@@ -2213,7 +2213,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 {
 	bool		is_system_catalog;
 	bool		checking_uniqueness;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	heapTuple;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
@@ -2652,7 +2652,7 @@ IndexCheckExclusion(Relation heapRelation,
 					Relation indexRelation,
 					IndexInfo *indexInfo)
 {
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	heapTuple;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
@@ -2966,7 +2966,7 @@ validate_index_heapscan(Relation heapRelation,
 						Snapshot snapshot,
 						v_i_state *state)
 {
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	heapTuple;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 5dce6d0041..30809dfd5a 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1266,7 +1266,7 @@ check_default_allows_bound(Relation parent, Relation default_rel,
 		Snapshot	snapshot;
 		TupleDesc	tupdesc;
 		ExprContext *econtext;
-		HeapScanDesc scan;
+		TableScanDesc scan;
 		MemoryContext oldCxt;
 		TupleTableSlot *tupslot;
 
diff --git a/src/backend/catalog/pg_conversion.c b/src/backend/catalog/pg_conversion.c
index f2b6a75e1b..ee527999a0 100644
--- a/src/backend/catalog/pg_conversion.c
+++ b/src/backend/catalog/pg_conversion.c
@@ -151,7 +151,7 @@ RemoveConversionById(Oid conversionOid)
 {
 	Relation	rel;
 	HeapTuple	tuple;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	ScanKeyData scanKeyData;
 
 	ScanKeyInit(&scanKeyData,
diff --git a/src/backend/catalog/pg_db_role_setting.c b/src/backend/catalog/pg_db_role_setting.c
index 7450bf0278..06cde51d4b 100644
--- a/src/backend/catalog/pg_db_role_setting.c
+++ b/src/backend/catalog/pg_db_role_setting.c
@@ -171,7 +171,7 @@ void
 DropSetting(Oid databaseid, Oid roleid)
 {
 	Relation	relsetting;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	ScanKeyData keys[2];
 	HeapTuple	tup;
 	int			numkeys = 0;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 8277d19ec5..c73fc78155 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -314,7 +314,7 @@ GetAllTablesPublicationRelations(void)
 {
 	Relation	classRel;
 	ScanKeyData key[1];
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tuple;
 	List	   *result = NIL;
 
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index 6cab833509..3d9d8dbb42 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -377,7 +377,7 @@ void
 RemoveSubscriptionRel(Oid subid, Oid relid)
 {
 	Relation	rel;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	ScanKeyData skey[2];
 	HeapTuple	tup;
 	int			nkeys = 0;
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index eda0410282..00eea8fc37 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -748,7 +748,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	Datum	   *values;
 	bool	   *isnull;
 	IndexScanDesc indexScan;
-	HeapScanDesc heapScan;
+	TableScanDesc heapScan;
 	bool		use_wal;
 	bool		is_system_catalog;
 	TransactionId OldestXmin;
@@ -1667,7 +1667,7 @@ static List *
 get_tables_to_cluster(MemoryContext cluster_context)
 {
 	Relation	indRelation;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	ScanKeyData entry;
 	HeapTuple	indexTuple;
 	Form_pg_index index;
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index a946dcef4b..a40636cca7 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2029,7 +2029,7 @@ CopyTo(CopyState cstate)
 	{
 		Datum	   *values;
 		bool	   *nulls;
-		HeapScanDesc scandesc;
+		TableScanDesc scandesc;
 		HeapTuple	tuple;
 
 		values = (Datum *) palloc(num_phys_attrs * sizeof(Datum));
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 0667516ff1..4338a7d7ce 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -99,7 +99,7 @@ static int	errdetail_busy_db(int notherbackends, int npreparedxacts);
 Oid
 createdb(ParseState *pstate, const CreatedbStmt *stmt)
 {
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	Relation	rel;
 	Oid			src_dboid;
 	Oid			src_owner;
@@ -1872,7 +1872,7 @@ static void
 remove_dbtablespaces(Oid db_id)
 {
 	Relation	rel;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tuple;
 
 	rel = heap_open(TableSpaceRelationId, AccessShareLock);
@@ -1939,7 +1939,7 @@ check_db_file_conflict(Oid db_id)
 {
 	bool		result = false;
 	Relation	rel;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tuple;
 
 	rel = heap_open(TableSpaceRelationId, AccessShareLock);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index e3c8b9166c..ad5ee3576a 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1894,7 +1894,7 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 {
 	Oid			objectOid;
 	Relation	relationRelation;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	ScanKeyData scan_keys[1];
 	HeapTuple	tuple;
 	MemoryContext private_context;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index fa321365a8..91d0b094fe 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4491,7 +4491,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		bool	   *isnull;
 		TupleTableSlot *oldslot;
 		TupleTableSlot *newslot;
-		HeapScanDesc scan;
+		TableScanDesc scan;
 		HeapTuple	tuple;
 		MemoryContext oldCxt;
 		List	   *dropped_attrs = NIL;
@@ -5064,7 +5064,7 @@ find_typed_table_dependencies(Oid typeOid, const char *typeName, DropBehavior be
 {
 	Relation	classRel;
 	ScanKeyData key[1];
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tuple;
 	List	   *result = NIL;
 
@@ -8158,7 +8158,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	Expr	   *origexpr;
 	ExprState  *exprstate;
 	TupleDesc	tupdesc;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tuple;
 	ExprContext *econtext;
 	MemoryContext oldcxt;
@@ -8241,7 +8241,7 @@ validateForeignKeyConstraint(char *conname,
 							 Oid pkindOid,
 							 Oid constraintOid)
 {
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tuple;
 	Trigger		trig;
 	Snapshot	snapshot;
@@ -10750,7 +10750,7 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 	ListCell   *l;
 	ScanKeyData key[1];
 	Relation	rel;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tuple;
 	Oid			orig_tablespaceoid;
 	Oid			new_tablespaceoid;
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 128d38758b..99fc5c0b87 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -402,7 +402,7 @@ DropTableSpace(DropTableSpaceStmt *stmt)
 {
 #ifdef HAVE_SYMLINK
 	char	   *tablespacename = stmt->tablespacename;
-	HeapScanDesc scandesc;
+	TableScanDesc scandesc;
 	Relation	rel;
 	HeapTuple	tuple;
 	ScanKeyData entry[1];
@@ -913,7 +913,7 @@ RenameTableSpace(const char *oldname, const char *newname)
 	Oid			tspId;
 	Relation	rel;
 	ScanKeyData entry[1];
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tup;
 	HeapTuple	newtuple;
 	Form_pg_tablespace newform;
@@ -988,7 +988,7 @@ AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt)
 {
 	Relation	rel;
 	ScanKeyData entry[1];
-	HeapScanDesc scandesc;
+	TableScanDesc scandesc;
 	HeapTuple	tup;
 	Oid			tablespaceoid;
 	Datum		datum;
@@ -1382,7 +1382,7 @@ get_tablespace_oid(const char *tablespacename, bool missing_ok)
 {
 	Oid			result;
 	Relation	rel;
-	HeapScanDesc scandesc;
+	TableScanDesc scandesc;
 	HeapTuple	tuple;
 	ScanKeyData entry[1];
 
@@ -1428,7 +1428,7 @@ get_tablespace_name(Oid spc_oid)
 {
 	char	   *result;
 	Relation	rel;
-	HeapScanDesc scandesc;
+	TableScanDesc scandesc;
 	HeapTuple	tuple;
 	ScanKeyData entry[1];
 
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 4a0db17815..92062b3ac3 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2382,7 +2382,7 @@ AlterDomainNotNull(List *names, bool notNull)
 			RelToCheck *rtc = (RelToCheck *) lfirst(rt);
 			Relation	testrel = rtc->rel;
 			TupleDesc	tupdesc = RelationGetDescr(testrel);
-			HeapScanDesc scan;
+			TableScanDesc scan;
 			HeapTuple	tuple;
 			Snapshot	snapshot;
 
@@ -2778,7 +2778,7 @@ validateDomainConstraint(Oid domainoid, char *ccbin)
 		RelToCheck *rtc = (RelToCheck *) lfirst(rt);
 		Relation	testrel = rtc->rel;
 		TupleDesc	tupdesc = RelationGetDescr(testrel);
-		HeapScanDesc scan;
+		TableScanDesc scan;
 		HeapTuple	tuple;
 		Snapshot	snapshot;
 
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 24a2d258f4..5aebfbdf6c 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -529,7 +529,7 @@ get_all_vacuum_rels(void)
 {
 	List	   *vacrels = NIL;
 	Relation	pgclass;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tuple;
 
 	pgclass = heap_open(RelationRelationId, AccessShareLock);
@@ -1184,7 +1184,7 @@ vac_truncate_clog(TransactionId frozenXID,
 {
 	TransactionId nextXID = ReadNewTransactionId();
 	Relation	relation;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tuple;
 	Oid			oldestxid_datoid;
 	Oid			minmulti_datoid;
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index 5187e319ca..4218f63889 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -55,14 +55,14 @@
 
 
 static TupleTableSlot *BitmapHeapNext(BitmapHeapScanState *node);
-static void bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres);
+static void bitgetpage(BitmapHeapScanState *node, TBMIterateResult *tbmres);
 static inline void BitmapDoneInitializingSharedState(
 								  ParallelBitmapHeapState *pstate);
 static inline void BitmapAdjustPrefetchIterator(BitmapHeapScanState *node,
 							 TBMIterateResult *tbmres);
 static inline void BitmapAdjustPrefetchTarget(BitmapHeapScanState *node);
 static inline void BitmapPrefetch(BitmapHeapScanState *node,
-			   HeapScanDesc scan);
+			   TableScanDesc scan);
 static bool BitmapShouldInitializeSharedState(
 								  ParallelBitmapHeapState *pstate);
 
@@ -77,7 +77,8 @@ static TupleTableSlot *
 BitmapHeapNext(BitmapHeapScanState *node)
 {
 	ExprContext *econtext;
-	HeapScanDesc scan;
+	TableScanDesc scan;
+	HeapPageScanDesc pagescan;
 	TIDBitmap  *tbm;
 	TBMIterator *tbmiterator = NULL;
 	TBMSharedIterator *shared_tbmiterator = NULL;
@@ -93,6 +94,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 	econtext = node->ss.ps.ps_ExprContext;
 	slot = node->ss.ss_ScanTupleSlot;
 	scan = node->ss.ss_currentScanDesc;
+	pagescan = node->pagescan;
 	tbm = node->tbm;
 	if (pstate == NULL)
 		tbmiterator = node->tbmiterator;
@@ -192,8 +194,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 
 	for (;;)
 	{
-		Page		dp;
-		ItemId		lp;
+		TableTuple tuple;
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -220,7 +221,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 			 * least AccessShareLock on the table before performing any of the
 			 * indexscans, but let's be safe.)
 			 */
-			if (tbmres->blockno >= scan->rs_nblocks)
+			if (tbmres->blockno >= pagescan->rs_nblocks)
 			{
 				node->tbmres = tbmres = NULL;
 				continue;
@@ -243,14 +244,14 @@ BitmapHeapNext(BitmapHeapScanState *node)
 				 * The number of tuples on this page is put into
 				 * scan->rs_ntuples; note we don't fill scan->rs_vistuples.
 				 */
-				scan->rs_ntuples = tbmres->ntuples;
+				pagescan->rs_ntuples = tbmres->ntuples;
 			}
 			else
 			{
 				/*
 				 * Fetch the current heap page and identify candidate tuples.
 				 */
-				bitgetpage(scan, tbmres);
+				bitgetpage(node, tbmres);
 			}
 
 			if (tbmres->ntuples >= 0)
@@ -261,7 +262,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 			/*
 			 * Set rs_cindex to first slot to examine
 			 */
-			scan->rs_cindex = 0;
+			pagescan->rs_cindex = 0;
 
 			/* Adjust the prefetch target */
 			BitmapAdjustPrefetchTarget(node);
@@ -271,7 +272,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 			/*
 			 * Continuing in previously obtained page; advance rs_cindex
 			 */
-			scan->rs_cindex++;
+			pagescan->rs_cindex++;
 
 #ifdef USE_PREFETCH
 
@@ -298,7 +299,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 		/*
 		 * Out of range?  If so, nothing more to look at on this page
 		 */
-		if (scan->rs_cindex < 0 || scan->rs_cindex >= scan->rs_ntuples)
+		if (pagescan->rs_cindex < 0 || pagescan->rs_cindex >= pagescan->rs_ntuples)
 		{
 			node->tbmres = tbmres = NULL;
 			continue;
@@ -325,23 +326,14 @@ BitmapHeapNext(BitmapHeapScanState *node)
 			/*
 			 * Okay to fetch the tuple.
 			 */
-			targoffset = scan->rs_vistuples[scan->rs_cindex];
-			dp = (Page) BufferGetPage(scan->rs_cbuf);
-			lp = PageGetItemId(dp, targoffset);
-			Assert(ItemIdIsNormal(lp));
-
-			scan->rs_ctup.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lp);
-			scan->rs_ctup.t_len = ItemIdGetLength(lp);
-			scan->rs_ctup.t_tableOid = scan->rs_rd->rd_id;
-			ItemPointerSet(&scan->rs_ctup.t_self, tbmres->blockno, targoffset);
-
-			pgstat_count_heap_fetch(scan->rs_rd);
+			targoffset = pagescan->rs_vistuples[pagescan->rs_cindex];
+			tuple = table_tuple_fetch_from_offset(scan, tbmres->blockno, targoffset);
 
 			/*
 			 * Set up the result slot to point to this tuple.  Note that the
 			 * slot acquires a pin on the buffer.
 			 */
-			ExecStoreTuple(&scan->rs_ctup,
+			ExecStoreTuple(tuple,
 						   slot,
 						   scan->rs_cbuf,
 						   false);
@@ -383,8 +375,10 @@ BitmapHeapNext(BitmapHeapScanState *node)
  * interesting according to the bitmap, and visible according to the snapshot.
  */
 static void
-bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
+bitgetpage(BitmapHeapScanState *node, TBMIterateResult *tbmres)
 {
+	TableScanDesc scan = node->ss.ss_currentScanDesc;
+	HeapPageScanDesc pagescan = node->pagescan;
 	BlockNumber page = tbmres->blockno;
 	Buffer		buffer;
 	Snapshot	snapshot;
@@ -393,7 +387,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 	/*
 	 * Acquire pin on the target heap page, trading in any pin we held before.
 	 */
-	Assert(page < scan->rs_nblocks);
+	Assert(page < pagescan->rs_nblocks);
 
 	scan->rs_cbuf = ReleaseAndReadBuffer(scan->rs_cbuf,
 										 scan->rs_rd,
@@ -436,7 +430,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 			ItemPointerSet(&tid, page, offnum);
 			if (table_hot_search_buffer(&tid, scan->rs_rd, buffer, snapshot,
 										  &heapTuple, NULL, true))
-				scan->rs_vistuples[ntup++] = ItemPointerGetOffsetNumber(&tid);
+				pagescan->rs_vistuples[ntup++] = ItemPointerGetOffsetNumber(&tid);
 		}
 	}
 	else
@@ -452,23 +446,21 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 		for (offnum = FirstOffsetNumber; offnum <= maxoff; offnum = OffsetNumberNext(offnum))
 		{
 			ItemId		lp;
-			HeapTupleData loctup;
+			TableTuple	loctup;
 			bool		valid;
 
 			lp = PageGetItemId(dp, offnum);
 			if (!ItemIdIsNormal(lp))
 				continue;
-			loctup.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lp);
-			loctup.t_len = ItemIdGetLength(lp);
-			loctup.t_tableOid = scan->rs_rd->rd_id;
-			ItemPointerSet(&loctup.t_self, page, offnum);
-			valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_tableamroutine, &loctup, snapshot, buffer);
+
+			loctup = table_tuple_fetch_from_offset(scan, page, offnum);
+			valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_tableamroutine, loctup, snapshot, buffer);
 			if (valid)
 			{
-				scan->rs_vistuples[ntup++] = offnum;
-				PredicateLockTuple(scan->rs_rd, &loctup, snapshot);
+				pagescan->rs_vistuples[ntup++] = offnum;
+				PredicateLockTuple(scan->rs_rd, loctup, snapshot);
 			}
-			CheckForSerializableConflictOut(valid, scan->rs_rd, &loctup,
+			CheckForSerializableConflictOut(valid, scan->rs_rd, loctup,
 											buffer, snapshot);
 		}
 	}
@@ -476,7 +468,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
 
 	Assert(ntup <= MaxHeapTuplesPerPage);
-	scan->rs_ntuples = ntup;
+	pagescan->rs_ntuples = ntup;
 }
 
 /*
@@ -602,7 +594,7 @@ BitmapAdjustPrefetchTarget(BitmapHeapScanState *node)
  * BitmapPrefetch - Prefetch, if prefetch_pages are behind prefetch_target
  */
 static inline void
-BitmapPrefetch(BitmapHeapScanState *node, HeapScanDesc scan)
+BitmapPrefetch(BitmapHeapScanState *node, TableScanDesc scan)
 {
 #ifdef USE_PREFETCH
 	ParallelBitmapHeapState *pstate = node->pstate;
@@ -793,7 +785,7 @@ void
 ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 {
 	Relation	relation;
-	HeapScanDesc scanDesc;
+	TableScanDesc scanDesc;
 
 	/*
 	 * extract information from the node
@@ -958,6 +950,8 @@ ExecInitBitmapHeapScan(BitmapHeapScan *node, EState *estate, int eflags)
 															0,
 															NULL);
 
+	scanstate->pagescan = tableam_get_heappagescandesc(scanstate->ss.ss_currentScanDesc);
+
 	/*
 	 * get the scan type from the relation descriptor.
 	 */
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index d54b27196c..e171b5b13d 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -31,9 +31,8 @@ static TupleTableSlot *SampleNext(SampleScanState *node);
 static void tablesample_init(SampleScanState *scanstate);
 static TableTuple tablesample_getnext(SampleScanState *scanstate);
 static bool SampleTupleVisible(TableTuple tuple, OffsetNumber tupoffset,
-				   HeapScanDesc scan);
+					SampleScanState *scanstate);
 
-/* hari */
 
 /* ----------------------------------------------------------------
  *						Scan Support
@@ -357,6 +356,7 @@ tablesample_init(SampleScanState *scanstate)
 									   scanstate->use_bulkread,
 									   allow_sync,
 									   scanstate->use_pagemode);
+		scanstate->pagescan = tableam_get_heappagescandesc(scanstate->ss.ss_currentScanDesc);
 	}
 	else
 	{
@@ -382,10 +382,11 @@ static TableTuple
 tablesample_getnext(SampleScanState *scanstate)
 {
 	TsmRoutine *tsm = scanstate->tsmroutine;
-	HeapScanDesc scan = scanstate->ss.ss_currentScanDesc;
-	HeapTuple	tuple = &(scan->rs_ctup);
+	TableScanDesc scan = scanstate->ss.ss_currentScanDesc;
+	HeapPageScanDesc pagescan = scanstate->pagescan;
+	TableTuple tuple;
 	Snapshot	snapshot = scan->rs_snapshot;
-	bool		pagemode = scan->rs_pageatatime;
+	bool		pagemode = pagescan->rs_pageatatime;
 	BlockNumber blockno;
 	Page		page;
 	bool		all_visible;
@@ -396,10 +397,9 @@ tablesample_getnext(SampleScanState *scanstate)
 		/*
 		 * return null immediately if relation is empty
 		 */
-		if (scan->rs_nblocks == 0)
+		if (pagescan->rs_nblocks == 0)
 		{
 			Assert(!BufferIsValid(scan->rs_cbuf));
-			tuple->t_data = NULL;
 			return NULL;
 		}
 		if (tsm->NextSampleBlock)
@@ -407,13 +407,12 @@ tablesample_getnext(SampleScanState *scanstate)
 			blockno = tsm->NextSampleBlock(scanstate);
 			if (!BlockNumberIsValid(blockno))
 			{
-				tuple->t_data = NULL;
 				return NULL;
 			}
 		}
 		else
-			blockno = scan->rs_startblock;
-		Assert(blockno < scan->rs_nblocks);
+			blockno = pagescan->rs_startblock;
+		Assert(blockno < pagescan->rs_nblocks);
 		heapgetpage(scan, blockno);
 		scan->rs_inited = true;
 	}
@@ -456,14 +455,12 @@ tablesample_getnext(SampleScanState *scanstate)
 			if (!ItemIdIsNormal(itemid))
 				continue;
 
-			tuple->t_data = (HeapTupleHeader) PageGetItem(page, itemid);
-			tuple->t_len = ItemIdGetLength(itemid);
-			ItemPointerSet(&(tuple->t_self), blockno, tupoffset);
+			tuple = table_tuple_fetch_from_offset(scan, blockno, tupoffset);
 
 			if (all_visible)
 				visible = true;
 			else
-				visible = SampleTupleVisible(tuple, tupoffset, scan);
+				visible = SampleTupleVisible(tuple, tupoffset, scanstate);
 
 			/* in pagemode, heapgetpage did this for us */
 			if (!pagemode)
@@ -494,14 +491,14 @@ tablesample_getnext(SampleScanState *scanstate)
 		if (tsm->NextSampleBlock)
 		{
 			blockno = tsm->NextSampleBlock(scanstate);
-			Assert(!scan->rs_syncscan);
+			Assert(!pagescan->rs_syncscan);
 			finished = !BlockNumberIsValid(blockno);
 		}
 		else
 		{
 			/* Without NextSampleBlock, just do a plain forward seqscan. */
 			blockno++;
-			if (blockno >= scan->rs_nblocks)
+			if (blockno >= pagescan->rs_nblocks)
 				blockno = 0;
 
 			/*
@@ -514,10 +511,10 @@ tablesample_getnext(SampleScanState *scanstate)
 			 * a little bit backwards on every invocation, which is confusing.
 			 * We don't guarantee any specific ordering in general, though.
 			 */
-			if (scan->rs_syncscan)
+			if (pagescan->rs_syncscan)
 				ss_report_location(scan->rs_rd, blockno);
 
-			finished = (blockno == scan->rs_startblock);
+			finished = (blockno == pagescan->rs_startblock);
 		}
 
 		/*
@@ -529,12 +526,11 @@ tablesample_getnext(SampleScanState *scanstate)
 				ReleaseBuffer(scan->rs_cbuf);
 			scan->rs_cbuf = InvalidBuffer;
 			scan->rs_cblock = InvalidBlockNumber;
-			tuple->t_data = NULL;
 			scan->rs_inited = false;
 			return NULL;
 		}
 
-		Assert(blockno < scan->rs_nblocks);
+		Assert(blockno < pagescan->rs_nblocks);
 		heapgetpage(scan, blockno);
 
 		/* Re-establish state for new page */
@@ -549,16 +545,19 @@ tablesample_getnext(SampleScanState *scanstate)
 	/* Count successfully-fetched tuples as heap fetches */
 	pgstat_count_heap_getnext(scan->rs_rd);
 
-	return &(scan->rs_ctup);
+	return tuple;
 }
 
 /*
  * Check visibility of the tuple.
  */
 static bool
-SampleTupleVisible(TableTuple tuple, OffsetNumber tupoffset, HeapScanDesc scan) //hari
+SampleTupleVisible(TableTuple tuple, OffsetNumber tupoffset, SampleScanState *scanstate)
 {
-	if (scan->rs_pageatatime)
+	TableScanDesc scan = scanstate->ss.ss_currentScanDesc;
+	HeapPageScanDesc pagescan = scanstate->pagescan;
+
+	if (pagescan->rs_pageatatime)
 	{
 		/*
 		 * In pageatatime mode, heapgetpage() already did visibility checks,
@@ -570,12 +569,12 @@ SampleTupleVisible(TableTuple tuple, OffsetNumber tupoffset, HeapScanDesc scan)
 		 * gain to justify the restriction.
 		 */
 		int			start = 0,
-					end = scan->rs_ntuples - 1;
+					end = pagescan->rs_ntuples - 1;
 
 		while (start <= end)
 		{
 			int			mid = (start + end) / 2;
-			OffsetNumber curoffset = scan->rs_vistuples[mid];
+			OffsetNumber curoffset = pagescan->rs_vistuples[mid];
 
 			if (tupoffset == curoffset)
 				return true;
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index f6903ac962..b2f15a0d6f 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -317,9 +317,10 @@ void
 ExecSeqScanReInitializeDSM(SeqScanState *node,
 						   ParallelContext *pcxt)
 {
-	HeapScanDesc scan = node->ss.ss_currentScanDesc;
+	ParallelHeapScanDesc pscan;
 
-	heap_parallelscan_reinitialize(scan->rs_parallel);
+	pscan = tableam_get_parallelheapscandesc(node->ss.ss_currentScanDesc);
+	heap_parallelscan_reinitialize(pscan);
 }
 
 /* ----------------------------------------------------------------
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 8c60b35068..f06c16b84e 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -21,6 +21,7 @@
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/nbtree.h"
+#include "access/tableamapi.h"
 #include "access/sysattr.h"
 #include "access/transam.h"
 #include "access/xlog.h"
@@ -253,7 +254,8 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 			info->amsearchnulls = amroutine->amsearchnulls;
 			info->amcanparallel = amroutine->amcanparallel;
 			info->amhasgettuple = (amroutine->amgettuple != NULL);
-			info->amhasgetbitmap = (amroutine->amgetbitmap != NULL);
+			info->amhasgetbitmap = ((amroutine->amgetbitmap != NULL)
+									&& (relation->rd_tableamroutine->scan_get_heappagescandesc != NULL));
 			info->amcostestimate = amroutine->amcostestimate;
 			Assert(info->amcostestimate != NULL);
 
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index d697289c0d..5745d0f884 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1865,7 +1865,7 @@ get_database_list(void)
 {
 	List	   *dblist = NIL;
 	Relation	rel;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tup;
 	MemoryContext resultcxt;
 
@@ -1931,7 +1931,7 @@ do_autovacuum(void)
 {
 	Relation	classRel;
 	HeapTuple	tuple;
-	HeapScanDesc relScan;
+	TableScanDesc relScan;
 	Form_pg_database dbForm;
 	List	   *table_oids = NIL;
 	List	   *orphan_oids = NIL;
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 7d683ad49a..28b5d87d1e 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -1207,7 +1207,7 @@ pgstat_collect_oids(Oid catalogid)
 	HTAB	   *htab;
 	HASHCTL		hash_ctl;
 	Relation	rel;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tup;
 	Snapshot	snapshot;
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 6837cc4be2..b8cdbb36c7 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -107,7 +107,7 @@ get_subscription_list(void)
 {
 	List	   *res = NIL;
 	Relation	rel;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tup;
 	MemoryContext resultcxt;
 
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 8c9e506926..d2a0c9b44f 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -420,7 +420,7 @@ DefineQueryRewrite(const char *rulename,
 		if (event_relation->rd_rel->relkind != RELKIND_VIEW &&
 			event_relation->rd_rel->relkind != RELKIND_MATVIEW)
 		{
-			HeapScanDesc scanDesc;
+			TableScanDesc scanDesc;
 			Snapshot	snapshot;
 
 			if (event_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 8d1663d6a4..368af41be8 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -1208,7 +1208,7 @@ static bool
 ThereIsAtLeastOneRole(void)
 {
 	Relation	pg_authid_rel;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	bool		result;
 
 	pg_authid_rel = heap_open(AuthIdRelationId, AccessShareLock);
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index a1baaa96e1..8d1263d0ae 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -98,6 +98,8 @@ extern Relation heap_openrv_extended(const RangeVar *relation,
 #define heap_close(r,l)  relation_close(r,l)
 
 /* struct definitions appear in relscan.h */
+typedef struct HeapPageScanDescData *HeapPageScanDesc;
+typedef struct TableScanDescData *TableScanDesc;
 typedef struct HeapScanDescData *HeapScanDesc;
 typedef struct ParallelHeapScanDescData *ParallelHeapScanDesc;
 
@@ -107,7 +109,7 @@ typedef struct ParallelHeapScanDescData *ParallelHeapScanDesc;
  */
 #define HeapScanIsValid(scan) PointerIsValid(scan)
 
-extern HeapScanDesc heap_beginscan(Relation relation, Snapshot snapshot,
+extern TableScanDesc heap_beginscan(Relation relation, Snapshot snapshot,
 			   int nkeys, ScanKey key,
 			   ParallelHeapScanDesc parallel_scan,
 			   bool allow_strat,
@@ -116,22 +118,22 @@ extern HeapScanDesc heap_beginscan(Relation relation, Snapshot snapshot,
 			   bool is_bitmapscan,
 			   bool is_samplescan,
 			   bool temp_snap);
-extern void heap_setscanlimits(HeapScanDesc scan, BlockNumber startBlk,
+extern void heap_setscanlimits(TableScanDesc scan, BlockNumber startBlk,
 				   BlockNumber endBlk);
-extern void heapgetpage(HeapScanDesc scan, BlockNumber page);
-extern void heap_rescan(HeapScanDesc scan, ScanKey key, bool set_params,
+extern void heapgetpage(TableScanDesc scan, BlockNumber page);
+extern void heap_rescan(TableScanDesc scan, ScanKey key, bool set_params,
 			bool allow_strat, bool allow_sync, bool allow_pagemode);
-extern void heap_rescan_set_params(HeapScanDesc scan, ScanKey key,
+extern void heap_rescan_set_params(TableScanDesc scan, ScanKey key,
 					   bool allow_strat, bool allow_sync, bool allow_pagemode);
-extern void heap_endscan(HeapScanDesc scan);
-extern TableTuple heap_getnext(HeapScanDesc scan, ScanDirection direction);
-extern TupleTableSlot *heap_getnextslot(HeapScanDesc sscan, ScanDirection direction,
+extern void heap_endscan(TableScanDesc scan);
+extern TableTuple heap_getnext(TableScanDesc scan, ScanDirection direction);
+extern TupleTableSlot *heap_getnextslot(TableScanDesc sscan, ScanDirection direction,
 				 TupleTableSlot *slot);
 extern Size heap_parallelscan_estimate(Snapshot snapshot);
 extern void heap_parallelscan_initialize(ParallelHeapScanDesc target,
 							 Relation relation, Snapshot snapshot);
 extern void heap_parallelscan_reinitialize(ParallelHeapScanDesc parallel_scan);
-extern HeapScanDesc heap_beginscan_parallel(Relation, ParallelHeapScanDesc);
+extern TableScanDesc heap_beginscan_parallel(Relation, ParallelHeapScanDesc);
 
 extern bool heap_fetch(Relation relation, ItemPointer tid, Snapshot snapshot,
 		   HeapTuple tuple, Buffer *userbuf, bool keep_buf,
@@ -181,7 +183,7 @@ extern void simple_heap_update(Relation relation, ItemPointer otid,
 				   HeapTuple tup);
 
 extern void heap_sync(Relation relation);
-extern void heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot);
+extern void heap_update_snapshot(TableScanDesc scan, Snapshot snapshot);
 
 /* in heap/pruneheap.c */
 extern void heap_page_prune_opt(Relation relation, Buffer buffer);
diff --git a/src/include/access/relscan.h b/src/include/access/relscan.h
index 9c603ca637..06474273e9 100644
--- a/src/include/access/relscan.h
+++ b/src/include/access/relscan.h
@@ -16,6 +16,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/tableam.h"
 #include "access/htup_details.h"
 #include "access/itup.h"
 #include "access/tupdesc.h"
@@ -42,40 +43,54 @@ typedef struct ParallelHeapScanDescData
 	char		phs_snapshot_data[FLEXIBLE_ARRAY_MEMBER];
 }			ParallelHeapScanDescData;
 
-typedef struct HeapScanDescData
+typedef struct TableScanDescData
 {
 	/* scan parameters */
 	Relation	rs_rd;			/* heap relation descriptor */
 	Snapshot	rs_snapshot;	/* snapshot to see */
 	int			rs_nkeys;		/* number of scan keys */
 	ScanKey		rs_key;			/* array of scan key descriptors */
-	bool		rs_bitmapscan;	/* true if this is really a bitmap scan */
-	bool		rs_samplescan;	/* true if this is really a sample scan */
+
+	/* scan current state */
+	bool		rs_inited;		/* false = scan not init'd yet */
+	BlockNumber rs_cblock;		/* current block # in scan, if any */
+	Buffer		rs_cbuf;		/* current buffer in scan, if any */
+}			TableScanDescData;
+
+typedef struct HeapPageScanDescData
+{
 	bool		rs_pageatatime; /* verify visibility page-at-a-time? */
-	bool		rs_allow_strat; /* allow or disallow use of access strategy */
-	bool		rs_allow_sync;	/* allow or disallow use of syncscan */
-	bool		rs_temp_snap;	/* unregister snapshot at scan end? */
 
 	/* state set up at initscan time */
 	BlockNumber rs_nblocks;		/* total number of blocks in rel */
 	BlockNumber rs_startblock;	/* block # to start at */
 	BlockNumber rs_numblocks;	/* max number of blocks to scan */
+
 	/* rs_numblocks is usually InvalidBlockNumber, meaning "scan whole rel" */
 	BufferAccessStrategy rs_strategy;	/* access strategy for reads */
 	bool		rs_syncscan;	/* report location to syncscan logic? */
 
-	/* scan current state */
-	bool		rs_inited;		/* false = scan not init'd yet */
-	HeapTupleData rs_ctup;		/* current tuple in scan, if any */
-	BlockNumber rs_cblock;		/* current block # in scan, if any */
-	Buffer		rs_cbuf;		/* current buffer in scan, if any */
-	/* NB: if rs_cbuf is not InvalidBuffer, we hold a pin on that buffer */
-	ParallelHeapScanDesc rs_parallel;	/* parallel scan information */
-
 	/* these fields only used in page-at-a-time mode and for bitmap scans */
 	int			rs_cindex;		/* current tuple's index in vistuples */
 	int			rs_ntuples;		/* number of visible tuples on page */
 	OffsetNumber rs_vistuples[MaxHeapTuplesPerPage];	/* their offsets */
+}			HeapPageScanDescData;
+
+typedef struct HeapScanDescData
+{
+	/* scan parameters */
+	TableScanDescData rs_scan;	/* */
+	HeapPageScanDescData rs_pagescan;
+	bool		rs_bitmapscan;	/* true if this is really a bitmap scan */
+	bool		rs_samplescan;	/* true if this is really a sample scan */
+	bool		rs_allow_strat; /* allow or disallow use of access strategy */
+	bool		rs_allow_sync;	/* allow or disallow use of syncscan */
+	bool		rs_temp_snap;	/* unregister snapshot at scan end? */
+
+	HeapTupleData rs_ctup;		/* current tuple in scan, if any */
+
+	/* NB: if rs_cbuf is not InvalidBuffer, we hold a pin on that buffer */
+	ParallelHeapScanDesc rs_parallel;	/* parallel scan information */
 }			HeapScanDescData;
 
 /*
@@ -149,12 +164,12 @@ typedef struct ParallelIndexScanDescData
 	char		ps_snapshot_data[FLEXIBLE_ARRAY_MEMBER];
 }			ParallelIndexScanDescData;
 
-/* Struct for heap-or-index scans of system tables */
+/* Struct for storage-or-index scans of system tables */
 typedef struct SysScanDescData
 {
 	Relation	heap_rel;		/* catalog being scanned */
 	Relation	irel;			/* NULL if doing heap scan */
-	HeapScanDesc scan;			/* only valid in heap-scan case */
+	TableScanDesc scan;		/* only valid in storage-scan case */
 	IndexScanDesc iscan;		/* only valid in index-scan case */
 	Snapshot	snapshot;		/* snapshot to unregister at end of scan */
 }			SysScanDescData;
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 4fd353dd05..9be26fb86d 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -42,29 +42,31 @@ typedef List *(*InsertIndexTuples) (TupleTableSlot *slot, EState *estate, bool n
 /* Function pointer to let the index tuple delete from storage am */
 typedef void (*DeleteIndexTuples) (Relation rel, ItemPointer tid, TransactionId old_xmin);
 
-extern HeapScanDesc table_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan);
-
-extern void table_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
-extern HeapScanDesc table_beginscan(Relation relation, Snapshot snapshot,
+extern TableScanDesc table_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan);
+extern ParallelHeapScanDesc tableam_get_parallelheapscandesc(TableScanDesc sscan);
+extern HeapPageScanDesc tableam_get_heappagescandesc(TableScanDesc sscan);
+extern void table_setscanlimits(TableScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
+extern TableScanDesc table_beginscan(Relation relation, Snapshot snapshot,
 				  int nkeys, ScanKey key);
-extern HeapScanDesc table_beginscan_catalog(Relation relation, int nkeys, ScanKey key);
-extern HeapScanDesc table_beginscan_strat(Relation relation, Snapshot snapshot,
+extern TableScanDesc table_beginscan_catalog(Relation relation, int nkeys, ScanKey key);
+extern TableScanDesc table_beginscan_strat(Relation relation, Snapshot snapshot,
 						int nkeys, ScanKey key,
 						bool allow_strat, bool allow_sync);
-extern HeapScanDesc table_beginscan_bm(Relation relation, Snapshot snapshot,
+extern TableScanDesc table_beginscan_bm(Relation relation, Snapshot snapshot,
 					 int nkeys, ScanKey key);
-extern HeapScanDesc table_beginscan_sampling(Relation relation, Snapshot snapshot,
+extern TableScanDesc table_beginscan_sampling(Relation relation, Snapshot snapshot,
 						   int nkeys, ScanKey key,
 						   bool allow_strat, bool allow_sync, bool allow_pagemode);
 
-extern void table_endscan(HeapScanDesc scan);
-extern void table_rescan(HeapScanDesc scan, ScanKey key);
-extern void table_rescan_set_params(HeapScanDesc scan, ScanKey key,
+extern void table_endscan(TableScanDesc scan);
+extern void table_rescan(TableScanDesc scan, ScanKey key);
+extern void table_rescan_set_params(TableScanDesc scan, ScanKey key,
 						  bool allow_strat, bool allow_sync, bool allow_pagemode);
-extern void table_scan_update_snapshot(HeapScanDesc scan, Snapshot snapshot);
+extern void table_scan_update_snapshot(TableScanDesc scan, Snapshot snapshot);
 
-extern TableTuple table_scan_getnext(HeapScanDesc sscan, ScanDirection direction);
-extern TupleTableSlot *table_scan_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *slot);
+extern TableTuple table_scan_getnext(TableScanDesc sscan, ScanDirection direction);
+extern TupleTableSlot *table_scan_getnextslot(TableScanDesc sscan, ScanDirection direction, TupleTableSlot *slot);
+extern TableTuple table_tuple_fetch_from_offset(TableScanDesc sscan, BlockNumber blkno, OffsetNumber offset);
 
 extern void storage_get_latest_tid(Relation relation,
 					   Snapshot snapshot,
diff --git a/src/include/access/tableam_common.h b/src/include/access/tableam_common.h
index a473c67e86..26541abbde 100644
--- a/src/include/access/tableam_common.h
+++ b/src/include/access/tableam_common.h
@@ -28,7 +28,6 @@
 
 /* A physical tuple coming from a table AM scan */
 typedef void *TableTuple;
-typedef void *TableScanDesc;
 
 /* Result codes for HeapTupleSatisfiesVacuum */
 typedef enum
diff --git a/src/include/access/tableamapi.h b/src/include/access/tableamapi.h
index 8acbde32c3..b32a4bff83 100644
--- a/src/include/access/tableamapi.h
+++ b/src/include/access/tableamapi.h
@@ -82,7 +82,7 @@ typedef TableTuple(*TupleFromDatum_function) (Datum data, Oid tableoid);
 typedef void (*RelationSync_function) (Relation relation);
 
 
-typedef HeapScanDesc (*ScanBegin_function) (Relation relation,
+typedef TableScanDesc (*ScanBegin_function) (Relation relation,
 											Snapshot snapshot,
 											int nkeys, ScanKey key,
 											ParallelHeapScanDesc parallel_scan,
@@ -92,22 +92,29 @@ typedef HeapScanDesc (*ScanBegin_function) (Relation relation,
 											bool is_bitmapscan,
 											bool is_samplescan,
 											bool temp_snap);
-typedef void (*ScanSetlimits_function) (HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
+
+typedef ParallelHeapScanDesc (*ScanGetParallelheapscandesc_function) (TableScanDesc scan);
+typedef HeapPageScanDesc(*ScanGetHeappagescandesc_function) (TableScanDesc scan);
+
+typedef void (*ScanSetlimits_function) (TableScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
 
 /* must return a TupleTableSlot? */
-typedef TableTuple(*ScanGetnext_function) (HeapScanDesc scan,
+typedef TableTuple(*ScanGetnext_function) (TableScanDesc scan,
 											 ScanDirection direction);
 
-typedef TupleTableSlot *(*ScanGetnextSlot_function) (HeapScanDesc scan,
+typedef TupleTableSlot *(*ScanGetnextSlot_function) (TableScanDesc scan,
 													 ScanDirection direction, TupleTableSlot *slot);
 
-typedef void (*ScanEnd_function) (HeapScanDesc scan);
+typedef TableTuple(*ScanFetchTupleFromOffset_function) (TableScanDesc scan,
+														  BlockNumber blkno, OffsetNumber offset);
+
+typedef void (*ScanEnd_function) (TableScanDesc scan);
 
 
-typedef void (*ScanGetpage_function) (HeapScanDesc scan, BlockNumber page);
-typedef void (*ScanRescan_function) (HeapScanDesc scan, ScanKey key, bool set_params,
+typedef void (*ScanGetpage_function) (TableScanDesc scan, BlockNumber page);
+typedef void (*ScanRescan_function) (TableScanDesc scan, ScanKey key, bool set_params,
 									 bool allow_strat, bool allow_sync, bool allow_pagemode);
-typedef void (*ScanUpdateSnapshot_function) (HeapScanDesc scan, Snapshot snapshot);
+typedef void (*ScanUpdateSnapshot_function) (TableScanDesc scan, Snapshot snapshot);
 
 typedef bool (*HotSearchBuffer_function) (ItemPointer tid, Relation relation,
 										  Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
@@ -149,9 +156,12 @@ typedef struct TableAmRoutine
 
 	/* Operations on relation scans */
 	ScanBegin_function scan_begin;
+	ScanGetParallelheapscandesc_function scan_get_parallelheapscandesc;
+	ScanGetHeappagescandesc_function scan_get_heappagescandesc;
 	ScanSetlimits_function scansetlimits;
 	ScanGetnext_function scan_getnext;
 	ScanGetnextSlot_function scan_getnextslot;
+	ScanFetchTupleFromOffset_function scan_fetch_tuple_from_offset;
 	ScanEnd_function scan_end;
 	ScanGetpage_function scan_getpage;
 	ScanRescan_function scan_rescan;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 384a33b516..619ef0fe00 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1113,7 +1113,7 @@ typedef struct ScanState
 {
 	PlanState	ps;				/* its first field is NodeTag */
 	Relation	ss_currentRelation;
-	HeapScanDesc ss_currentScanDesc;
+	TableScanDesc ss_currentScanDesc;
 	TupleTableSlot *ss_ScanTupleSlot;
 } ScanState;
 
@@ -1134,6 +1134,7 @@ typedef struct SeqScanState
 typedef struct SampleScanState
 {
 	ScanState	ss;
+	HeapPageScanDesc pagescan;
 	List	   *args;			/* expr states for TABLESAMPLE params */
 	ExprState  *repeatable;		/* expr state for REPEATABLE expr */
 	/* use struct pointer to avoid including tsmapi.h here */
@@ -1362,6 +1363,7 @@ typedef struct ParallelBitmapHeapState
 typedef struct BitmapHeapScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
+	HeapPageScanDesc pagescan;
 	ExprState  *bitmapqualorig;
 	TIDBitmap  *tbm;
 	TBMIterator *tbmiterator;
-- 
2.15.0.windows.1



  [application/octet-stream] 0009-BulkInsertState-is-added-into-table-AM.patch (10.7K, ../../CAJrrPGdY_hgvu06R_vCibUktKRLp=b8nBfeZkh=KRc95tq4euA@mail.gmail.com/14-0009-BulkInsertState-is-added-into-table-AM.patch)
  download | inline diff:
From 5b93c2ba9d11664b840df0507911630eab0424da Mon Sep 17 00:00:00 2001
From: Hari Babu <[email protected]>
Date: Tue, 9 Jan 2018 18:04:50 +1100
Subject: [PATCH 09/12] BulkInsertState is added into table AM

Added Get, free and release bulkinsertstate functions
to operate with bulkinsertstate that is provided by
the heap access method.
---
 src/backend/access/heap/heapam.c         |  2 +-
 src/backend/access/heap/heapam_handler.c |  4 ++++
 src/backend/access/table/tableam.c       | 23 +++++++++++++++++++++++
 src/backend/commands/copy.c              |  8 ++++----
 src/backend/commands/createas.c          |  6 +++---
 src/backend/commands/matview.c           |  6 +++---
 src/backend/commands/tablecmds.c         |  6 +++---
 src/include/access/heapam.h              |  4 +---
 src/include/access/tableam.h             |  4 ++++
 src/include/access/tableam_common.h      |  3 +++
 src/include/access/tableamapi.h          |  7 +++++++
 11 files changed, 56 insertions(+), 17 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 088799c70d..9c477f7013 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2315,7 +2315,7 @@ GetBulkInsertState(void)
 	bistate = (BulkInsertState) palloc(sizeof(BulkInsertStateData));
 	bistate->strategy = GetAccessStrategy(BAS_BULKWRITE);
 	bistate->current_buf = InvalidBuffer;
-	return bistate;
+	return (void *)bistate;
 }
 
 /*
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 769febcd15..ba49551a07 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -384,5 +384,9 @@ heap_tableam_handler(PG_FUNCTION_ARGS)
 	amroutine->tuple_get_latest_tid = heap_get_latest_tid;
 	amroutine->relation_sync = heap_sync;
 
+	amroutine->getbulkinsertstate = GetBulkInsertState;
+	amroutine->freebulkinsertstate = FreeBulkInsertState;
+	amroutine->releasebulkinsertstate = ReleaseBulkInsertStatePin;
+
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index bef1b9d34f..626d7ab237 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -379,3 +379,26 @@ table_sync(Relation rel)
 {
 	rel->rd_tableamroutine->relation_sync(rel);
 }
+
+/*
+ * -------------------
+ * storage Bulk Insert functions
+ * -------------------
+ */
+BulkInsertState
+table_getbulkinsertstate(Relation rel)
+{
+	return rel->rd_tableamroutine->getbulkinsertstate();
+}
+
+void
+table_freebulkinsertstate(Relation rel, BulkInsertState bistate)
+{
+	rel->rd_tableamroutine->freebulkinsertstate(bistate);
+}
+
+void
+table_releasebulkinsertstate(Relation rel, BulkInsertState bistate)
+{
+	rel->rd_tableamroutine->releasebulkinsertstate(bistate);
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index a40636cca7..669c9062aa 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2294,7 +2294,7 @@ CopyFrom(CopyState cstate)
 	ErrorContextCallback errcallback;
 	CommandId	mycid = GetCurrentCommandId(true);
 	int			hi_options = 0; /* start with default heap_insert options */
-	BulkInsertState bistate;
+	void       *bistate;
 	uint64		processed = 0;
 	bool		useHeapMultiInsert;
 	int			nBufferedTuples = 0;
@@ -2532,7 +2532,7 @@ CopyFrom(CopyState cstate)
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	bistate = GetBulkInsertState();
+	bistate = table_getbulkinsertstate(resultRelInfo->ri_RelationDesc);
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2613,7 +2613,7 @@ CopyFrom(CopyState cstate)
 			 */
 			if (prev_leaf_part_index != leaf_part_index)
 			{
-				ReleaseBulkInsertStatePin(bistate);
+				table_releasebulkinsertstate(resultRelInfo->ri_RelationDesc, bistate);
 				prev_leaf_part_index = leaf_part_index;
 			}
 
@@ -2798,7 +2798,7 @@ CopyFrom(CopyState cstate)
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	FreeBulkInsertState(bistate);
+	table_freebulkinsertstate(resultRelInfo->ri_RelationDesc, bistate);
 
 	MemoryContextSwitchTo(oldcontext);
 
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 9c531b7f28..c2d0a14d45 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -60,7 +60,7 @@ typedef struct
 	ObjectAddress reladdr;		/* address of rel, for ExecCreateTableAs */
 	CommandId	output_cid;		/* cmin to insert in output tuples */
 	int			hi_options;		/* heap_insert performance options */
-	BulkInsertState bistate;	/* bulk insert state */
+	void       *bistate;		/* bulk insert state */
 } DR_intorel;
 
 /* utility functions for CTAS definition creation */
@@ -570,7 +570,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	 */
 	myState->hi_options = HEAP_INSERT_SKIP_FSM |
 		(XLogIsNeeded() ? 0 : HEAP_INSERT_SKIP_WAL);
-	myState->bistate = GetBulkInsertState();
+	myState->bistate = table_getbulkinsertstate(intoRelationDesc);
 
 	/* Not using WAL requires smgr_targblock be initially invalid */
 	Assert(RelationGetTargetBlock(intoRelationDesc) == InvalidBlockNumber);
@@ -619,7 +619,7 @@ intorel_shutdown(DestReceiver *self)
 {
 	DR_intorel *myState = (DR_intorel *) self;
 
-	FreeBulkInsertState(myState->bistate);
+	table_freebulkinsertstate(myState->rel, myState->bistate);
 
 	/* If we skipped using WAL, must heap_sync before commit */
 	if (myState->hi_options & HEAP_INSERT_SKIP_WAL)
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index d51d3c2c8e..fb1842dfdf 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -52,7 +52,7 @@ typedef struct
 	Relation	transientrel;	/* relation to write to */
 	CommandId	output_cid;		/* cmin to insert in output tuples */
 	int			hi_options;		/* heap_insert performance options */
-	BulkInsertState bistate;	/* bulk insert state */
+	void       *bistate;		/* bulk insert state */
 } DR_transientrel;
 
 static int	matview_maintenance_depth = 0;
@@ -479,7 +479,7 @@ transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	myState->hi_options = HEAP_INSERT_SKIP_FSM | HEAP_INSERT_FROZEN;
 	if (!XLogIsNeeded())
 		myState->hi_options |= HEAP_INSERT_SKIP_WAL;
-	myState->bistate = GetBulkInsertState();
+	myState->bistate = table_getbulkinsertstate(transientrel);
 
 	/* Not using WAL requires smgr_targblock be initially invalid */
 	Assert(RelationGetTargetBlock(transientrel) == InvalidBlockNumber);
@@ -522,7 +522,7 @@ transientrel_shutdown(DestReceiver *self)
 {
 	DR_transientrel *myState = (DR_transientrel *) self;
 
-	FreeBulkInsertState(myState->bistate);
+	table_freebulkinsertstate(myState->transientrel, myState->bistate);
 
 	/* If we skipped using WAL, must heap_sync before commit */
 	if (myState->hi_options & HEAP_INSERT_SKIP_WAL)
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 91d0b094fe..4dc5ed80bb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4383,7 +4383,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	ListCell   *l;
 	EState	   *estate;
 	CommandId	mycid;
-	BulkInsertState bistate;
+	void       *bistate;
 	int			hi_options;
 	ExprState  *partqualstate = NULL;
 
@@ -4409,7 +4409,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	if (newrel)
 	{
 		mycid = GetCurrentCommandId(true);
-		bistate = GetBulkInsertState();
+		bistate = table_getbulkinsertstate(newrel);
 
 		hi_options = HEAP_INSERT_SKIP_FSM;
 		if (!XLogIsNeeded())
@@ -4684,7 +4684,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	heap_close(oldrel, NoLock);
 	if (newrel)
 	{
-		FreeBulkInsertState(bistate);
+		table_freebulkinsertstate(newrel, bistate);
 
 		/* If we skipped writing WAL, then we need to sync the heap. */
 		if (hi_options & HEAP_INSERT_SKIP_WAL)
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 8d1263d0ae..f9a0602b86 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -31,8 +31,6 @@
 #define HEAP_INSERT_FROZEN		0x0004
 #define HEAP_INSERT_SPECULATIVE 0x0008
 
-typedef struct BulkInsertStateData *BulkInsertState;
-
 /*
  * Possible lock modes for a tuple.
  */
@@ -148,7 +146,7 @@ extern void heap_get_latest_tid(Relation relation, Snapshot snapshot,
 extern void setLastTid(const ItemPointer tid);
 
 extern BulkInsertState GetBulkInsertState(void);
-extern void FreeBulkInsertState(BulkInsertState);
+extern void FreeBulkInsertState(BulkInsertState bistate);
 extern void ReleaseBulkInsertStatePin(BulkInsertState bistate);
 
 extern Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid,
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 9be26fb86d..1027bcfba8 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -118,4 +118,8 @@ extern void table_get_latest_tid(Relation relation,
 
 extern void table_sync(Relation rel);
 
+extern BulkInsertState table_getbulkinsertstate(Relation rel);
+extern void table_freebulkinsertstate(Relation rel, BulkInsertState bistate);
+extern void table_releasebulkinsertstate(Relation rel, BulkInsertState bistate);
+
 #endif		/* TABLEAM_H */
diff --git a/src/include/access/tableam_common.h b/src/include/access/tableam_common.h
index 26541abbde..74c8ac58bb 100644
--- a/src/include/access/tableam_common.h
+++ b/src/include/access/tableam_common.h
@@ -39,6 +39,9 @@ typedef enum
 	HEAPTUPLE_DELETE_IN_PROGRESS	/* deleting xact is still in progress */
 } HTSV_Result;
 
+typedef struct BulkInsertStateData *BulkInsertState;
+
+
 /*
  * slot table AM routine functions
  */
diff --git a/src/include/access/tableamapi.h b/src/include/access/tableamapi.h
index b32a4bff83..adc3057eca 100644
--- a/src/include/access/tableamapi.h
+++ b/src/include/access/tableamapi.h
@@ -81,6 +81,9 @@ typedef TableTuple(*TupleFromDatum_function) (Datum data, Oid tableoid);
 
 typedef void (*RelationSync_function) (Relation relation);
 
+typedef BulkInsertState (*GetBulkInsertState_function) (void);
+typedef void (*FreeBulkInsertState_function) (BulkInsertState bistate);
+typedef void (*ReleaseBulkInsertState_function) (BulkInsertState bistate);
 
 typedef TableScanDesc (*ScanBegin_function) (Relation relation,
 											Snapshot snapshot,
@@ -154,6 +157,10 @@ typedef struct TableAmRoutine
 
 	RelationSync_function relation_sync;	/* heap_sync */
 
+	GetBulkInsertState_function getbulkinsertstate;
+	FreeBulkInsertState_function freebulkinsertstate;
+	ReleaseBulkInsertState_function releasebulkinsertstate;
+
 	/* Operations on relation scans */
 	ScanBegin_function scan_begin;
 	ScanGetParallelheapscandesc_function scan_get_parallelheapscandesc;
-- 
2.15.0.windows.1



^ permalink  raw  reply  [nested|flat] 21+ messages in thread

* [PATCH 3/4] Generation: keeper block
@ 2021-07-31 00:54 Tomas Vondra <[email protected]>
  0 siblings, 0 replies; 21+ messages in thread

From: Tomas Vondra @ 2021-07-31 00:54 UTC (permalink / raw)

---
 src/backend/utils/mmgr/generation.c | 61 +++++++++++++++++++++++++----
 1 file changed, 54 insertions(+), 7 deletions(-)

diff --git a/src/backend/utils/mmgr/generation.c b/src/backend/utils/mmgr/generation.c
index 771a2525ca..6c90416e27 100644
--- a/src/backend/utils/mmgr/generation.c
+++ b/src/backend/utils/mmgr/generation.c
@@ -65,6 +65,7 @@ typedef struct GenerationContext
 	Size		nextBlockSize;	/* next block size to allocate */
 
 	GenerationBlock *block;		/* current (most recently allocated) block */
+	GenerationBlock *keeper;	/* keeper block */
 	dlist_head	blocks;			/* list of blocks */
 } GenerationContext;
 
@@ -254,6 +255,7 @@ GenerationContextCreate(MemoryContext parent,
 	set->maxBlockSize = maxBlockSize;
 	set->nextBlockSize = initBlockSize;
 	set->block = NULL;
+	set->keeper = NULL;
 	dlist_init(&set->blocks);
 
 	/* Finally, do the type-independent part of context creation */
@@ -302,6 +304,7 @@ GenerationReset(MemoryContext context)
 	}
 
 	set->block = NULL;
+	set->keeper = NULL;
 
 	/* Reset block size allocation sequence, too */
 	set->nextBlockSize = set->initBlockSize;
@@ -344,7 +347,7 @@ GenerationAlloc(MemoryContext context, Size size)
 	Size		chunk_size = MAXALIGN(size);
 	Size		blockSize;
 
-	blockSize = (set->block) ? set->block->blksize : set->nextBlockSize;
+	blockSize = set->initBlockSize;
 
 	/* is it an over-sized chunk? if yes, allocate special block */
 	if (chunk_size > (blockSize / 8))
@@ -400,6 +403,26 @@ GenerationAlloc(MemoryContext context, Size size)
 	 */
 	block = set->block;
 
+	/*
+	 * If we can't use the current block, and we have a keeper block with
+	 * enough free space in it, use it as the block.
+	 *
+	 * XXX We don't want to do this when there's not enough free space
+	 * (although the keeper block should be empty, so not sure if checking
+	 * the space in the keeper block is necessary).
+	 */
+	if (((block == NULL) ||
+		(block->endptr - block->freeptr) < Generation_CHUNKHDRSZ + chunk_size) &&
+		(set->keeper != NULL) &&
+		(set->keeper->endptr - set->keeper->freeptr) < Generation_CHUNKHDRSZ + chunk_size)
+	{
+		block = set->keeper;
+		set->keeper = NULL;
+
+		/* keeper block was not counted as allocated, so add it back */
+		context->mem_allocated += block->blksize;
+	}
+
 	if ((block == NULL) ||
 		(block->endptr - block->freeptr) < Generation_CHUNKHDRSZ + chunk_size)
 	{
@@ -524,16 +547,34 @@ GenerationFree(MemoryContext context, void *pointer)
 	if (block->nfree < block->nchunks)
 		return;
 
+	/* Also make sure the block is not marked as the current block. */
+	if (set->block == block)
+		set->block = NULL;
+
+	/* Keep the block for reuse, if we don't have one already. */
+	if (!set->keeper && block->blksize <= set->maxBlockSize)
+	{
+		/* reset the pointers before we use it as keeper block */
+		block->freeptr = ((char *) block) + Generation_BLOCKHDRSZ;
+		block->endptr = ((char *) block) + block->blksize;
+
+		block->nfree = 0;
+		block->nchunks = 0;
+
+		set->keeper = block;
+
+		/* keeper block is not counted as allocated */
+		context->mem_allocated -= block->blksize;
+
+		return;
+	}
+
 	/*
 	 * The block is empty, so let's get rid of it. First remove it from the
 	 * list of blocks, then return it to malloc().
 	 */
 	dlist_delete(&block->node);
 
-	/* Also make sure the block is not marked as the current block. */
-	if (set->block == block)
-		set->block = NULL;
-
 	context->mem_allocated -= block->blksize;
 	free(block);
 }
@@ -770,13 +811,19 @@ GenerationCheck(MemoryContext context)
 					nchunks;
 		char	   *ptr;
 
-		total_allocated += block->blksize;
+		/*
+		 * The keeper block in the list of blocks, but we don't consider it
+		 * as allocated in memory accounting. So don't include it in the sum.
+		 */
+		if (block != gen->keeper)
+			total_allocated += block->blksize;
 
 		/*
 		 * nfree > nchunks is surely wrong, and we don't expect to see
 		 * equality either, because such a block should have gotten freed.
 		 */
-		if (block->nfree >= block->nchunks)
+		if ((block->nfree > block->nchunks) &&
+			((block != gen->keeper) && (block->nfree == block->nchunks)))
 			elog(WARNING, "problem in Generation %s: number of free chunks %d in block %p exceeds %d allocated",
 				 name, block->nfree, block, block->nchunks);
 
-- 
2.31.1


--------------D082F723AC01DF154D86FBFF
Content-Type: text/x-patch; charset=UTF-8;
 name="0004-Generation-allocChunkLimit.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0004-Generation-allocChunkLimit.patch"



^ permalink  raw  reply  [nested|flat] 21+ messages in thread

* [PATCH 3/4] Generation: keeper block
@ 2021-07-31 00:54 Tomas Vondra <[email protected]>
  0 siblings, 0 replies; 21+ messages in thread

From: Tomas Vondra @ 2021-07-31 00:54 UTC (permalink / raw)

---
 src/backend/utils/mmgr/generation.c | 61 +++++++++++++++++++++++++----
 1 file changed, 54 insertions(+), 7 deletions(-)

diff --git a/src/backend/utils/mmgr/generation.c b/src/backend/utils/mmgr/generation.c
index 771a2525ca..6c90416e27 100644
--- a/src/backend/utils/mmgr/generation.c
+++ b/src/backend/utils/mmgr/generation.c
@@ -65,6 +65,7 @@ typedef struct GenerationContext
 	Size		nextBlockSize;	/* next block size to allocate */
 
 	GenerationBlock *block;		/* current (most recently allocated) block */
+	GenerationBlock *keeper;	/* keeper block */
 	dlist_head	blocks;			/* list of blocks */
 } GenerationContext;
 
@@ -254,6 +255,7 @@ GenerationContextCreate(MemoryContext parent,
 	set->maxBlockSize = maxBlockSize;
 	set->nextBlockSize = initBlockSize;
 	set->block = NULL;
+	set->keeper = NULL;
 	dlist_init(&set->blocks);
 
 	/* Finally, do the type-independent part of context creation */
@@ -302,6 +304,7 @@ GenerationReset(MemoryContext context)
 	}
 
 	set->block = NULL;
+	set->keeper = NULL;
 
 	/* Reset block size allocation sequence, too */
 	set->nextBlockSize = set->initBlockSize;
@@ -344,7 +347,7 @@ GenerationAlloc(MemoryContext context, Size size)
 	Size		chunk_size = MAXALIGN(size);
 	Size		blockSize;
 
-	blockSize = (set->block) ? set->block->blksize : set->nextBlockSize;
+	blockSize = set->initBlockSize;
 
 	/* is it an over-sized chunk? if yes, allocate special block */
 	if (chunk_size > (blockSize / 8))
@@ -400,6 +403,26 @@ GenerationAlloc(MemoryContext context, Size size)
 	 */
 	block = set->block;
 
+	/*
+	 * If we can't use the current block, and we have a keeper block with
+	 * enough free space in it, use it as the block.
+	 *
+	 * XXX We don't want to do this when there's not enough free space
+	 * (although the keeper block should be empty, so not sure if checking
+	 * the space in the keeper block is necessary).
+	 */
+	if (((block == NULL) ||
+		(block->endptr - block->freeptr) < Generation_CHUNKHDRSZ + chunk_size) &&
+		(set->keeper != NULL) &&
+		(set->keeper->endptr - set->keeper->freeptr) < Generation_CHUNKHDRSZ + chunk_size)
+	{
+		block = set->keeper;
+		set->keeper = NULL;
+
+		/* keeper block was not counted as allocated, so add it back */
+		context->mem_allocated += block->blksize;
+	}
+
 	if ((block == NULL) ||
 		(block->endptr - block->freeptr) < Generation_CHUNKHDRSZ + chunk_size)
 	{
@@ -524,16 +547,34 @@ GenerationFree(MemoryContext context, void *pointer)
 	if (block->nfree < block->nchunks)
 		return;
 
+	/* Also make sure the block is not marked as the current block. */
+	if (set->block == block)
+		set->block = NULL;
+
+	/* Keep the block for reuse, if we don't have one already. */
+	if (!set->keeper && block->blksize <= set->maxBlockSize)
+	{
+		/* reset the pointers before we use it as keeper block */
+		block->freeptr = ((char *) block) + Generation_BLOCKHDRSZ;
+		block->endptr = ((char *) block) + block->blksize;
+
+		block->nfree = 0;
+		block->nchunks = 0;
+
+		set->keeper = block;
+
+		/* keeper block is not counted as allocated */
+		context->mem_allocated -= block->blksize;
+
+		return;
+	}
+
 	/*
 	 * The block is empty, so let's get rid of it. First remove it from the
 	 * list of blocks, then return it to malloc().
 	 */
 	dlist_delete(&block->node);
 
-	/* Also make sure the block is not marked as the current block. */
-	if (set->block == block)
-		set->block = NULL;
-
 	context->mem_allocated -= block->blksize;
 	free(block);
 }
@@ -770,13 +811,19 @@ GenerationCheck(MemoryContext context)
 					nchunks;
 		char	   *ptr;
 
-		total_allocated += block->blksize;
+		/*
+		 * The keeper block in the list of blocks, but we don't consider it
+		 * as allocated in memory accounting. So don't include it in the sum.
+		 */
+		if (block != gen->keeper)
+			total_allocated += block->blksize;
 
 		/*
 		 * nfree > nchunks is surely wrong, and we don't expect to see
 		 * equality either, because such a block should have gotten freed.
 		 */
-		if (block->nfree >= block->nchunks)
+		if ((block->nfree > block->nchunks) &&
+			((block != gen->keeper) && (block->nfree == block->nchunks)))
 			elog(WARNING, "problem in Generation %s: number of free chunks %d in block %p exceeds %d allocated",
 				 name, block->nfree, block, block->nchunks);
 
-- 
2.31.1


--------------2DA8AC73530187D08AAA54D6
Content-Type: text/x-patch; charset=UTF-8;
 name="0004-Generation-allocChunkLimit-v2.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0004-Generation-allocChunkLimit-v2.patch"



^ permalink  raw  reply  [nested|flat] 21+ messages in thread

* [PATCH 2/3] Generation: keeper block
@ 2021-07-31 00:54 Tomas Vondra <[email protected]>
  0 siblings, 0 replies; 21+ messages in thread

From: Tomas Vondra @ 2021-07-31 00:54 UTC (permalink / raw)

---
 src/backend/utils/mmgr/generation.c | 61 +++++++++++++++++++++++++----
 1 file changed, 54 insertions(+), 7 deletions(-)

diff --git a/src/backend/utils/mmgr/generation.c b/src/backend/utils/mmgr/generation.c
index 771a2525ca..6c90416e27 100644
--- a/src/backend/utils/mmgr/generation.c
+++ b/src/backend/utils/mmgr/generation.c
@@ -65,6 +65,7 @@ typedef struct GenerationContext
 	Size		nextBlockSize;	/* next block size to allocate */
 
 	GenerationBlock *block;		/* current (most recently allocated) block */
+	GenerationBlock *keeper;	/* keeper block */
 	dlist_head	blocks;			/* list of blocks */
 } GenerationContext;
 
@@ -254,6 +255,7 @@ GenerationContextCreate(MemoryContext parent,
 	set->maxBlockSize = maxBlockSize;
 	set->nextBlockSize = initBlockSize;
 	set->block = NULL;
+	set->keeper = NULL;
 	dlist_init(&set->blocks);
 
 	/* Finally, do the type-independent part of context creation */
@@ -302,6 +304,7 @@ GenerationReset(MemoryContext context)
 	}
 
 	set->block = NULL;
+	set->keeper = NULL;
 
 	/* Reset block size allocation sequence, too */
 	set->nextBlockSize = set->initBlockSize;
@@ -344,7 +347,7 @@ GenerationAlloc(MemoryContext context, Size size)
 	Size		chunk_size = MAXALIGN(size);
 	Size		blockSize;
 
-	blockSize = (set->block) ? set->block->blksize : set->nextBlockSize;
+	blockSize = set->initBlockSize;
 
 	/* is it an over-sized chunk? if yes, allocate special block */
 	if (chunk_size > (blockSize / 8))
@@ -400,6 +403,26 @@ GenerationAlloc(MemoryContext context, Size size)
 	 */
 	block = set->block;
 
+	/*
+	 * If we can't use the current block, and we have a keeper block with
+	 * enough free space in it, use it as the block.
+	 *
+	 * XXX We don't want to do this when there's not enough free space
+	 * (although the keeper block should be empty, so not sure if checking
+	 * the space in the keeper block is necessary).
+	 */
+	if (((block == NULL) ||
+		(block->endptr - block->freeptr) < Generation_CHUNKHDRSZ + chunk_size) &&
+		(set->keeper != NULL) &&
+		(set->keeper->endptr - set->keeper->freeptr) < Generation_CHUNKHDRSZ + chunk_size)
+	{
+		block = set->keeper;
+		set->keeper = NULL;
+
+		/* keeper block was not counted as allocated, so add it back */
+		context->mem_allocated += block->blksize;
+	}
+
 	if ((block == NULL) ||
 		(block->endptr - block->freeptr) < Generation_CHUNKHDRSZ + chunk_size)
 	{
@@ -524,16 +547,34 @@ GenerationFree(MemoryContext context, void *pointer)
 	if (block->nfree < block->nchunks)
 		return;
 
+	/* Also make sure the block is not marked as the current block. */
+	if (set->block == block)
+		set->block = NULL;
+
+	/* Keep the block for reuse, if we don't have one already. */
+	if (!set->keeper && block->blksize <= set->maxBlockSize)
+	{
+		/* reset the pointers before we use it as keeper block */
+		block->freeptr = ((char *) block) + Generation_BLOCKHDRSZ;
+		block->endptr = ((char *) block) + block->blksize;
+
+		block->nfree = 0;
+		block->nchunks = 0;
+
+		set->keeper = block;
+
+		/* keeper block is not counted as allocated */
+		context->mem_allocated -= block->blksize;
+
+		return;
+	}
+
 	/*
 	 * The block is empty, so let's get rid of it. First remove it from the
 	 * list of blocks, then return it to malloc().
 	 */
 	dlist_delete(&block->node);
 
-	/* Also make sure the block is not marked as the current block. */
-	if (set->block == block)
-		set->block = NULL;
-
 	context->mem_allocated -= block->blksize;
 	free(block);
 }
@@ -770,13 +811,19 @@ GenerationCheck(MemoryContext context)
 					nchunks;
 		char	   *ptr;
 
-		total_allocated += block->blksize;
+		/*
+		 * The keeper block in the list of blocks, but we don't consider it
+		 * as allocated in memory accounting. So don't include it in the sum.
+		 */
+		if (block != gen->keeper)
+			total_allocated += block->blksize;
 
 		/*
 		 * nfree > nchunks is surely wrong, and we don't expect to see
 		 * equality either, because such a block should have gotten freed.
 		 */
-		if (block->nfree >= block->nchunks)
+		if ((block->nfree > block->nchunks) &&
+			((block != gen->keeper) && (block->nfree == block->nchunks)))
 			elog(WARNING, "problem in Generation %s: number of free chunks %d in block %p exceeds %d allocated",
 				 name, block->nfree, block, block->nchunks);
 
-- 
2.31.1


--------------132BD247D801061DA85680D7
Content-Type: text/x-patch; charset=UTF-8;
 name="0003-Generation-allocChunkLimit.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Generation-allocChunkLimit.patch"



^ permalink  raw  reply  [nested|flat] 21+ messages in thread

* Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman
@ 2022-04-07 17:52 Andres Freund <[email protected]>
  2022-04-07 17:57 ` Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Tom Lane <[email protected]>
  0 siblings, 1 reply; 21+ messages in thread

From: Andres Freund @ 2022-04-07 17:52 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Michael Paquier <[email protected]>; [email protected]

Hi,

On 2022-04-07 13:40:30 -0400, Tom Lane wrote:
> Michael Paquier <[email protected]> writes:
> > Add TAP test for archive_cleanup_command and recovery_end_command
> 
> grassquit just showed a non-reproducible failure in this test [1]:

I was just staring at that as well.


> # Postmaster PID for node "standby" is 291160
> ok 1 - check content from archives
> not ok 2 - archive_cleanup_command executed on checkpoint
> 
> #   Failed test 'archive_cleanup_command executed on checkpoint'
> #   at t/002_archiving.pl line 74.
> 
> This test is sending a CHECKPOINT command to the standby and
> expecting it to run the archive_cleanup_command, but it looks
> like the standby did not actually run any checkpoint:
> 
> 2022-04-07 16:11:33.060 UTC [291806][not initialized][:0] LOG:  connection received: host=[local]
> 2022-04-07 16:11:33.078 UTC [291806][client backend][2/15:0] LOG:  connection authorized: user=bf database=postgres application_name=002_archiving.pl
> 2022-04-07 16:11:33.084 UTC [291806][client backend][2/16:0] LOG:  statement: CHECKPOINT
> 2022-04-07 16:11:33.092 UTC [291806][client backend][:0] LOG:  disconnection: session time: 0:00:00.032 user=bf database=postgres host=[local]
> 
> I am suspicious that the reason is that ProcessUtility does not
> ask for a forced checkpoint when in recovery:
> 
>             RequestCheckpoint(CHECKPOINT_IMMEDIATE | CHECKPOINT_WAIT |
>                               (RecoveryInProgress() ? 0 : CHECKPOINT_FORCE));
> 
> The trouble with this theory is that this test has been there for
> nearly six months and this is the first such failure (I scraped the
> buildfarm logs to be sure).  Seems like failures should be a lot
> more common than that.

> I wondered if the recent pg_stats changes could have affected this, but I
> don't really see how.

I don't really see either. It's a bit more conceivable that the recovery
prefetching changes could affect the timing sufficiently?

It's also possible that it requires an animal of a certain speed to happen -
we didn't have an -fsanitize=address animal until recently.

I guess we'll have to wait and see what the frequency of the problem is?

Greetings,

Andres Freund






^ permalink  raw  reply  [nested|flat] 21+ messages in thread

* Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman
  2022-04-07 17:52 Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Andres Freund <[email protected]>
@ 2022-04-07 17:57 ` Tom Lane <[email protected]>
  2022-04-07 18:12   ` Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Andres Freund <[email protected]>
  0 siblings, 1 reply; 21+ messages in thread

From: Tom Lane @ 2022-04-07 17:57 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Michael Paquier <[email protected]>; [email protected]

Andres Freund <[email protected]> writes:
> On 2022-04-07 13:40:30 -0400, Tom Lane wrote:
>> This test is sending a CHECKPOINT command to the standby and
>> expecting it to run the archive_cleanup_command, but it looks
>> like the standby did not actually run any checkpoint:
>> ...
>> I wondered if the recent pg_stats changes could have affected this, but I
>> don't really see how.

> I don't really see either. It's a bit more conceivable that the recovery
> prefetching changes could affect the timing sufficiently?

Oh, that's at least a little plausible.

> I guess we'll have to wait and see what the frequency of the problem is?

Yeah, with only one instance it could just be cosmic rays or something.
However, assuming it is real, I guess I wonder why we don't say
CHECKPOINT_FORCE in standby mode too.

			regards, tom lane






^ permalink  raw  reply  [nested|flat] 21+ messages in thread

* Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman
  2022-04-07 17:52 Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Andres Freund <[email protected]>
  2022-04-07 17:57 ` Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Tom Lane <[email protected]>
@ 2022-04-07 18:12   ` Andres Freund <[email protected]>
  2022-04-08 16:36     ` Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Tom Lane <[email protected]>
  2022-04-08 21:55     ` Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Tom Lane <[email protected]>
  0 siblings, 2 replies; 21+ messages in thread

From: Andres Freund @ 2022-04-07 18:12 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Michael Paquier <[email protected]>; [email protected]

Hi,

On 2022-04-07 13:57:45 -0400, Tom Lane wrote:
> Yeah, with only one instance it could just be cosmic rays or something.
> However, assuming it is real, I guess I wonder why we don't say
> CHECKPOINT_FORCE in standby mode too.

I guess it might partially be that restartpoints require a checkpoint to have
happened on the primary. If we used FORCE, we'd have to wait till the next
checkpoint on the primary, which'd be a problem if it's e.g. a manually issued
CHECKPOINT; before shutting the standby down.

Greetings,

Andres Freund






^ permalink  raw  reply  [nested|flat] 21+ messages in thread

* Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman
  2022-04-07 17:52 Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Andres Freund <[email protected]>
  2022-04-07 17:57 ` Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Tom Lane <[email protected]>
  2022-04-07 18:12   ` Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Andres Freund <[email protected]>
@ 2022-04-08 16:36     ` Tom Lane <[email protected]>
  1 sibling, 0 replies; 21+ messages in thread

From: Tom Lane @ 2022-04-08 16:36 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Michael Paquier <[email protected]>; [email protected]

Andres Freund <[email protected]> writes:
> On 2022-04-07 13:57:45 -0400, Tom Lane wrote:
>> Yeah, with only one instance it could just be cosmic rays or something.

Not cosmic rays: skink has shown the same symptom three times running.
Looks like maybe the archive_cleanup_command itself is doing something
it shouldn't?

			regards, tom lane






^ permalink  raw  reply  [nested|flat] 21+ messages in thread

* Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman
  2022-04-07 17:52 Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Andres Freund <[email protected]>
  2022-04-07 17:57 ` Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Tom Lane <[email protected]>
  2022-04-07 18:12   ` Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Andres Freund <[email protected]>
@ 2022-04-08 21:55     ` Tom Lane <[email protected]>
  2022-04-08 23:56       ` Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Andres Freund <[email protected]>
  2022-04-09 00:59       ` Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Andres Freund <[email protected]>
  1 sibling, 2 replies; 21+ messages in thread

From: Tom Lane @ 2022-04-08 21:55 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Michael Paquier <[email protected]>; Thomas Munro <[email protected]>; [email protected]

Andres Freund <[email protected]> writes:
> On 2022-04-07 13:57:45 -0400, Tom Lane wrote:
>> Yeah, with only one instance it could just be cosmic rays or something.
>> However, assuming it is real, I guess I wonder why we don't say
>> CHECKPOINT_FORCE in standby mode too.

> I guess it might partially be that restartpoints require a checkpoint to have
> happened on the primary. If we used FORCE, we'd have to wait till the next
> checkpoint on the primary, which'd be a problem if it's e.g. a manually issued
> CHECKPOINT; before shutting the standby down.

After seeing skink's results, I tried running that test under valgrind
here, and it fails just like that every time.  skink's history allows
us to bound the failure introduction between 79b716cfb7 and
d7ab2a9a3c, which I think makes it just about certain that it was
5dc0418fab (Prefetch data referenced by the WAL, take II), though I've
not bisected to be 100% sure.

Adding some debug printouts to ExecuteRecoveryCommand convinces me
that indeed the archive_cleanup_command is NOT getting called by the
problematic CHECKPOINT command.  I surmise based on Andres' comment
above that the standby isn't making a restartpoint for lack of
an available primary checkpoint, which looks to me like it could be
a pre-existing bug in the test case: it's sure not doing anything to
guarantee that the primary's checkpoint record has reached the standby.

I tried adjusting the patch so it does guarantee that (as attached),
and in two out of two tries it got past the archive_cleanup_command
failure but then hung up waiting for standby2 to promote.

On the whole, I'm not sure that the WAL prefetch logic is noticeably
more stable than when we booted it out last year :-(.  However, I also
wonder why it is that this test case wasn't occasionally failing already.

			regards, tom lane



Attachments:

  [text/x-diff] 002_archiving-hack.patch (890B, ../../[email protected]/2-002_archiving-hack.patch)
  download | inline diff:
diff --git a/src/test/recovery/t/002_archiving.pl b/src/test/recovery/t/002_archiving.pl
index c8f5ffbaf0..1032d8a388 100644
--- a/src/test/recovery/t/002_archiving.pl
+++ b/src/test/recovery/t/002_archiving.pl
@@ -44,13 +44,14 @@ $node_standby->start;
 # Create some content on primary
 $node_primary->safe_psql('postgres',
 	"CREATE TABLE tab_int AS SELECT generate_series(1,1000) AS a");
-my $current_lsn =
-  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_lsn();");
 
 # Note the presence of this checkpoint for the archive_cleanup_command
 # check done below, before switching to a new segment.
 $node_primary->safe_psql('postgres', "CHECKPOINT");
 
+my $current_lsn =
+  $node_primary->safe_psql('postgres', "SELECT pg_current_wal_lsn();");
+
 # Force archiving of WAL file to make it present on primary
 $node_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
 


^ permalink  raw  reply  [nested|flat] 21+ messages in thread

* Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman
  2022-04-07 17:52 Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Andres Freund <[email protected]>
  2022-04-07 17:57 ` Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Tom Lane <[email protected]>
  2022-04-07 18:12   ` Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Andres Freund <[email protected]>
  2022-04-08 21:55     ` Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Tom Lane <[email protected]>
@ 2022-04-08 23:56       ` Andres Freund <[email protected]>
  1 sibling, 0 replies; 21+ messages in thread

From: Andres Freund @ 2022-04-08 23:56 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Michael Paquier <[email protected]>; Thomas Munro <[email protected]>; [email protected]

Hi,

On 2022-04-08 17:55:51 -0400, Tom Lane wrote:
> After seeing skink's results, I tried running that test under valgrind
> here, and it fails just like that every time.  skink's history allows
> us to bound the failure introduction between 79b716cfb7 and
> d7ab2a9a3c, which I think makes it just about certain that it was
> 5dc0418fab (Prefetch data referenced by the WAL, take II), though I've
> not bisected to be 100% sure.

I've tested it, it's 5dc0418fab that makes the difference. I reduced the cycle
time by making initdb not go through valgrind, but have normal postgres
instances go through it.


> On the whole, I'm not sure that the WAL prefetch logic is noticeably
> more stable than when we booted it out last year :-(.

IDK. Last year's issues seems to have largely been caused by a flaky
machine. And a bug, if it's that, in some archiving corner case that's not
normally reached during tests...

Greetings,

Andres Freund






^ permalink  raw  reply  [nested|flat] 21+ messages in thread

* Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman
  2022-04-07 17:52 Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Andres Freund <[email protected]>
  2022-04-07 17:57 ` Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Tom Lane <[email protected]>
  2022-04-07 18:12   ` Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Andres Freund <[email protected]>
  2022-04-08 21:55     ` Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Tom Lane <[email protected]>
@ 2022-04-09 00:59       ` Andres Freund <[email protected]>
  1 sibling, 0 replies; 21+ messages in thread

From: Andres Freund @ 2022-04-09 00:59 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Michael Paquier <[email protected]>; Thomas Munro <[email protected]>; [email protected]

Hi,

On 2022-04-08 17:55:51 -0400, Tom Lane wrote:
> I tried adjusting the patch so it does guarantee that (as attached),
> and in two out of two tries it got past the archive_cleanup_command
> failure but then hung up waiting for standby2 to promote.

Adding

$node_standby->safe_psql('postgres', "SELECT pg_switch_wal()");
just after
$node_standby2->start;

makes the tests pass here.


What is that second test really testing?

# Check the presence of temporary files specifically generated during
# archive recovery.  To ensure the presence of the temporary history
# file, switch to a timeline large enough to allow a standby to recover
# a history file from an archive.  As this requires at least two timeline
# switches, promote the existing standby first.  Then create a second
# standby based on the promoted one.  Finally, the second standby is
# promoted.

Note "Then create a second standby based on the promoted one." - but that's
not actually what's happening:

$node_standby2->init_from_backup($node_primary, $backup_name,
	has_restoring => 1);

It's created from the original primary, not the first standby, as the
description says...

Both nodes get promoted independently, in a run without valgrind:

standby:
2022-04-08 17:23:42.966 PDT [2463835][startup][1/0:0][] LOG:  INSERT @ 0/4000058:  - XLOG/END_OF_RECOVERY: tli 2; prev tli 1; time 2022-04-08 17:23:42.96686-07
2022-04-08 17:23:42.966 PDT [2463835][startup][1/0:0][] LOG:  xlog flush request 0/4000058; write 0/4000000; flush 0/4000000

standby2:
2022-04-08 17:23:43.307 PDT [2463999][startup][1/0:0][] LOG:  INSERT @ 0/4000058:  - XLOG/END_OF_RECOVERY: tli 3; prev tli 1; time 2022-04-08 17:23:43.307443->
2022-04-08 17:23:43.307 PDT [2463999][startup][1/0:0][] LOG:  xlog flush request 0/4000058; write 0/4000000; flush 0/4000000

except that standby2 can't choose tli 2 because it finds it used.

Sure looks like something is funky with that test.


But I think there's also something funky in the prefetching logic. I think it
may attempt restoring during prefetching somehow, even though there's code
that appears to try to prevent that?

on standby2 I can see replay progress like the following:
2022-04-08 17:02:12.310 PDT [2441453][startup][1/0:0][] LOG:  REDO @ 0/3024488; LSN 0/30244C8: prev 0/3024448; xid 725; len 3; blkref #0: rel 1663/5/16384, blk 4 - Heap/INSERT: off 60 flags 0x00
2022-04-08 17:02:12.311 PDT [2441453][startup][1/0:0][] DEBUG:  record known xact 725 latestObservedXid 725
2022-04-08 17:02:12.311 PDT [2441453][startup][1/0:0][] CONTEXT:  WAL redo at 0/3024488 for Heap/INSERT: off 60 flags 0x00; blkref #0: rel 1663/5/16384, blk 4
2022-04-08 17:02:12.312 PDT [2441453][startup][1/0:0][] DEBUG:  executing restore command "cp "/home/andres/build/postgres/dev-assert/vpath/src/test/recovery/tmp_check/t_002_archiving_primary_data/archives/000000010000000000000004" "pg_wal/RECOVERYXLOG""
2022-04-08 17:02:13.855 PDT [2441453][startup][1/0:0][] DEBUG:  could not restore file "000000010000000000000004" from archive: child process exited with exit code 1
2022-04-08 17:02:13.855 PDT [2441453][startup][1/0:0][] DEBUG:  could not open file "pg_wal/000000010000000000000004": No such file or directory
2022-04-08 17:02:13.856 PDT [2441453][startup][1/0:0][] LOG:  REDO @ 0/30244C8; LSN 0/3024508: prev 0/3024488; xid 725; len 3; blkref #0: rel 1663/5/16384, blk 4 - Heap/INSERT: off 61 flags 0x00
2022-04-08 17:02:13.856 PDT [2441453][startup][1/0:0][] DEBUG:  record known xact 725 latestObservedXid 725
2022-04-08 17:02:13.856 PDT [2441453][startup][1/0:0][] CONTEXT:  WAL redo at 0/30244C8 for Heap/INSERT: off 61 flags 0x00; blkref #0: rel 1663/5/16384, blk 4
2022-04-08 17:02:13.857 PDT [2441453][startup][1/0:0][] DEBUG:  executing restore command "cp "/home/andres/build/postgres/dev-assert/vpath/src/test/recovery/tmp_check/t_002_archiving_primary_data/archives/000000010000000000000004" "pg_wal/RECOVERYXLOG""
2022-04-08 17:02:15.413 PDT [2441453][startup][1/0:0][] DEBUG:  could not restore file "000000010000000000000004" from archive: child process exited with exit code 1
2022-04-08 17:02:15.413 PDT [2441453][startup][1/0:0][] DEBUG:  could not open file "pg_wal/000000010000000000000004": No such file or directory
2022-04-08 17:02:15.414 PDT [2441453][startup][1/0:0][] LOG:  REDO @ 0/3024508; LSN 0/3024548: prev 0/30244C8; xid 725; len 3; blkref #0: rel 1663/5/16384, blk 4 - Heap/INSERT: off 62 flags 0x00
2022-04-08 17:02:15.414 PDT [2441453][startup][1/0:0][] DEBUG:  record known xact 725 latestObservedXid 725
2022-04-08 17:02:15.414 PDT [2441453][startup][1/0:0][] CONTEXT:  WAL redo at 0/3024508 for Heap/INSERT: off 62 flags 0x00; blkref #0: rel 1663/5/16384, blk 4
2022-04-08 17:02:15.415 PDT [2441453][startup][1/0:0][] DEBUG:  executing restore command "cp "/home/andres/build/postgres/dev-assert/vpath/src/test/recovery/tmp_check/t_002_archiving_primary_data/archives/000000010000000000000004" "pg_wal/RECOVERYXLOG""

note that we appear to wait between replaying of records, even though we
apparently have WAL for the next record!

And interestingly I'm not seeing the
"switched WAL source from stream to archive after failure"
lines I'd expect.

Greetings,

Andres Freund






^ permalink  raw  reply  [nested|flat] 21+ messages in thread


end of thread, other threads:[~2022-04-09 00:59 UTC | newest]

Thread overview: 21+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2017-12-12 04:06 Re: [HACKERS] Pluggable storage Haribabu Kommi <[email protected]>
2017-12-27 03:54 ` Haribabu Kommi <[email protected]>
2017-12-27 12:33   ` Alexander Korotkov <[email protected]>
2018-01-03 07:08     ` Haribabu Kommi <[email protected]>
2018-01-03 23:00       ` Alexander Korotkov <[email protected]>
2018-01-04 05:03         ` Haribabu Kommi <[email protected]>
2018-01-04 22:55           ` Alexander Korotkov <[email protected]>
2018-01-04 23:20             ` Haribabu Kommi <[email protected]>
2018-01-05 16:20       ` Robert Haas <[email protected]>
2018-01-05 19:31         ` Alexander Korotkov <[email protected]>
2018-01-09 12:42           ` Haribabu Kommi <[email protected]>
2021-07-31 00:54 [PATCH 3/4] Generation: keeper block Tomas Vondra <[email protected]>
2021-07-31 00:54 [PATCH 3/4] Generation: keeper block Tomas Vondra <[email protected]>
2021-07-31 00:54 [PATCH 2/3] Generation: keeper block Tomas Vondra <[email protected]>
2022-04-07 17:52 Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Andres Freund <[email protected]>
2022-04-07 17:57 ` Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Tom Lane <[email protected]>
2022-04-07 18:12   ` Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Andres Freund <[email protected]>
2022-04-08 16:36     ` Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Tom Lane <[email protected]>
2022-04-08 21:55     ` Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Tom Lane <[email protected]>
2022-04-08 23:56       ` Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Andres Freund <[email protected]>
2022-04-09 00:59       ` Re: pgsql: Add TAP test for archive_cleanup_command and recovery_end_comman Andres Freund <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox